@@ -181,10 +181,8 @@ type pickedCompaction struct {
181
181
// - in multilevel compaction, the output level is the lowest level involved in
182
182
// the compaction
183
183
outputLevel * compactionLevel
184
- // extraLevels contain additional levels in between the input and output
185
- // levels that get compacted in multi level compactions
186
- extraLevels []* compactionLevel
187
- inputs []compactionLevel
184
+ // inputs contain levels involved in the compaction in ascending order
185
+ inputs []compactionLevel
188
186
// LBase at the time of compaction picking. Might be uninitialized for
189
187
// intra-L0 compactions.
190
188
baseLevel int
@@ -316,7 +314,6 @@ func (pc *pickedCompaction) String() string {
316
314
builder .WriteString (fmt .Sprintf (`inputs=%s, ` , pc .inputs ))
317
315
builder .WriteString (fmt .Sprintf (`startlevel=%s, ` , pc .startLevel ))
318
316
builder .WriteString (fmt .Sprintf (`outputLevel=%s, ` , pc .outputLevel ))
319
- builder .WriteString (fmt .Sprintf (`extraLevels=%s, ` , pc .extraLevels ))
320
317
builder .WriteString (fmt .Sprintf (`l0SublevelInfo=%s, ` , pc .startLevel .l0SublevelInfo ))
321
318
builder .WriteString (fmt .Sprintf (`lcf=%s` , pc .lcf ))
322
319
return builder .String ()
@@ -348,15 +345,12 @@ func (pc *pickedCompaction) clone() *pickedCompaction {
348
345
}
349
346
350
347
newPC .inputs = make ([]compactionLevel , len (pc .inputs ))
351
- newPC .extraLevels = make ([]* compactionLevel , 0 , len (pc .extraLevels ))
352
348
for i := range pc .inputs {
353
349
newPC .inputs [i ] = pc .inputs [i ].Clone ()
354
350
if i == 0 {
355
351
newPC .startLevel = & newPC .inputs [i ]
356
352
} else if i == len (pc .inputs )- 1 {
357
353
newPC .outputLevel = & newPC .inputs [i ]
358
- } else {
359
- newPC .extraLevels = append (newPC .extraLevels , & newPC .inputs [i ])
360
354
}
361
355
}
362
356
@@ -394,51 +388,56 @@ func (pc *pickedCompaction) maybeExpandBounds(smallest InternalKey, largest Inte
394
388
}
395
389
}
396
390
397
- // setupInputs returns true if a compaction has been set up. It returns false if
398
- // a concurrent compaction is occurring on the start or output level files.
391
+ // setupInputs returns true if a compaction has been set up using the provided inputLevel and
392
+ // pc.outputLevel. It returns false if a concurrent compaction is occurring on the start or
393
+ // output level files. Note that inputLevel is not necessarily pc.startLevel. In multiLevel
394
+ // compactions, inputs are set by calling setupInputs once for each adjacent pair of levels.
395
+ // This will preserve level invariants when expanding the compaction. pc.smallest and pc.largest
396
+ // will be updated to reflect the key range of the inputs.
399
397
func (pc * pickedCompaction ) setupInputs (
400
398
opts * Options ,
401
399
diskAvailBytes uint64 ,
402
- startLevel * compactionLevel ,
400
+ inputLevel * compactionLevel ,
403
401
problemSpans * problemspans.ByLevel ,
404
402
) bool {
405
- // maxExpandedBytes is the maximum size of an expanded compaction. If
406
- // growing a compaction results in a larger size, the original compaction
407
- // is used instead.
408
- maxExpandedBytes := expandedCompactionByteSizeLimit (
409
- opts , adjustedOutputLevel (pc .outputLevel .level , pc .baseLevel ), diskAvailBytes ,
410
- )
411
-
412
- if ! canCompactTables (startLevel .files , startLevel .level , problemSpans ) {
403
+ if ! canCompactTables (inputLevel .files , inputLevel .level , problemSpans ) {
413
404
return false
414
405
}
415
406
416
- pc .maybeExpandBounds (manifest .KeyRange (pc .cmp , startLevel .files .All ()))
407
+ pc .maybeExpandBounds (manifest .KeyRange (pc .cmp , inputLevel .files .All ()))
417
408
418
- // Determine the sstables in the output level which overlap with the input
419
- // sstables. No need to do this for intra-L0 compactions; outputLevel.files is
420
- // left empty for those.
421
- if startLevel .level != pc .outputLevel .level {
409
+ // Setup output files and attempt to grow the inputLevel files with
410
+ // the expanded key range. No need to do this for intra-L0 compactions;
411
+ // outputLevel.files is left empty for those.
412
+ if inputLevel .level != pc .outputLevel .level {
413
+ // Determine the sstables in the output level which overlap with the compaction
414
+ // key range.
422
415
pc .outputLevel .files = pc .version .Overlaps (pc .outputLevel .level , pc .userKeyBounds ())
423
416
if ! canCompactTables (pc .outputLevel .files , pc .outputLevel .level , problemSpans ) {
424
417
return false
425
418
}
426
419
427
- pc .maybeExpandBounds (manifest .KeyRange (pc .cmp ,
428
- startLevel .files .All (), pc .outputLevel .files .All ()))
429
- }
420
+ pc .maybeExpandBounds (manifest .KeyRange (pc .cmp , pc .outputLevel .files .All ()))
430
421
431
- // Grow the sstables in startLevel.level as long as it doesn't affect the number
432
- // of sstables included from pc.outputLevel.level.
433
- if pc .lcf != nil && startLevel .level == 0 && pc .outputLevel .level != 0 {
434
- pc .growL0ForBase (maxExpandedBytes )
435
- } else if pc .grow (pc .smallest , pc .largest , maxExpandedBytes , startLevel , problemSpans ) {
436
- pc .maybeExpandBounds (manifest .KeyRange (pc .cmp ,
437
- startLevel .files .All (), pc .outputLevel .files .All ()))
422
+ // maxExpandedBytes is the maximum size of an expanded compaction. If
423
+ // growing a compaction results in a larger size, the original compaction
424
+ // is used instead.
425
+ maxExpandedBytes := expandedCompactionByteSizeLimit (
426
+ opts , adjustedOutputLevel (pc .outputLevel .level , pc .baseLevel ), diskAvailBytes ,
427
+ )
428
+
429
+ // Grow the sstables in inputLevel.level as long as it doesn't affect the number
430
+ // of sstables included from pc.outputLevel.level.
431
+ if pc .lcf != nil && inputLevel .level == 0 {
432
+ pc .growL0ForBase (maxExpandedBytes )
433
+ } else if pc .grow (pc .smallest , pc .largest , maxExpandedBytes , inputLevel , problemSpans ) {
434
+ // inputLevel was expanded, adjust key range if necessary.
435
+ pc .maybeExpandBounds (manifest .KeyRange (pc .cmp , inputLevel .files .All ()))
436
+ }
438
437
}
439
438
440
- if pc . startLevel .level == 0 {
441
- // We don't change the input files for the compaction beyond this point .
439
+ if inputLevel .level == 0 {
440
+ // If L0 is involved, it should always be the startLevel of the compaction .
442
441
pc .startLevel .l0SublevelInfo = generateSublevelInfo (pc .cmp , pc .startLevel .files )
443
442
}
444
443
@@ -451,34 +450,35 @@ func (pc *pickedCompaction) setupInputs(
451
450
func (pc * pickedCompaction ) grow (
452
451
sm , la InternalKey ,
453
452
maxExpandedBytes uint64 ,
454
- startLevel * compactionLevel ,
453
+ inputLevel * compactionLevel ,
455
454
problemSpans * problemspans.ByLevel ,
456
455
) bool {
457
456
if pc .outputLevel .files .Empty () {
458
457
return false
459
458
}
460
- grow0 := pc .version .Overlaps (startLevel .level , base .UserKeyBoundsFromInternal (sm , la ))
461
- if ! canCompactTables (grow0 , startLevel .level , problemSpans ) {
459
+ expandedInputLevel := pc .version .Overlaps (inputLevel .level , base .UserKeyBoundsFromInternal (sm , la ))
460
+ if ! canCompactTables (expandedInputLevel , inputLevel .level , problemSpans ) {
462
461
return false
463
462
}
464
- if grow0 .Len () <= startLevel .files .Len () {
463
+ if expandedInputLevel .Len () <= inputLevel .files .Len () {
465
464
return false
466
465
}
467
- if grow0 .AggregateSizeSum ()+ pc .outputLevel .files .AggregateSizeSum () >= maxExpandedBytes {
466
+ if expandedInputLevel .AggregateSizeSum ()+ pc .outputLevel .files .AggregateSizeSum () >= maxExpandedBytes {
468
467
return false
469
468
}
469
+ // Check that expanding the input level does not change the number of overlapping files in output level.
470
470
// We need to include the outputLevel iter because without it, in a multiLevel scenario,
471
- // sm1 and la1 could shift the output level keyspace when pc.outputLevel.files is set to grow1.
472
- sm1 , la1 := manifest .KeyRange (pc .cmp , grow0 .All (), pc .outputLevel .files .All ())
473
- grow1 := pc .version .Overlaps (pc .outputLevel .level , base .UserKeyBoundsFromInternal (sm1 , la1 ))
474
- if grow1 .Len () != pc .outputLevel .files .Len () {
471
+ // expandedInputLevel's key range not fully cover all files currently in pc.outputLevel,
472
+ // since pc.outputLevel was created using the entire key range which includes higher levels.
473
+ expandedOutputLevel := pc .version .Overlaps (pc .outputLevel .level ,
474
+ base .UserKeyBoundsFromInternal (manifest .KeyRange (pc .cmp , expandedInputLevel .All (), pc .outputLevel .files .All ())))
475
+ if expandedOutputLevel .Len () != pc .outputLevel .files .Len () {
475
476
return false
476
477
}
477
- if ! canCompactTables (grow1 , pc .outputLevel .level , problemSpans ) {
478
+ if ! canCompactTables (expandedOutputLevel , pc .outputLevel .level , problemSpans ) {
478
479
return false
479
480
}
480
- startLevel .files = grow0
481
- pc .outputLevel .files = grow1
481
+ inputLevel .files = expandedInputLevel
482
482
return true
483
483
}
484
484
@@ -565,7 +565,7 @@ func (pc *pickedCompaction) estimatedInputSize() uint64 {
565
565
return bytesToCompact
566
566
}
567
567
568
- // setupMultiLevelCandidated returns true if it successfully added another level
568
+ // setupMultiLevelCandidate returns true if it successfully added another level
569
569
// to the compaction.
570
570
func (pc * pickedCompaction ) setupMultiLevelCandidate (opts * Options , diskAvailBytes uint64 ) bool {
571
571
pc .inputs = append (pc .inputs , compactionLevel {level : pc .outputLevel .level + 1 })
@@ -574,9 +574,8 @@ func (pc *pickedCompaction) setupMultiLevelCandidate(opts *Options, diskAvailByt
574
574
// - startLevel and outputLevel pointers may be obsolete after appending to pc.inputs.
575
575
// - push outputLevel to extraLevels and move the new level to outputLevel
576
576
pc .startLevel = & pc .inputs [0 ]
577
- pc .extraLevels = []* compactionLevel {& pc .inputs [1 ]}
578
577
pc .outputLevel = & pc .inputs [2 ]
579
- return pc .setupInputs (opts , diskAvailBytes , pc .extraLevels [ len ( pc . extraLevels ) - 1 ], nil /* TODO(radu) */ )
578
+ return pc .setupInputs (opts , diskAvailBytes , & pc .inputs [ 1 ], nil /* TODO(radu) */ )
580
579
}
581
580
582
581
// canCompactTables returns true if the tables in the level slice are not
@@ -1947,8 +1946,8 @@ func newPickedManualCompaction(
1947
1946
return nil , false
1948
1947
}
1949
1948
if pc .outputLevel .level != outputLevel {
1950
- if len (pc .extraLevels ) > 0 {
1951
- // multilevel compactions relax this invariant
1949
+ if len (pc .inputs ) > 2 {
1950
+ // Multilevel compactions relax this invariant.
1952
1951
} else {
1953
1952
panic ("pebble: compaction picked unexpected output level" )
1954
1953
}
0 commit comments