Skip to content

Commit

Permalink
options: add EnableDeleteOnlyCompactionExcises Option
Browse files Browse the repository at this point in the history
This change adds a dynamic knob in Options to be able to toggle
the functionality added in #3926. If the method is undefined
or returns false, delete-only compactions only do full-file deletes
and not excises.
  • Loading branch information
itsbilal committed Oct 17, 2024
1 parent c6b097e commit 1d2e9e8
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 10 deletions.
33 changes: 23 additions & 10 deletions compaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ type compaction struct {
// virtual sstables.
deletionHints []deleteCompactionHint

// exciseEnabled is set to true if this is a compactionKindDeleteOnly and
// this compaction is allowed to excise files.
exciseEnabled bool

metrics map[int]*LevelMetrics

pickerMetrics compactionPickerMetrics
Expand Down Expand Up @@ -401,6 +405,7 @@ func newDeleteOnlyCompaction(
inputs []compactionLevel,
beganAt time.Time,
hints []deleteCompactionHint,
exciseEnabled bool,
) *compaction {
c := &compaction{
kind: compactionKindDeleteOnly,
Expand All @@ -413,6 +418,7 @@ func newDeleteOnlyCompaction(
beganAt: beganAt,
inputs: inputs,
deletionHints: hints,
exciseEnabled: exciseEnabled,
}

// Set c.smallest, c.largest.
Expand Down Expand Up @@ -1773,11 +1779,15 @@ func (d *DB) maybeScheduleCompactionPicker(
func (d *DB) tryScheduleDeleteOnlyCompaction() {
v := d.mu.versions.currentVersion()
snapshots := d.mu.snapshots.toSlice()
inputs, resolvedHints, unresolvedHints := checkDeleteCompactionHints(d.cmp, v, d.mu.compact.deletionHints, snapshots, d.FormatMajorVersion())
// We need to save the value of exciseEnabled in the compaction itself, as
// it can change dynamically between now and when the compaction runs.
exciseEnabled := d.FormatMajorVersion() >= FormatVirtualSSTables &&
d.opts.Experimental.EnableDeleteOnlyCompactionExcises != nil && d.opts.Experimental.EnableDeleteOnlyCompactionExcises()
inputs, resolvedHints, unresolvedHints := checkDeleteCompactionHints(d.cmp, v, d.mu.compact.deletionHints, snapshots, exciseEnabled)
d.mu.compact.deletionHints = unresolvedHints

if len(inputs) > 0 {
c := newDeleteOnlyCompaction(d.opts, v, inputs, d.timeNow(), resolvedHints)
c := newDeleteOnlyCompaction(d.opts, v, inputs, d.timeNow(), resolvedHints, exciseEnabled)
d.mu.compact.compactingCount++
d.addInProgressCompaction(c)
go d.compact(c, nil)
Expand Down Expand Up @@ -1941,7 +1951,7 @@ func (h deleteCompactionHint) String() string {
}

func (h *deleteCompactionHint) canDeleteOrExcise(
cmp Compare, m *fileMetadata, snapshots compact.Snapshots, fmv FormatMajorVersion,
cmp Compare, m *fileMetadata, snapshots compact.Snapshots, exciseEnabled bool,
) deletionHintOverlap {
// The file can only be deleted if all of its keys are older than the
// earliest tombstone aggregated into the hint. Note that we use
Expand Down Expand Up @@ -1990,7 +2000,7 @@ func (h *deleteCompactionHint) canDeleteOrExcise(
base.UserKeyExclusive(h.end).CompareUpperBounds(cmp, m.UserKeyBounds().End) >= 0 {
return hintDeletesFile
}
if fmv < FormatVirtualSSTables {
if !exciseEnabled {
// The file's keys must be completely contained within the hint range; excises
// aren't allowed.
return hintDoesNotApply
Expand All @@ -2015,15 +2025,16 @@ func checkDeleteCompactionHints(
v *version,
hints []deleteCompactionHint,
snapshots compact.Snapshots,
fmv FormatMajorVersion,
exciseEnabled bool,
) (levels []compactionLevel, resolved, unresolved []deleteCompactionHint) {
var files map[*fileMetadata]bool
var byLevel [numLevels][]*fileMetadata

// Delete-only compactions can be quadratic (O(mn)) in terms of runtime
// where m = number of files in the delete-only compaction and n = number
// of resolved hints. To prevent these from growing unbounded, we cap
// the number of hints we resolve for one delete-only compaction.
// the number of hints we resolve for one delete-only compaction. This
// cap only applies if exciseEnabled == true.
const maxHintsPerDeleteOnlyCompaction = 10

unresolvedHints := hints[:0]
Expand Down Expand Up @@ -2071,7 +2082,7 @@ func checkDeleteCompactionHints(
// a b c d e f g h i j k l m n o p q r s t u v w x y z

if snapshots.Index(h.tombstoneLargestSeqNum) != snapshots.Index(h.fileSmallestSeqNum) ||
len(resolvedHints) >= maxHintsPerDeleteOnlyCompaction {
(len(resolvedHints) >= maxHintsPerDeleteOnlyCompaction && exciseEnabled) {
// Cannot resolve yet.
unresolvedHints = append(unresolvedHints, h)
continue
Expand All @@ -2086,8 +2097,9 @@ func checkDeleteCompactionHints(
for l := h.tombstoneLevel + 1; l < numLevels; l++ {
overlaps := v.Overlaps(l, base.UserKeyBoundsEndExclusive(h.start, h.end))
iter := overlaps.Iter()

for m := iter.First(); m != nil; m = iter.Next() {
doesHintApply := h.canDeleteOrExcise(cmp, m, snapshots, fmv)
doesHintApply := h.canDeleteOrExcise(cmp, m, snapshots, exciseEnabled)
if m.IsCompacting() || doesHintApply == hintDoesNotApply || files[m] {
continue
}
Expand Down Expand Up @@ -2546,6 +2558,7 @@ func (d *DB) runDeleteOnlyCompactionForLevel(
ve *versionEdit,
snapshots compact.Snapshots,
fragments []deleteCompactionHintFragment,
exciseEnabled bool,
) error {
curFragment := 0
iter := cl.files.Iter()
Expand Down Expand Up @@ -2582,7 +2595,7 @@ func (d *DB) runDeleteOnlyCompactionForLevel(
// above it.
continue
}
hintOverlap := h.canDeleteOrExcise(d.cmp, curFile, snapshots, d.FormatMajorVersion())
hintOverlap := h.canDeleteOrExcise(d.cmp, curFile, snapshots, exciseEnabled)
if hintOverlap == hintDoesNotApply {
continue
}
Expand Down Expand Up @@ -2669,7 +2682,7 @@ func (d *DB) runDeleteOnlyCompaction(
}
for _, cl := range c.inputs {
levelMetrics := &LevelMetrics{}
if err := d.runDeleteOnlyCompactionForLevel(cl, levelMetrics, ve, snapshots, fragments); err != nil {
if err := d.runDeleteOnlyCompactionForLevel(cl, levelMetrics, ve, snapshots, fragments, c.exciseEnabled); err != nil {
return nil, stats, err
}
c.metrics[cl.level] = levelMetrics
Expand Down
2 changes: 2 additions & 0 deletions compaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1357,6 +1357,7 @@ func TestCompactionDeleteOnlyHints(t *testing.T) {
},
FormatMajorVersion: internalFormatNewest,
}).WithFSDefaults()
opts.Experimental.EnableDeleteOnlyCompactionExcises = func() bool { return true }
opts.Experimental.EnableColumnarBlocks = func() bool { return true }
return opts, nil
}
Expand Down Expand Up @@ -1617,6 +1618,7 @@ func TestCompactionTombstones(t *testing.T) {
},
FormatMajorVersion: internalFormatNewest,
}).WithFSDefaults()
opts.Experimental.EnableDeleteOnlyCompactionExcises = func() bool { return true }
opts.Experimental.EnableColumnarBlocks = func() bool { return true }
var err error
d, err = runDBDefineCmd(td, opts)
Expand Down
16 changes: 16 additions & 0 deletions metamorphic/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ func parseOptions(
case "TestOptions.use_excise":
opts.useExcise = true
return true
case "TestOptions.use_delete_only_compaction_excises":
opts.useDeleteOnlyCompactionExcises = true
opts.Opts.Experimental.EnableDeleteOnlyCompactionExcises = func() bool {
return opts.useDeleteOnlyCompactionExcises
}
return true
case "TestOptions.use_jemalloc_size_classes":
opts.Opts.AllocatorSizeClasses = pebble.JemallocSizeClasses
return true
Expand Down Expand Up @@ -259,6 +265,9 @@ func optionsToString(opts *TestOptions) string {
if opts.useExcise {
fmt.Fprintf(&buf, " use_excise=%v\n", opts.useExcise)
}
if opts.useDeleteOnlyCompactionExcises {
fmt.Fprintf(&buf, " use_delete_only_compaction_excises=%v\n", opts.useDeleteOnlyCompactionExcises)
}
if opts.Opts.AllocatorSizeClasses != nil {
if fmt.Sprint(opts.Opts.AllocatorSizeClasses) != fmt.Sprint(pebble.JemallocSizeClasses) {
panic(fmt.Sprintf("unexpected AllocatorSizeClasses %v", opts.Opts.AllocatorSizeClasses))
Expand Down Expand Up @@ -401,6 +410,9 @@ type TestOptions struct {
// excises. However !useExcise && !useSharedReplicate can be used to guarantee
// lack of excises.
useExcise bool
// useDeleteOnlyCompactionExcises turns on the ability for delete-only compactions
// to do excises. Note that this can be true even when useExcise is false.
useDeleteOnlyCompactionExcises bool
}

// InitRemoteStorageFactory initializes Opts.Experimental.RemoteStorage.
Expand Down Expand Up @@ -837,6 +849,10 @@ func RandomOptions(
testOpts.Opts.FormatMajorVersion = pebble.FormatVirtualSSTables
}
}
testOpts.useDeleteOnlyCompactionExcises = rng.IntN(2) == 0
opts.Experimental.EnableDeleteOnlyCompactionExcises = func() bool {
return testOpts.useDeleteOnlyCompactionExcises
}
testOpts.InitRemoteStorageFactory()
testOpts.Opts.EnsureDefaults()
return testOpts
Expand Down
1 change: 1 addition & 0 deletions metamorphic/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func TestOptionsRoundtrip(t *testing.T) {
"Experimental.IngestSplit:",
"Experimental.RemoteStorage:",
"Experimental.SingleDeleteInvariantViolationCallback:",
"Experimental.EnableDeleteOnlyCompactionExcises:",
"Levels[0].Compression:",
"Levels[1].Compression:",
"Levels[2].Compression:",
Expand Down
6 changes: 6 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,12 @@ type Options struct {
// which will later be consumed by SingleDelete#3. The violation will
// not be detected and the DB will be correct.
SingleDeleteInvariantViolationCallback func(userKey []byte)

// EnableDeleteOnlyCompactionExcises enables delete-only compactions to also
// apply delete-only compaction hints on sstables that partially overlap
// with it. This application happens through an excise, similar to
// the excise phase of IngestAndExcise.
EnableDeleteOnlyCompactionExcises func() bool
}

// Filters is a map from filter policy name to filter policy. It is used for
Expand Down

0 comments on commit 1d2e9e8

Please sign in to comment.