Skip to content

Commit 612609e

Browse files
committed
options: make TargetByteDeletionRate dynamically configurable
Change this setting to a function. This change will be backported to older releases, as it provides an important "escape hatch" if delete pacing goes wrong. Informs #5424
1 parent 2c2ad4d commit 612609e

File tree

8 files changed

+60
-15
lines changed

8 files changed

+60
-15
lines changed

compaction_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2700,7 +2700,7 @@ func TestSharedObjectDeletePacing(t *testing.T) {
27002700
"": remote.NewInMem(),
27012701
})
27022702
opts.Experimental.CreateOnShared = remote.CreateOnSharedAll
2703-
opts.TargetByteDeletionRate = 1
2703+
opts.TargetByteDeletionRate = func() int { return 1 }
27042704
opts.Logger = testLogger{t}
27052705

27062706
d, err := Open("", &opts)

metamorphic/options.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,8 @@ func RandomOptions(
663663
opts.FormatMajorVersion += pebble.FormatMajorVersion(rng.IntN(n + 1))
664664
opts.Experimental.L0CompactionConcurrency = 1 + rng.IntN(4) // 1-4
665665
opts.Experimental.LevelMultiplier = 5 << rng.IntN(7) // 5 - 320
666-
opts.TargetByteDeletionRate = 1 << uint(20+rng.IntN(10)) // 1MB - 1GB
666+
targetByteDeletionRate := 1 << uint(20+rng.IntN(10)) // 1MB - 1GB
667+
opts.TargetByteDeletionRate = func() int { return targetByteDeletionRate }
667668
opts.Experimental.ValidateOnIngest = rng.IntN(2) != 0
668669
opts.L0CompactionThreshold = 1 + rng.IntN(100) // 1 - 100
669670
opts.L0CompactionFileThreshold = 1 << rng.IntN(11) // 1 - 1024

metamorphic/options_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func TestOptionsRoundtrip(t *testing.T) {
7373
"EventListener:",
7474
"CompactionConcurrencyRange:",
7575
"MaxConcurrentDownloads:",
76+
"TargetByteDeletionRate:",
7677
"Experimental.DisableIngestAsFlushable:",
7778
"Experimental.EnableColumnarBlocks:",
7879
"Experimental.EnableValueBlocks:",
@@ -117,6 +118,7 @@ func TestOptionsRoundtrip(t *testing.T) {
117118
if o.Opts.Experimental.IngestSplit != nil && o.Opts.Experimental.IngestSplit() {
118119
require.Equal(t, o.Opts.Experimental.IngestSplit(), parsed.Opts.Experimental.IngestSplit())
119120
}
121+
require.Equal(t, o.Opts.TargetByteDeletionRate(), parsed.Opts.TargetByteDeletionRate())
120122

121123
expBaseline, expUpper := o.Opts.CompactionConcurrencyRange()
122124
parsedBaseline, parsedUpper := parsed.Opts.CompactionConcurrencyRange()

obsolete_files.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func openCleanupManager(
9191
deletePacer: newDeletionPacer(
9292
crtime.NowMono(),
9393
opts.FreeSpaceThresholdBytes,
94-
int64(opts.TargetByteDeletionRate),
94+
opts.TargetByteDeletionRate,
9595
opts.FreeSpaceTimeframe,
9696
opts.ObsoleteBytesMaxRatio,
9797
opts.ObsoleteBytesTimeframe,

options.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,8 +1056,8 @@ type Options struct {
10561056
// This value is only a best-effort target; the effective rate can be
10571057
// higher if deletions are falling behind or disk space is running low.
10581058
//
1059-
// Setting this to 0 disables deletion pacing, which is also the default.
1060-
TargetByteDeletionRate int
1059+
// A returned value of 0 disables deletion pacing (this is also the default).
1060+
TargetByteDeletionRate func() int
10611061

10621062
// FreeSpaceThresholdBytes specifies the minimum amount of free disk space that Pebble
10631063
// attempts to maintain. If free disk space drops below this threshold, deletions
@@ -1166,6 +1166,10 @@ func (o *Options) EnsureDefaults() {
11661166
o.Cleaner = DeleteCleaner{}
11671167
}
11681168

1169+
if o.TargetByteDeletionRate == nil {
1170+
o.TargetByteDeletionRate = func() int { return 0 }
1171+
}
1172+
11691173
if o.FreeSpaceThresholdBytes == 0 {
11701174
o.FreeSpaceThresholdBytes = 16 << 30 // 16 GB
11711175
}
@@ -1444,7 +1448,7 @@ func (o *Options) String() string {
14441448
fmt.Fprintf(&buf, " max_open_files=%d\n", o.MaxOpenFiles)
14451449
fmt.Fprintf(&buf, " mem_table_size=%d\n", o.MemTableSize)
14461450
fmt.Fprintf(&buf, " mem_table_stop_writes_threshold=%d\n", o.MemTableStopWritesThreshold)
1447-
fmt.Fprintf(&buf, " min_deletion_rate=%d\n", o.TargetByteDeletionRate)
1451+
fmt.Fprintf(&buf, " min_deletion_rate=%d\n", o.TargetByteDeletionRate())
14481452
fmt.Fprintf(&buf, " free_space_threshold_bytes=%d\n", o.FreeSpaceThresholdBytes)
14491453
fmt.Fprintf(&buf, " free_space_timeframe=%s\n", o.FreeSpaceTimeframe.String())
14501454
fmt.Fprintf(&buf, " obsolete_bytes_max_ratio=%f\n", o.ObsoleteBytesMaxRatio)
@@ -1789,7 +1793,11 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
17891793
// Do nothing; option existed in older versions of pebble, and
17901794
// may be meaningful again eventually.
17911795
case "min_deletion_rate":
1792-
o.TargetByteDeletionRate, err = strconv.Atoi(value)
1796+
var rate int
1797+
rate, err = strconv.Atoi(value)
1798+
if err == nil {
1799+
o.TargetByteDeletionRate = func() int { return rate }
1800+
}
17931801
case "free_space_threshold_bytes":
17941802
o.FreeSpaceThresholdBytes, err = strconv.ParseUint(value, 10, 64)
17951803
case "free_space_timeframe":

options_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ func TestOptionsParse(t *testing.T) {
322322
opts.FlushDelayDeleteRange = 10 * time.Second
323323
opts.FlushDelayRangeKey = 11 * time.Second
324324
opts.Experimental.LevelMultiplier = 5
325-
opts.TargetByteDeletionRate = 200
325+
opts.TargetByteDeletionRate = func() int { return 200 }
326326
opts.WALFailover = &WALFailoverOptions{
327327
Secondary: wal.Dir{Dirname: "wal_secondary", FS: vfs.Default},
328328
}

pacer.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ type deletionPacer struct {
4646
history history
4747
}
4848

49-
targetByteDeletionRate int64
49+
targetByteDeletionRate func() int
5050

5151
getInfo func() deletionPacerInfo
5252
}
@@ -62,7 +62,7 @@ const deletePacerHistory = 5 * time.Minute
6262
func newDeletionPacer(
6363
now crtime.Mono,
6464
freeSpaceThreshold uint64,
65-
targetByteDeletionRate int64,
65+
targetByteDeletionRate func() int,
6666
freeSpaceTimeframe time.Duration,
6767
obsoleteBytesMaxRatio float64,
6868
obsoleteBytesTimeframe time.Duration,
@@ -99,12 +99,13 @@ func (p *deletionPacer) ReportDeletion(now crtime.Mono, bytesToDelete uint64) {
9999
//
100100
// PacingDelay is thread-safe.
101101
func (p *deletionPacer) PacingDelay(now crtime.Mono, bytesToDelete uint64) (waitSeconds float64) {
102-
if p.targetByteDeletionRate == 0 {
102+
targetByteDeletionRate := p.targetByteDeletionRate()
103+
if targetByteDeletionRate == 0 {
103104
// Pacing disabled.
104105
return 0.0
105106
}
106107

107-
baseRate := float64(p.targetByteDeletionRate)
108+
baseRate := float64(targetByteDeletionRate)
108109
// If recent deletion rate is more than our target, use that so that we don't
109110
// fall behind.
110111
historicRate := func() float64 {

pacer_test.go

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ import (
1010
"math"
1111
"math/rand/v2"
1212
"slices"
13+
"sync/atomic"
1314
"testing"
1415
"time"
1516

1617
"github.com/cockroachdb/crlib/crtime"
1718
"github.com/stretchr/testify/require"
1819
)
1920

21+
const MB = 1 << 20
22+
const GB = 1 << 30
23+
2024
func TestDeletionPacer(t *testing.T) {
21-
const MB = 1 << 20
22-
const GB = 1 << 30
2325
testCases := []struct {
2426
freeBytes uint64
2527
obsoleteBytes uint64
@@ -126,7 +128,7 @@ func TestDeletionPacer(t *testing.T) {
126128
pacer := newDeletionPacer(
127129
start,
128130
opts.FreeSpaceThresholdBytes,
129-
100*MB,
131+
func() int { return 100 * MB },
130132
opts.FreeSpaceTimeframe,
131133
opts.ObsoleteBytesMaxRatio,
132134
opts.ObsoleteBytesTimeframe,
@@ -142,6 +144,37 @@ func TestDeletionPacer(t *testing.T) {
142144
}
143145
}
144146

147+
func TestDeletionPacerCfgChange(t *testing.T) {
148+
getInfo := func() deletionPacerInfo {
149+
return deletionPacerInfo{
150+
freeBytes: 100 * GB,
151+
liveBytes: 10 * GB,
152+
obsoleteBytes: 10 * MB,
153+
}
154+
}
155+
156+
var targetRate atomic.Int32
157+
targetRate.Store(100 * MB)
158+
159+
start := crtime.NowMono()
160+
var opts Options
161+
opts.EnsureDefaults()
162+
pacer := newDeletionPacer(
163+
start,
164+
opts.FreeSpaceThresholdBytes,
165+
func() int { return int(targetRate.Load()) },
166+
opts.FreeSpaceTimeframe,
167+
opts.ObsoleteBytesMaxRatio,
168+
opts.ObsoleteBytesTimeframe,
169+
getInfo,
170+
)
171+
require.InDelta(t, 1.0/100, pacer.PacingDelay(start, 1*MB), 1e-4)
172+
targetRate.Store(200 * MB)
173+
require.InDelta(t, 1.0/200, pacer.PacingDelay(start, 1*MB), 1e-4)
174+
targetRate.Store(0)
175+
require.Equal(t, 0.0, pacer.PacingDelay(start, 1*MB), 1e-4)
176+
}
177+
145178
// TestDeletionPacerHistory tests the history helper by crosschecking Sum()
146179
// against a naive implementation.
147180
func TestDeletionPacerHistory(t *testing.T) {

0 commit comments

Comments
 (0)