@@ -17,6 +17,7 @@ import (
17
17
"github.com/cockroachdb/pebble/internal/humanize"
18
18
"github.com/cockroachdb/pebble/internal/invariants"
19
19
"github.com/cockroachdb/pebble/internal/manifest"
20
+ "github.com/cockroachdb/pebble/internal/problemspans"
20
21
)
21
22
22
23
// The minimum count for an intra-L0 compaction. This matches the RocksDB
@@ -39,6 +40,10 @@ type compactionEnv struct {
39
40
earliestSnapshotSeqNum base.SeqNum
40
41
inProgressCompactions []compactionInfo
41
42
readCompactionEnv readCompactionEnv
43
+ // problemSpans is checked by the compaction picker to avoid compactions that
44
+ // overlap an active "problem span". It can be nil when there are no problem
45
+ // spans.
46
+ problemSpans * problemspans.ByLevel
42
47
}
43
48
44
49
type compactionPicker interface {
@@ -414,7 +419,10 @@ func (pc *pickedCompaction) maybeExpandBounds(smallest InternalKey, largest Inte
414
419
// setupInputs returns true if a compaction has been set up. It returns false if
415
420
// a concurrent compaction is occurring on the start or output level files.
416
421
func (pc * pickedCompaction ) setupInputs (
417
- opts * Options , diskAvailBytes uint64 , startLevel * compactionLevel ,
422
+ opts * Options ,
423
+ diskAvailBytes uint64 ,
424
+ startLevel * compactionLevel ,
425
+ problemSpans * problemspans.ByLevel ,
418
426
) bool {
419
427
// maxExpandedBytes is the maximum size of an expanded compaction. If
420
428
// growing a compaction results in a larger size, the original compaction
@@ -423,7 +431,7 @@ func (pc *pickedCompaction) setupInputs(
423
431
opts , adjustedOutputLevel (pc .outputLevel .level , pc .baseLevel ), diskAvailBytes ,
424
432
)
425
433
426
- if anyTablesCompacting (startLevel .files ) {
434
+ if ! canCompactTables (startLevel .files , startLevel . level , problemSpans ) {
427
435
return false
428
436
}
429
437
@@ -434,7 +442,7 @@ func (pc *pickedCompaction) setupInputs(
434
442
// left empty for those.
435
443
if startLevel .level != pc .outputLevel .level {
436
444
pc .outputLevel .files = pc .version .Overlaps (pc .outputLevel .level , pc .userKeyBounds ())
437
- if anyTablesCompacting (pc .outputLevel .files ) {
445
+ if ! canCompactTables (pc .outputLevel .files , pc . outputLevel . level , problemSpans ) {
438
446
return false
439
447
}
440
448
@@ -504,7 +512,7 @@ func (pc *pickedCompaction) setupInputs(
504
512
* pc .lcf = * oldLcf
505
513
}
506
514
}
507
- } else if pc .grow (pc .smallest , pc .largest , maxExpandedBytes , startLevel ) {
515
+ } else if pc .grow (pc .smallest , pc .largest , maxExpandedBytes , startLevel , problemSpans ) {
508
516
pc .maybeExpandBounds (manifest .KeyRange (pc .cmp ,
509
517
startLevel .files .All (), pc .outputLevel .files .All ()))
510
518
}
@@ -521,13 +529,16 @@ func (pc *pickedCompaction) setupInputs(
521
529
// c.level+1 files in the compaction, and returns whether the inputs grew. sm
522
530
// and la are the smallest and largest InternalKeys in all of the inputs.
523
531
func (pc * pickedCompaction ) grow (
524
- sm , la InternalKey , maxExpandedBytes uint64 , startLevel * compactionLevel ,
532
+ sm , la InternalKey ,
533
+ maxExpandedBytes uint64 ,
534
+ startLevel * compactionLevel ,
535
+ problemSpans * problemspans.ByLevel ,
525
536
) bool {
526
537
if pc .outputLevel .files .Empty () {
527
538
return false
528
539
}
529
540
grow0 := pc .version .Overlaps (startLevel .level , base .UserKeyBoundsFromInternal (sm , la ))
530
- if anyTablesCompacting (grow0 ) {
541
+ if ! canCompactTables (grow0 , startLevel . level , problemSpans ) {
531
542
return false
532
543
}
533
544
if grow0 .Len () <= startLevel .files .Len () {
@@ -540,10 +551,10 @@ func (pc *pickedCompaction) grow(
540
551
// sm1 and la1 could shift the output level keyspace when pc.outputLevel.files is set to grow1.
541
552
sm1 , la1 := manifest .KeyRange (pc .cmp , grow0 .All (), pc .outputLevel .files .All ())
542
553
grow1 := pc .version .Overlaps (pc .outputLevel .level , base .UserKeyBoundsFromInternal (sm1 , la1 ))
543
- if anyTablesCompacting ( grow1 ) {
554
+ if grow1 . Len () != pc . outputLevel . files . Len ( ) {
544
555
return false
545
556
}
546
- if grow1 . Len () != pc .outputLevel .files . Len ( ) {
557
+ if ! canCompactTables ( grow1 , pc .outputLevel .level , problemSpans ) {
547
558
return false
548
559
}
549
560
startLevel .files = grow0
@@ -570,18 +581,23 @@ func (pc *pickedCompaction) setupMultiLevelCandidate(opts *Options, diskAvailByt
570
581
pc .startLevel = & pc .inputs [0 ]
571
582
pc .extraLevels = []* compactionLevel {& pc .inputs [1 ]}
572
583
pc .outputLevel = & pc .inputs [2 ]
573
- return pc .setupInputs (opts , diskAvailBytes , pc .extraLevels [len (pc .extraLevels )- 1 ])
584
+ return pc .setupInputs (opts , diskAvailBytes , pc .extraLevels [len (pc .extraLevels )- 1 ], nil /* TODO(radu) */ )
574
585
}
575
586
576
- // anyTablesCompacting returns true if any tables in the level slice are
577
- // compacting.
578
- func anyTablesCompacting (inputs manifest.LevelSlice ) bool {
587
+ // canCompactTables returns true if the tables in the level slice are not
588
+ // compacting already and don't intersect any problem spans.
589
+ func canCompactTables (
590
+ inputs manifest.LevelSlice , level int , problemSpans * problemspans.ByLevel ,
591
+ ) bool {
579
592
for f := range inputs .All () {
580
593
if f .IsCompacting () {
581
- return true
594
+ return false
595
+ }
596
+ if problemSpans != nil && problemSpans .Overlaps (level , f .UserKeyBounds ()) {
597
+ return false
582
598
}
583
599
}
584
- return false
600
+ return true
585
601
}
586
602
587
603
// newCompactionPickerByScore creates a compactionPickerByScore associated with
@@ -1033,6 +1049,7 @@ func pickCompactionSeedFile(
1033
1049
opts * Options ,
1034
1050
level , outputLevel int ,
1035
1051
earliestSnapshotSeqNum base.SeqNum ,
1052
+ problemSpans * problemspans.ByLevel ,
1036
1053
) (manifest.LevelFile , bool ) {
1037
1054
// Select the file within the level to compact. We want to minimize write
1038
1055
// amplification, but also ensure that (a) deletes are propagated to the
@@ -1067,22 +1084,36 @@ func pickCompactionSeedFile(
1067
1084
1068
1085
for f := startIter .First (); f != nil ; f = startIter .Next () {
1069
1086
var overlappingBytes uint64
1070
- compacting := f .IsCompacting ()
1071
- if compacting {
1087
+ if f .IsCompacting () {
1072
1088
// Move on if this file is already being compacted. We'll likely
1073
1089
// still need to move past the overlapping output files regardless,
1074
1090
// but in cases where all start-level files are compacting we won't.
1075
1091
continue
1076
1092
}
1093
+ if problemSpans != nil && problemSpans .Overlaps (level , f .UserKeyBounds ()) {
1094
+ // File touches problem span which temporarily disallows auto compactions.
1095
+ continue
1096
+ }
1077
1097
1078
1098
// Trim any output-level files smaller than f.
1079
1099
for outputFile != nil && sstableKeyCompare (cmp , outputFile .Largest , f .Smallest ) < 0 {
1080
1100
outputFile = outputIter .Next ()
1081
1101
}
1082
1102
1083
- for outputFile != nil && sstableKeyCompare (cmp , outputFile .Smallest , f .Largest ) <= 0 && ! compacting {
1103
+ skip := false
1104
+ for outputFile != nil && sstableKeyCompare (cmp , outputFile .Smallest , f .Largest ) <= 0 {
1084
1105
overlappingBytes += outputFile .Size
1085
- compacting = compacting || outputFile .IsCompacting ()
1106
+ if outputFile .IsCompacting () {
1107
+ // If one of the overlapping files is compacting, we're not going to be
1108
+ // able to compact f anyway, so skip it.
1109
+ skip = true
1110
+ break
1111
+ }
1112
+ if problemSpans != nil && problemSpans .Overlaps (outputLevel , outputFile .UserKeyBounds ()) {
1113
+ // Overlapping file touches problem span which temporarily disallows auto compactions.
1114
+ skip = true
1115
+ break
1116
+ }
1086
1117
1087
1118
// For files in the bottommost level of the LSM, the
1088
1119
// Stats.RangeDeletionsBytesEstimate field is set to the estimate
@@ -1129,11 +1160,7 @@ func pickCompactionSeedFile(
1129
1160
}
1130
1161
outputFile = outputIter .Next ()
1131
1162
}
1132
-
1133
- // If the input level file or one of the overlapping files is
1134
- // compacting, we're not going to be able to compact this file
1135
- // anyways, so skip it.
1136
- if compacting {
1163
+ if skip {
1137
1164
continue
1138
1165
}
1139
1166
@@ -1312,7 +1339,7 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
1312
1339
1313
1340
// info.level > 0
1314
1341
var ok bool
1315
- info .file , ok = pickCompactionSeedFile (p .vers , p .virtualBackings , p .opts , info .level , info .outputLevel , env .earliestSnapshotSeqNum )
1342
+ info .file , ok = pickCompactionSeedFile (p .vers , p .virtualBackings , p .opts , info .level , info .outputLevel , env .earliestSnapshotSeqNum , env . problemSpans )
1316
1343
if ! ok {
1317
1344
continue
1318
1345
}
@@ -1504,9 +1531,7 @@ func (p *compactionPickerByScore) pickedCompactionFromCandidateFile(
1504
1531
return nil
1505
1532
}
1506
1533
1507
- if ! pc .setupInputs (p .opts , env .diskAvailBytes , pc .startLevel ) {
1508
- // TODO(radu): do we expect this to happen? (it does seem to happen if I add
1509
- // a log here).
1534
+ if ! pc .setupInputs (p .opts , env .diskAvailBytes , pc .startLevel , env .problemSpans ) {
1510
1535
return nil
1511
1536
}
1512
1537
@@ -1639,8 +1664,7 @@ func pickAutoLPositive(
1639
1664
}
1640
1665
}
1641
1666
1642
- if ! pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel ) {
1643
- opts .Logger .Errorf ("%v" , base .AssertionFailedf ("setupInputs failed" ))
1667
+ if ! pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel , env .problemSpans ) {
1644
1668
return nil
1645
1669
}
1646
1670
return pc .maybeAddLevel (opts , env .diskAvailBytes )
@@ -1781,10 +1805,10 @@ func pickL0(
1781
1805
//
1782
1806
// TODO(bilal) Remove the minCompactionDepth parameter once fixing it at 1
1783
1807
// has been shown to not cause a performance regression.
1784
- lcf := l0Organizer .PickBaseCompaction (opts .Logger , 1 , vers .Levels [baseLevel ].Slice ())
1808
+ lcf := l0Organizer .PickBaseCompaction (opts .Logger , 1 , vers .Levels [baseLevel ].Slice (), baseLevel , env . problemSpans )
1785
1809
if lcf != nil {
1786
1810
pc := newPickedCompactionFromL0 (lcf , opts , vers , l0Organizer , baseLevel , true )
1787
- if pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel ) {
1811
+ if pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel , env . problemSpans ) {
1788
1812
if pc .startLevel .files .Empty () {
1789
1813
opts .Logger .Errorf ("%v" , base .AssertionFailedf ("empty compaction chosen" ))
1790
1814
}
@@ -1798,10 +1822,10 @@ func pickL0(
1798
1822
// compaction. Note that we pass in L0CompactionThreshold here as opposed to
1799
1823
// 1, since choosing a single sublevel intra-L0 compaction is
1800
1824
// counterproductive.
1801
- lcf = l0Organizer .PickIntraL0Compaction (env .earliestUnflushedSeqNum , minIntraL0Count )
1825
+ lcf = l0Organizer .PickIntraL0Compaction (env .earliestUnflushedSeqNum , minIntraL0Count , env . problemSpans )
1802
1826
if lcf != nil {
1803
1827
pc := newPickedCompactionFromL0 (lcf , opts , vers , l0Organizer , 0 , false )
1804
- if pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel ) {
1828
+ if pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel , env . problemSpans ) {
1805
1829
if pc .startLevel .files .Empty () {
1806
1830
opts .Logger .Fatalf ("empty compaction chosen" )
1807
1831
}
@@ -1854,7 +1878,9 @@ func newPickedManualCompaction(
1854
1878
// Nothing to do
1855
1879
return nil , false
1856
1880
}
1857
- if ! pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel ) {
1881
+ // We use nil problemSpans because we don't want problem spans to prevent
1882
+ // manual compactions.
1883
+ if ! pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel , nil /* problemSpans */ ) {
1858
1884
// setupInputs returned false indicating there's a conflicting
1859
1885
// concurrent compaction.
1860
1886
return nil , true
@@ -1899,7 +1925,7 @@ func pickDownloadCompaction(
1899
1925
pc = newPickedCompaction (opts , vers , l0Organizer , level , level , baseLevel )
1900
1926
pc .kind = kind
1901
1927
pc .startLevel .files = manifest .NewLevelSliceKeySorted (opts .Comparer .Compare , []* tableMetadata {file })
1902
- if ! pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel ) {
1928
+ if ! pc .setupInputs (opts , env .diskAvailBytes , pc .startLevel , nil /* problemSpans */ ) {
1903
1929
// setupInputs returned false indicating there's a conflicting
1904
1930
// concurrent compaction.
1905
1931
return nil
@@ -1950,7 +1976,7 @@ func pickReadTriggeredCompactionHelper(
1950
1976
pc = newPickedCompaction (p .opts , p .vers , p .l0Organizer , rc .level , defaultOutputLevel (rc .level , p .baseLevel ), p .baseLevel )
1951
1977
1952
1978
pc .startLevel .files = overlapSlice
1953
- if ! pc .setupInputs (p .opts , env .diskAvailBytes , pc .startLevel ) {
1979
+ if ! pc .setupInputs (p .opts , env .diskAvailBytes , pc .startLevel , env . problemSpans ) {
1954
1980
return nil
1955
1981
}
1956
1982
if inputRangeAlreadyCompacting (env , pc ) {
0 commit comments