@@ -1299,7 +1299,24 @@ func TestPartitionCompactor_ShouldCompactOnlyShardsOwnedByTheInstanceOnShardingE
12991299
13001300 // Get all of the unique group hashes so that they can be used to ensure all groups were compacted
13011301 groupHashes [groupHash ]++
1302- bucketClient .MockGet (userID + "/partitioned-groups/" + fmt .Sprint (groupHash )+ ".json" , "" , nil )
1302+
1303+ // Create mock partitioned group info for the new validation check
1304+ partitionedGroupInfo := PartitionedGroupInfo {
1305+ PartitionedGroupID : groupHash ,
1306+ PartitionCount : 1 ,
1307+ Partitions : []Partition {
1308+ {
1309+ PartitionID : 0 ,
1310+ Blocks : []ulid.ULID {ulid .MustParse (blockID )},
1311+ },
1312+ },
1313+ RangeStart : blockTimes ["startTime" ],
1314+ RangeEnd : blockTimes ["endTime" ],
1315+ CreationTime : time .Now ().Unix (),
1316+ Version : PartitionedGroupInfoVersion1 ,
1317+ }
1318+ partitionedGroupInfoContent , _ := json .Marshal (partitionedGroupInfo )
1319+ bucketClient .MockGet (userID + "/partitioned-groups/" + fmt .Sprint (groupHash )+ ".json" , string (partitionedGroupInfoContent ), nil )
13031320 bucketClient .MockUpload (userID + "/partitioned-groups/" + fmt .Sprint (groupHash )+ ".json" , nil )
13041321 }
13051322
@@ -1826,3 +1843,157 @@ func TestPartitionCompactor_ShouldNotFailCompactionIfAccessDeniedErrReturnedFrom
18261843
18271844 require .NoError (t , services .StopAndAwaitTerminated (context .Background (), c ))
18281845}
1846+
1847+ func TestPartitionCompactionRaceCondition (t * testing.T ) {
1848+ t .Run ("planner_detects_missing_partition_group" , func (t * testing.T ) {
1849+ setup := newRaceConditionTestSetup (12345 )
1850+
1851+ // Create a planner that will try to process blocks but find missing partition group
1852+ planner := setup .createPlanner ()
1853+ cortexMetaExtensions := setup .createCortexMetaExtensions (time .Now ().Unix ())
1854+ metasByMinTime := setup .createTestMetadata ()
1855+
1856+ result , err := planner .PlanWithPartition (setup .ctx , metasByMinTime , cortexMetaExtensions , nil )
1857+
1858+ require .Error (t , err , "Planner should fail when partition group is missing" )
1859+ require .Nil (t , result , "Should not return any result when partition group is missing" )
1860+ require .ErrorIs (t , err , plannerCompletedPartitionError , "Error should be completed partition error when partition group is missing" )
1861+ })
1862+
1863+ t .Run ("planner_detects_creation_time_mismatch" , func (t * testing.T ) {
1864+ setup := newRaceConditionTestSetup (54321 )
1865+ originalCreationTime := time .Now ().Unix ()
1866+
1867+ // Create initial partition group
1868+ partitionedGroupInfo := setup .createPartitionedGroupInfo (originalCreationTime )
1869+ _ , err := UpdatePartitionedGroupInfo (setup .ctx , setup .bucket , setup .logger , * partitionedGroupInfo )
1870+ require .NoError (t , err )
1871+
1872+ // Simulate cleaner deleting partition group
1873+ partitionGroupFile := GetPartitionedGroupFile (setup .partitionedGroupID )
1874+ err = setup .bucket .Delete (setup .ctx , partitionGroupFile )
1875+ require .NoError (t , err )
1876+
1877+ // Create new partition group with same ID but different creation time
1878+ newCreationTime := time .Now ().Unix () + 200
1879+ newPartitionedGroupInfo := setup .createPartitionedGroupInfo (newCreationTime )
1880+ _ , err = UpdatePartitionedGroupInfo (setup .ctx , setup .bucket , setup .logger , * newPartitionedGroupInfo )
1881+ require .NoError (t , err )
1882+
1883+ // Test planner creation time validation
1884+ planner := setup .createPlanner ()
1885+ cortexMetaExtensions := setup .createCortexMetaExtensions (originalCreationTime ) // OLD creation time
1886+ metasByMinTime := setup .createTestMetadata ()
1887+
1888+ result , err := planner .PlanWithPartition (setup .ctx , metasByMinTime , cortexMetaExtensions , nil )
1889+
1890+ require .Error (t , err , "Planner should detect creation time mismatch" )
1891+ require .ErrorIs (t , err , plannerCompletedPartitionError , "Should abort with completed partition error" )
1892+ require .Nil (t , result , "Should not return any result when aborting" )
1893+ })
1894+
1895+ t .Run ("normal_operation_with_matching_creation_time" , func (t * testing.T ) {
1896+ setup := newRaceConditionTestSetup (99999 )
1897+ creationTime := time .Now ().Unix ()
1898+
1899+ // Create partition group
1900+ partitionedGroupInfo := setup .createPartitionedGroupInfo (creationTime )
1901+ _ , err := UpdatePartitionedGroupInfo (setup .ctx , setup .bucket , setup .logger , * partitionedGroupInfo )
1902+ require .NoError (t , err )
1903+
1904+ // Create planner and test with matching creation time
1905+ planner := setup .createPlanner ()
1906+ cortexMetaExtensions := setup .createCortexMetaExtensions (creationTime ) // MATCHING creation time
1907+ metasByMinTime := setup .createTestMetadata ()
1908+
1909+ result , err := planner .PlanWithPartition (setup .ctx , metasByMinTime , cortexMetaExtensions , nil )
1910+
1911+ require .NoError (t , err , "Should not fail when creation times match" )
1912+ require .NotNil (t , result , "Should return result when creation times match" )
1913+ })
1914+ }
1915+
1916+ // raceConditionTestSetup provides common setup for race condition tests
1917+ type raceConditionTestSetup struct {
1918+ ctx context.Context
1919+ logger log.Logger
1920+ bucket objstore.InstrumentedBucket
1921+ userID string
1922+ partitionedGroupID uint32
1923+ partitionID int
1924+ partitionCount int
1925+ ranges []int64
1926+ noCompBlocksFunc func () map [ulid.ULID ]* metadata.NoCompactMark
1927+ }
1928+
1929+ func newRaceConditionTestSetup (partitionedGroupID uint32 ) * raceConditionTestSetup {
1930+ return & raceConditionTestSetup {
1931+ ctx : context .Background (),
1932+ logger : log .NewNopLogger (),
1933+ bucket : objstore .WithNoopInstr (objstore .NewInMemBucket ()),
1934+ userID : "test-user" ,
1935+ partitionedGroupID : partitionedGroupID ,
1936+ partitionID : 0 ,
1937+ partitionCount : 2 ,
1938+ ranges : []int64 {2 * 60 * 60 * 1000 }, // 2 hours in milliseconds
1939+ noCompBlocksFunc : func () map [ulid.ULID ]* metadata.NoCompactMark { return nil },
1940+ }
1941+ }
1942+
1943+ func (s * raceConditionTestSetup ) createPartitionedGroupInfo (creationTime int64 ) * PartitionedGroupInfo {
1944+ return & PartitionedGroupInfo {
1945+ PartitionedGroupID : s .partitionedGroupID ,
1946+ PartitionCount : s .partitionCount ,
1947+ Partitions : []Partition {
1948+ {PartitionID : 0 , Blocks : []ulid.ULID {ulid .MustNew (ulid .Now (), nil )}},
1949+ {PartitionID : 1 , Blocks : []ulid.ULID {ulid .MustNew (ulid .Now (), nil )}},
1950+ },
1951+ RangeStart : 0 ,
1952+ RangeEnd : 2 * 60 * 60 * 1000 ,
1953+ CreationTime : creationTime ,
1954+ Version : PartitionedGroupInfoVersion1 ,
1955+ }
1956+ }
1957+
1958+ func (s * raceConditionTestSetup ) createPlanner () * PartitionCompactionPlanner {
1959+ // Use the same metrics pattern as other tests
1960+ registerer := prometheus .NewPedanticRegistry ()
1961+ metrics := newCompactorMetrics (registerer )
1962+
1963+ return NewPartitionCompactionPlanner (
1964+ s .ctx ,
1965+ s .bucket ,
1966+ s .logger ,
1967+ s .ranges ,
1968+ s .noCompBlocksFunc ,
1969+ "test-compactor" ,
1970+ s .userID ,
1971+ time .Second ,
1972+ 10 * time .Minute ,
1973+ time .Minute ,
1974+ metrics ,
1975+ )
1976+ }
1977+
1978+ func (s * raceConditionTestSetup ) createCortexMetaExtensions (creationTime int64 ) * cortex_tsdb.CortexMetaExtensions {
1979+ return & cortex_tsdb.CortexMetaExtensions {
1980+ PartitionInfo : & cortex_tsdb.PartitionInfo {
1981+ PartitionedGroupID : s .partitionedGroupID ,
1982+ PartitionCount : s .partitionCount ,
1983+ PartitionID : s .partitionID ,
1984+ PartitionedGroupCreationTime : creationTime ,
1985+ },
1986+ }
1987+ }
1988+
1989+ func (s * raceConditionTestSetup ) createTestMetadata () []* metadata.Meta {
1990+ return []* metadata.Meta {
1991+ {
1992+ BlockMeta : tsdb.BlockMeta {
1993+ ULID : ulid .MustNew (ulid .Now (), nil ),
1994+ MinTime : 0 ,
1995+ MaxTime : 2 * 60 * 60 * 1000 ,
1996+ },
1997+ },
1998+ }
1999+ }
0 commit comments