Skip to content

Commit 57c482f

Browse files
committed
db: run score-based compactions before manual compactions
Score-based compactions are important for maintaining LSM health. Fixes #4571
1 parent 7ed6738 commit 57c482f

File tree

8 files changed

+202
-100
lines changed

8 files changed

+202
-100
lines changed

compaction.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,11 +1807,20 @@ func (d *DB) makeCompactionEnvLocked() *compactionEnv {
18071807

18081808
// pickAnyCompaction tries to pick a manual or automatic compaction.
18091809
func (d *DB) pickAnyCompaction(env compactionEnv) (pc *pickedCompaction) {
1810-
pc = d.pickManualCompaction(env)
1811-
if pc == nil && !d.opts.DisableAutomaticCompactions {
1812-
pc = d.mu.versions.picker.pickAuto(env)
1810+
// Pick a score-based compaction first, since a misshapen LSM is bad.
1811+
if !d.opts.DisableAutomaticCompactions {
1812+
if pc = d.mu.versions.picker.pickAutoScore(env); pc != nil {
1813+
return pc
1814+
}
1815+
}
1816+
// Pick a manual compaction, if any.
1817+
if pc = d.pickManualCompaction(env); pc != nil {
1818+
return pc
18131819
}
1814-
return pc
1820+
if !d.opts.DisableAutomaticCompactions {
1821+
return d.mu.versions.picker.pickAutoNonScore(env)
1822+
}
1823+
return nil
18151824
}
18161825

18171826
// runPickedCompaction kicks off the provided pickedCompaction. In case the

compaction_picker.go

Lines changed: 65 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,8 @@ type compactionPicker interface {
5050
getScores([]compactionInfo) [numLevels]float64
5151
getBaseLevel() int
5252
estimatedCompactionDebt(l0ExtraSize uint64) uint64
53-
pickAuto(env compactionEnv) (pc *pickedCompaction)
54-
pickElisionOnlyCompaction(env compactionEnv) (pc *pickedCompaction)
55-
pickRewriteCompaction(env compactionEnv) (pc *pickedCompaction)
56-
pickReadTriggeredCompaction(env compactionEnv) (pc *pickedCompaction)
53+
pickAutoScore(env compactionEnv) (pc *pickedCompaction)
54+
pickAutoNonScore(env compactionEnv) (pc *pickedCompaction)
5755
forceBaseLevel1()
5856
}
5957

@@ -1244,70 +1242,71 @@ func (p *compactionPickerByScore) getCompactionConcurrency() int {
12441242
return max(min(maxConcurrentCompactions, max(l0ReadAmpCompactions, compactionDebtCompactions)), 1)
12451243
}
12461244

1247-
// pickAuto picks the best compaction, if any.
1248-
//
1249-
// On each call, pickAuto computes per-level size adjustments based on
1250-
// in-progress compactions, and computes a per-level score. The levels are
1251-
// iterated over in decreasing score order trying to find a valid compaction
1252-
// anchored at that level.
1253-
//
1254-
// If a score-based compaction cannot be found, pickAuto falls back to looking
1255-
// for an elision-only compaction to remove obsolete keys.
1256-
func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompaction) {
1257-
scores := p.calculateLevelScores(env.inProgressCompactions)
1245+
// TODO(sumeer): remove unless someone actually finds this useful.
1246+
func (p *compactionPickerByScore) logCompactionForTesting(
1247+
env compactionEnv, scores [numLevels]candidateLevelInfo, pc *pickedCompaction,
1248+
) {
1249+
var buf bytes.Buffer
1250+
for i := 0; i < numLevels; i++ {
1251+
if i != 0 && i < p.baseLevel {
1252+
continue
1253+
}
12581254

1259-
// TODO(bananabrick): Either remove, or change this into an event sent to the
1260-
// EventListener.
1261-
logCompaction := func(pc *pickedCompaction) {
1262-
var buf bytes.Buffer
1263-
for i := 0; i < numLevels; i++ {
1264-
if i != 0 && i < p.baseLevel {
1265-
continue
1255+
var info *candidateLevelInfo
1256+
for j := range scores {
1257+
if scores[j].level == i {
1258+
info = &scores[j]
1259+
break
12661260
}
1261+
}
12671262

1268-
var info *candidateLevelInfo
1269-
for j := range scores {
1270-
if scores[j].level == i {
1271-
info = &scores[j]
1272-
break
1273-
}
1274-
}
1263+
marker := " "
1264+
if pc.startLevel.level == info.level {
1265+
marker = "*"
1266+
}
1267+
fmt.Fprintf(&buf, " %sL%d: %5.1f %5.1f %5.1f %5.1f %8s %8s",
1268+
marker, info.level, info.compensatedScoreRatio, info.compensatedScore,
1269+
info.uncompensatedScoreRatio, info.uncompensatedScore,
1270+
humanize.Bytes.Int64(int64(totalCompensatedSize(
1271+
p.vers.Levels[info.level].All(),
1272+
))),
1273+
humanize.Bytes.Int64(p.levelMaxBytes[info.level]),
1274+
)
12751275

1276-
marker := " "
1277-
if pc.startLevel.level == info.level {
1278-
marker = "*"
1279-
}
1280-
fmt.Fprintf(&buf, " %sL%d: %5.1f %5.1f %5.1f %5.1f %8s %8s",
1281-
marker, info.level, info.compensatedScoreRatio, info.compensatedScore,
1282-
info.uncompensatedScoreRatio, info.uncompensatedScore,
1283-
humanize.Bytes.Int64(int64(totalCompensatedSize(
1284-
p.vers.Levels[info.level].All(),
1285-
))),
1286-
humanize.Bytes.Int64(p.levelMaxBytes[info.level]),
1287-
)
1288-
1289-
count := 0
1290-
for i := range env.inProgressCompactions {
1291-
c := &env.inProgressCompactions[i]
1292-
if c.inputs[0].level != info.level {
1293-
continue
1294-
}
1295-
count++
1296-
if count == 1 {
1297-
fmt.Fprintf(&buf, " [")
1298-
} else {
1299-
fmt.Fprintf(&buf, " ")
1300-
}
1301-
fmt.Fprintf(&buf, "L%d->L%d", c.inputs[0].level, c.outputLevel)
1276+
count := 0
1277+
for i := range env.inProgressCompactions {
1278+
c := &env.inProgressCompactions[i]
1279+
if c.inputs[0].level != info.level {
1280+
continue
13021281
}
1303-
if count > 0 {
1304-
fmt.Fprintf(&buf, "]")
1282+
count++
1283+
if count == 1 {
1284+
fmt.Fprintf(&buf, " [")
1285+
} else {
1286+
fmt.Fprintf(&buf, " ")
13051287
}
1306-
fmt.Fprintf(&buf, "\n")
1288+
fmt.Fprintf(&buf, "L%d->L%d", c.inputs[0].level, c.outputLevel)
13071289
}
1308-
p.opts.Logger.Infof("pickAuto: L%d->L%d\n%s",
1309-
pc.startLevel.level, pc.outputLevel.level, buf.String())
1290+
if count > 0 {
1291+
fmt.Fprintf(&buf, "]")
1292+
}
1293+
fmt.Fprintf(&buf, "\n")
13101294
}
1295+
p.opts.Logger.Infof("pickAuto: L%d->L%d\n%s",
1296+
pc.startLevel.level, pc.outputLevel.level, buf.String())
1297+
}
1298+
1299+
// pickAutoScore picks the best score-based compaction, if any.
1300+
//
1301+
// On each call, pickAutoScore computes per-level size adjustments based on
1302+
// in-progress compactions, and computes a per-level score. The levels are
1303+
// iterated over in decreasing score order trying to find a valid compaction
1304+
// anchored at that level.
1305+
//
1306+
// If a score-based compaction cannot be found, pickAuto falls back to looking
1307+
// for an elision-only compaction to remove obsolete keys.
1308+
func (p *compactionPickerByScore) pickAutoScore(env compactionEnv) (pc *pickedCompaction) {
1309+
scores := p.calculateLevelScores(env.inProgressCompactions)
13111310

13121311
// Check for a score-based compaction. candidateLevelInfos are first sorted
13131312
// by whether they should be compacted, so if we find a level which shouldn't
@@ -1328,9 +1327,8 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
13281327
if pc != nil && !inputRangeAlreadyCompacting(env, pc) {
13291328
p.addScoresToPickedCompactionMetrics(pc, scores)
13301329
pc.score = info.compensatedScoreRatio
1331-
// TODO(bananabrick): Create an EventListener for logCompaction.
13321330
if false {
1333-
logCompaction(pc)
1331+
p.logCompactionForTesting(env, scores, pc)
13341332
}
13351333
return pc
13361334
}
@@ -1349,14 +1347,17 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
13491347
if pc != nil && !inputRangeAlreadyCompacting(env, pc) {
13501348
p.addScoresToPickedCompactionMetrics(pc, scores)
13511349
pc.score = info.compensatedScoreRatio
1352-
// TODO(bananabrick): Create an EventListener for logCompaction.
13531350
if false {
1354-
logCompaction(pc)
1351+
p.logCompactionForTesting(env, scores, pc)
13551352
}
13561353
return pc
13571354
}
13581355
}
1356+
return nil
1357+
}
13591358

1359+
// pickAutoNonScore picks the best non-score-based compaction, if any.
1360+
func (p *compactionPickerByScore) pickAutoNonScore(env compactionEnv) (pc *pickedCompaction) {
13601361
// Check for files which contain excessive point tombstones that could slow
13611362
// down reads. Unlike elision-only compactions, these compactions may select
13621363
// a file at any level rather than only the lowest level.

compaction_picker_test.go

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func TestCompactionPickerTargetLevel(t *testing.T) {
198198
if inProgressCompactions >= allowedCompactions {
199199
return nil
200200
}
201-
return pickerByScore.pickAuto(env)
201+
return pickerByScore.pickAutoScore(env)
202202
}
203203

204204
datadriven.RunTest(t, "testdata/compaction_picker_target_level",
@@ -572,11 +572,20 @@ func TestCompactionPickerL0(t *testing.T) {
572572
td.MaybeScanArgs(t, "l0_compaction_threshold", &opts.L0CompactionThreshold)
573573
td.MaybeScanArgs(t, "l0_compaction_file_threshold", &opts.L0CompactionFileThreshold)
574574

575-
pc = picker.pickAuto(compactionEnv{
575+
score := true
576+
if td.HasArg("non-score") {
577+
score = false
578+
}
579+
env := compactionEnv{
576580
diskAvailBytes: math.MaxUint64,
577581
earliestUnflushedSeqNum: math.MaxUint64,
578582
inProgressCompactions: inProgressCompactions,
579-
})
583+
}
584+
if score {
585+
pc = picker.pickAutoScore(env)
586+
} else {
587+
pc = picker.pickAutoNonScore(env)
588+
}
580589
var result strings.Builder
581590
if pc != nil {
582591
checkClone(t, pc)
@@ -804,7 +813,7 @@ func TestCompactionPickerConcurrency(t *testing.T) {
804813
allowedCompactions := picker.getCompactionConcurrency()
805814
var pc *pickedCompaction
806815
if inProgressCount < allowedCompactions {
807-
pc = picker.pickAuto(env)
816+
pc = picker.pickAutoScore(env)
808817
}
809818
var result strings.Builder
810819
fmt.Fprintf(&result, "picker.getCompactionConcurrency: %d\n", allowedCompactions)
@@ -946,14 +955,20 @@ func TestCompactionPickerPickReadTriggered(t *testing.T) {
946955
return sb.String()
947956

948957
case "pick-auto":
949-
pc := picker.pickAuto(compactionEnv{
958+
var result strings.Builder
959+
var pc *pickedCompaction
960+
env := compactionEnv{
950961
earliestUnflushedSeqNum: math.MaxUint64,
951962
readCompactionEnv: readCompactionEnv{
952963
readCompactions: &rcList,
953964
flushing: false,
954965
},
955-
})
956-
var result strings.Builder
966+
}
967+
if pc = picker.pickAutoScore(env); pc != nil {
968+
fmt.Fprintf(&result, "picked score-based compaction:\n")
969+
} else if pc = picker.pickAutoNonScore(env); pc != nil {
970+
fmt.Fprintf(&result, "picked non-score-based compaction:\n")
971+
}
957972
if pc != nil {
958973
fmt.Fprintf(&result, "L%d -> L%d\n", pc.startLevel.level, pc.outputLevel.level)
959974
fmt.Fprintf(&result, "L%d: %s\n", pc.startLevel.level, fileNums(pc.startLevel.files))
@@ -1334,7 +1349,7 @@ func TestCompactionOutputFileSize(t *testing.T) {
13341349
return buf.String()
13351350

13361351
case "pick-auto":
1337-
pc := picker.pickAuto(compactionEnv{
1352+
pc := picker.pickAutoNonScore(compactionEnv{
13381353
earliestUnflushedSeqNum: math.MaxUint64,
13391354
earliestSnapshotSeqNum: math.MaxUint64,
13401355
})

compaction_scheduler.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,20 @@ var scheduledCompactionMap map[compactionKind]compactionOptionalAndPriority
125125
var manualCompactionPriority int
126126

127127
func init() {
128-
// Manual compactions have the highest priority since DB.pickAnyCompaction
129-
// first picks a manual compaction, before calling
130-
// compactionPickerByScore.pickAuto.
131-
manualCompactionPriority = 100
128+
// Manual compactions have priority just below the score-rebased
129+
// compactions, since DB.pickAnyCompaction first picks score-based
130+
// compactions, and then manual compactions.
131+
manualCompactionPriority = 70
132132
scheduledCompactionMap = map[compactionKind]compactionOptionalAndPriority{}
133+
// Score-based-compactions have priorities {100, 90, 80}.
134+
//
133135
// We don't actually know if it is a compactionKindMove or
134136
// compactionKindCopy until a compactionKindDefault is turned from a
135137
// pickedCompaction into a compaction struct. So we will never see those
136138
// values here, but for completeness we include them.
137-
scheduledCompactionMap[compactionKindMove] = compactionOptionalAndPriority{priority: 90}
138-
scheduledCompactionMap[compactionKindCopy] = compactionOptionalAndPriority{priority: 80}
139-
scheduledCompactionMap[compactionKindDefault] = compactionOptionalAndPriority{priority: 70}
139+
scheduledCompactionMap[compactionKindMove] = compactionOptionalAndPriority{priority: 100}
140+
scheduledCompactionMap[compactionKindCopy] = compactionOptionalAndPriority{priority: 90}
141+
scheduledCompactionMap[compactionKindDefault] = compactionOptionalAndPriority{priority: 80}
140142
scheduledCompactionMap[compactionKindTombstoneDensity] =
141143
compactionOptionalAndPriority{optional: true, priority: 60}
142144
scheduledCompactionMap[compactionKindElisionOnly] =

compaction_test.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (p *compactionPickerForTesting) estimatedCompactionDebt(l0ExtraSize uint64)
8686

8787
func (p *compactionPickerForTesting) forceBaseLevel1() {}
8888

89-
func (p *compactionPickerForTesting) pickAuto(env compactionEnv) (pc *pickedCompaction) {
89+
func (p *compactionPickerForTesting) pickAutoScore(env compactionEnv) (pc *pickedCompaction) {
9090
if p.score < 1 {
9191
return nil
9292
}
@@ -107,21 +107,7 @@ func (p *compactionPickerForTesting) pickAuto(env compactionEnv) (pc *pickedComp
107107
return pickAutoLPositive(env, p.opts, p.vers, p.l0Organizer, cInfo, p.baseLevel)
108108
}
109109

110-
func (p *compactionPickerForTesting) pickElisionOnlyCompaction(
111-
env compactionEnv,
112-
) (pc *pickedCompaction) {
113-
return nil
114-
}
115-
116-
func (p *compactionPickerForTesting) pickRewriteCompaction(
117-
env compactionEnv,
118-
) (pc *pickedCompaction) {
119-
return nil
120-
}
121-
122-
func (p *compactionPickerForTesting) pickReadTriggeredCompaction(
123-
env compactionEnv,
124-
) (pc *pickedCompaction) {
110+
func (p *compactionPickerForTesting) pickAutoNonScore(env compactionEnv) (pc *pickedCompaction) {
125111
return nil
126112
}
127113

@@ -528,7 +514,7 @@ func TestPickCompaction(t *testing.T) {
528514
vs.versions.Init(nil)
529515
vs.append(tc.picker.vers)
530516
vs.picker = &tc.picker
531-
pc, got := vs.picker.pickAuto(compactionEnv{diskAvailBytes: math.MaxUint64}), ""
517+
pc, got := vs.picker.pickAutoScore(compactionEnv{diskAvailBytes: math.MaxUint64}), ""
532518
if pc != nil {
533519
c := newCompaction(pc, opts, time.Now(), nil /* provider */, noopGrantHandle{}, neverSeparateValues)
534520

@@ -1075,6 +1061,14 @@ func TestCompaction(t *testing.T) {
10751061
d.mu.Unlock()
10761062
return describeLSM(d, verbose)
10771063

1064+
case "set-disable-auto-compact":
1065+
var v bool
1066+
td.ScanArgs(t, "v", &v)
1067+
d.mu.Lock()
1068+
d.opts.DisableAutomaticCompactions = v
1069+
d.mu.Unlock()
1070+
return ""
1071+
10781072
case "async-compact":
10791073
var s string
10801074
ch := make(chan error, 1)
@@ -1236,6 +1230,12 @@ func TestCompaction(t *testing.T) {
12361230
maxVersion: FormatExperimentalValueSeparation,
12371231
verbose: true,
12381232
},
1233+
"score_compaction_picked_before_manual": {
1234+
// Run at a specific version, so that a single sstable format is used,
1235+
// since the test prints the compaction log which includes file sizes.
1236+
minVersion: FormatExperimentalValueSeparation,
1237+
maxVersion: FormatExperimentalValueSeparation,
1238+
},
12391239
}
12401240
datadriven.Walk(t, "testdata/compaction", func(t *testing.T, path string) {
12411241
filename := filepath.Base(path)

0 commit comments

Comments
 (0)