diff --git a/metamorphic/options.go b/metamorphic/options.go index 88a1c0c437..94f34d8e2c 100644 --- a/metamorphic/options.go +++ b/metamorphic/options.go @@ -163,6 +163,9 @@ func parseOptions( case "TestOptions.use_excise": opts.useExcise = true return true + case "TestOptions.use_jemalloc_size_classes": + opts.Opts.AllocatorSizeClasses = pebble.JemallocSizeClasses + return true default: if customOptionParsers == nil { return false @@ -256,6 +259,12 @@ func optionsToString(opts *TestOptions) string { if opts.useExcise { fmt.Fprintf(&buf, " use_excise=%v\n", opts.useExcise) } + if opts.Opts.AllocatorSizeClasses != nil { + if fmt.Sprint(opts.Opts.AllocatorSizeClasses) != fmt.Sprint(pebble.JemallocSizeClasses) { + panic(fmt.Sprintf("unexpected AllocatorSizeClasses %v", opts.Opts.AllocatorSizeClasses)) + } + fmt.Fprint(&buf, " use_jemalloc_size_classes=true\n") + } for _, customOpt := range opts.CustomOpts { fmt.Fprintf(&buf, " %s=%s\n", customOpt.Name(), customOpt.Value()) } @@ -445,6 +454,8 @@ func standardOptions() []*TestOptions { 2: ` [Options] disable_wal=true +[TestOptions] + use_jemalloc_size_classes=true `, 3: ` [Options] @@ -524,7 +535,8 @@ func standardOptions() []*TestOptions { `, 21: ` [TestOptions] - use_disk=true + use_disk=true + use_jemalloc_size_classes=true `, 22: ` [Options] @@ -542,6 +554,7 @@ func standardOptions() []*TestOptions { 25: ` [TestOptions] enable_value_blocks=true + use_jemalloc_size_classes=true `, 26: fmt.Sprintf(` [Options] @@ -705,6 +718,9 @@ func RandomOptions( AllowL0: rng.IntN(4) == 1, // 25% of the time } } + if rng.IntN(2) == 0 { + opts.AllocatorSizeClasses = pebble.JemallocSizeClasses + } var lopts pebble.LevelOptions lopts.BlockRestartInterval = 1 + rng.IntN(64) // 1 - 64 diff --git a/options.go b/options.go index 7b2ff26291..4c8dee2bf4 100644 --- a/options.go +++ b/options.go @@ -1188,6 +1188,9 @@ type WALFailoverOptions struct { // ReadaheadConfig controls the use of read-ahead. type ReadaheadConfig = objstorageprovider.ReadaheadConfig +// JemallocSizeClasses exports sstable.JemallocSizeClasses. +var JemallocSizeClasses = sstable.JemallocSizeClasses + // DebugCheckLevels calls CheckLevels on the provided database. // It may be set in the DebugCheck field of Options to check // level invariants whenever a new version is installed. diff --git a/sstable/block/flush_governor_test.go b/sstable/block/flush_governor_test.go index e8a2aabf16..3ef5a20581 100644 --- a/sstable/block/flush_governor_test.go +++ b/sstable/block/flush_governor_test.go @@ -2,16 +2,18 @@ // of this source code is governed by a BSD-style license that can be found in // the LICENSE file. -package block +package block_test import ( "testing" "github.com/cockroachdb/datadriven" + "github.com/cockroachdb/pebble/sstable" + "github.com/cockroachdb/pebble/sstable/block" ) func TestFlushGovernor(t *testing.T) { - var fg FlushGovernor + var fg block.FlushGovernor datadriven.RunTest(t, "testdata/flush_governor", func(t *testing.T, td *datadriven.TestData) string { switch td.Cmd { case "init": @@ -23,7 +25,10 @@ func TestFlushGovernor(t *testing.T) { td.MaybeScanArgs(t, "threshold", &blockSizeThreshold) td.MaybeScanArgs(t, "size-class-aware-threshold", &sizeClassAwareThreshold) td.MaybeScanArgs(t, "size-classes", &classes) - fg = MakeFlushGovernor(targetBlockSize, blockSizeThreshold, sizeClassAwareThreshold, classes) + if td.HasArg("jemalloc-size-classes") { + classes = sstable.JemallocSizeClasses + } + fg = block.MakeFlushGovernor(targetBlockSize, blockSizeThreshold, sizeClassAwareThreshold, classes) return fg.String() case "should-flush": diff --git a/sstable/block/testdata/flush_governor b/sstable/block/testdata/flush_governor index 7baab8ea27..42cb2f0f6f 100644 --- a/sstable/block/testdata/flush_governor +++ b/sstable/block/testdata/flush_governor @@ -84,3 +84,26 @@ init target-block-size=1000 size-class-aware-threshold=60 size-classes=(500, 600 low watermark: 600 high watermark: 1018 boundaries: [818 868 918 968] + +# Test with jemalloc boundaries. +init target-block-size=32768 jemalloc-size-classes +---- +low watermark: 19661 +high watermark: 40928 +boundaries: [20448 24544 28640 32736] + +# We should flush to avoid exceeding the boundary. +should-flush size-before=32000 size-after=32766 +---- +should flush + +# We should not flush since we waste less space against the 40KiB boundary. +should-flush size-before=30000 size-after=39000 +---- +should not flush + +# We should flush even if we would waste less space against the 48KiB boundary +# (it's not the first boundary after the target size). +should-flush size-before=39000 size-after=48500 +---- +should flush diff --git a/sstable/options.go b/sstable/options.go index ce9b284d0c..e885f8a1a4 100644 --- a/sstable/options.go +++ b/sstable/options.go @@ -302,6 +302,23 @@ type WriterOptions struct { disableObsoleteCollector bool } +// JemallocSizeClasses are a subset of available size classes in jemalloc[1], +// suitable for the AllocatorSizeClasses option. +// +// The size classes are used when writing sstables for determining target block +// sizes for flushes, with the goal of reducing internal memory fragmentation +// when the blocks are later loaded into the block cache. We only use the size +// classes between 16KiB - 256KiB as block limits fall in that range. +// +// [1] https://jemalloc.net/jemalloc.3.html#size_classes +var JemallocSizeClasses = []int{ + 16 * 1024, + 20 * 1024, 24 * 1024, 28 * 1024, 32 * 1024, // 4KiB spacing + 40 * 1024, 48 * 1024, 56 * 1024, 64 * 1024, // 8KiB spacing + 80 * 1024, 96 * 1024, 112 * 1024, 128 * 1024, // 16KiB spacing. + 160 * 1024, 192 * 1024, 224 * 1024, 256 * 1024, // 32KiB spacing. +} + // SetInternal sets the internal writer options. Note that even though this // method is public, a caller outside the pebble package can't construct a value // to pass to it.