github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/options_test.go (about) 1 // Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 // of this source code is governed by a BSD-style license that can be found in 3 // the LICENSE file. 4 5 package pebble 6 7 import ( 8 "fmt" 9 "math/rand" 10 "runtime" 11 "testing" 12 "time" 13 14 "github.com/cockroachdb/errors" 15 "github.com/cockroachdb/pebble/internal/base" 16 "github.com/cockroachdb/pebble/vfs" 17 "github.com/stretchr/testify/require" 18 ) 19 20 // testingRandomized randomizes some default options. Currently, it's 21 // used for testing under a random format major version in some tests. 22 func (o *Options) testingRandomized(t testing.TB) *Options { 23 if o == nil { 24 o = &Options{} 25 } 26 if o.Logger == nil { 27 o.Logger = testLogger{t: t} 28 } 29 if o.FormatMajorVersion == FormatDefault { 30 // Pick a random format major version from the range 31 // [MostCompatible, FormatNewest]. 32 o.FormatMajorVersion = FormatMajorVersion(rand.Intn(int(internalFormatNewest)) + 1) 33 t.Logf("Running %s with format major version %s", t.Name(), o.FormatMajorVersion.String()) 34 } 35 return o 36 } 37 38 func testingRandomized(t testing.TB, o *Options) *Options { 39 o.testingRandomized(t) 40 return o 41 } 42 43 func TestLevelOptions(t *testing.T) { 44 var opts *Options 45 opts = opts.EnsureDefaults() 46 47 testCases := []struct { 48 level int 49 targetFileSize int64 50 }{ 51 {0, 2 << 20}, 52 {1, (2 * 2) << 20}, 53 {2, (4 * 2) << 20}, 54 {3, (8 * 2) << 20}, 55 {4, (16 * 2) << 20}, 56 {5, (32 * 2) << 20}, 57 {6, (64 * 2) << 20}, 58 } 59 for _, c := range testCases { 60 l := opts.Level(c.level) 61 if c.targetFileSize != l.TargetFileSize { 62 t.Fatalf("%d: expected target-file-size %d, but found %d", 63 c.level, c.targetFileSize, l.TargetFileSize) 64 } 65 } 66 } 67 68 func TestOptionsString(t *testing.T) { 69 n := runtime.GOMAXPROCS(8) 70 defer runtime.GOMAXPROCS(n) 71 72 const expected = `[Version] 73 pebble_version=0.1 74 75 [Options] 76 bytes_per_sync=524288 77 cache_size=8388608 78 cleaner=delete 79 compaction_debt_concurrency=1073741824 80 comparer=leveldb.BytewiseComparator 81 disable_wal=false 82 flush_delay_delete_range=0s 83 flush_delay_range_key=0s 84 flush_split_bytes=4194304 85 format_major_version=1 86 l0_compaction_concurrency=10 87 l0_compaction_file_threshold=500 88 l0_compaction_threshold=4 89 l0_stop_writes_threshold=12 90 lbase_max_bytes=67108864 91 max_concurrent_compactions=1 92 max_manifest_file_size=134217728 93 max_open_files=1000 94 mem_table_size=4194304 95 mem_table_stop_writes_threshold=2 96 min_deletion_rate=0 97 merger=pebble.concatenate 98 multilevel_compaction_heuristic=wamp(0.00, false) 99 read_compaction_rate=16000 100 read_sampling_multiplier=16 101 strict_wal_tail=true 102 table_cache_shards=8 103 table_property_collectors=[] 104 validate_on_ingest=false 105 wal_dir= 106 wal_bytes_per_sync=0 107 max_writer_concurrency=0 108 force_writer_parallelism=false 109 secondary_cache_size_bytes=0 110 create_on_shared=0 111 112 [Level "0"] 113 block_restart_interval=16 114 block_size=4096 115 block_size_threshold=90 116 compression=Snappy 117 filter_policy=none 118 filter_type=table 119 index_block_size=4096 120 target_file_size=2097152 121 ` 122 123 var opts *Options 124 opts = opts.EnsureDefaults() 125 if v := opts.String(); expected != v { 126 t.Fatalf("expected\n%s\nbut found\n%s", expected, v) 127 } 128 } 129 130 func TestOptionsCheck(t *testing.T) { 131 var opts *Options 132 opts = opts.EnsureDefaults() 133 s := opts.String() 134 require.NoError(t, opts.Check(s)) 135 require.Regexp(t, `invalid key=value syntax`, opts.Check("foo\n")) 136 137 tmp := *opts 138 tmp.Comparer = &Comparer{Name: "foo"} 139 require.Regexp(t, `comparer name from file.*!=.*`, tmp.Check(s)) 140 141 tmp = *opts 142 tmp.Merger = &Merger{Name: "foo"} 143 require.Regexp(t, `merger name from file.*!=.*`, tmp.Check(s)) 144 145 // RocksDB uses a similar (INI-style) syntax for the OPTIONS file, but 146 // different section names and keys. 147 s = ` 148 [CFOptions "default"] 149 comparator=rocksdb-comparer 150 merge_operator=rocksdb-merger 151 ` 152 tmp = *opts 153 tmp.Comparer = &Comparer{Name: "foo"} 154 require.Regexp(t, `comparer name from file.*!=.*`, tmp.Check(s)) 155 156 tmp.Comparer = &Comparer{Name: "rocksdb-comparer"} 157 tmp.Merger = &Merger{Name: "foo"} 158 require.Regexp(t, `merger name from file.*!=.*`, tmp.Check(s)) 159 160 tmp.Merger = &Merger{Name: "rocksdb-merger"} 161 require.NoError(t, tmp.Check(s)) 162 163 // RocksDB allows the merge operator to be unspecified, in which case it 164 // shows up as "nullptr". 165 s = ` 166 [CFOptions "default"] 167 merge_operator=nullptr 168 ` 169 tmp = *opts 170 require.NoError(t, tmp.Check(s)) 171 } 172 173 type testCleaner struct{} 174 175 func (testCleaner) Clean(fs vfs.FS, fileType base.FileType, path string) error { 176 return nil 177 } 178 179 func (testCleaner) String() string { 180 return "test-cleaner" 181 } 182 183 func TestOptionsParse(t *testing.T) { 184 testComparer := *DefaultComparer 185 testComparer.Name = "test-comparer" 186 testMerger := *DefaultMerger 187 testMerger.Name = "test-merger" 188 var newCacheSize int64 189 190 hooks := &ParseHooks{ 191 NewCache: func(size int64) *Cache { 192 newCacheSize = size 193 return nil 194 }, 195 NewCleaner: func(name string) (Cleaner, error) { 196 if name == (testCleaner{}).String() { 197 return testCleaner{}, nil 198 } 199 return nil, errors.Errorf("unknown cleaner: %q", name) 200 }, 201 NewComparer: func(name string) (*Comparer, error) { 202 if name == testComparer.Name { 203 return &testComparer, nil 204 } 205 return nil, errors.Errorf("unknown comparer: %q", name) 206 }, 207 NewMerger: func(name string) (*Merger, error) { 208 if name == testMerger.Name { 209 return &testMerger, nil 210 } 211 return nil, errors.Errorf("unknown merger: %q", name) 212 }, 213 } 214 215 testCases := []struct { 216 cleaner Cleaner 217 comparer *Comparer 218 merger *Merger 219 }{ 220 {testCleaner{}, nil, nil}, 221 {nil, &testComparer, nil}, 222 {nil, nil, &testMerger}, 223 } 224 for _, c := range testCases { 225 t.Run("", func(t *testing.T) { 226 var opts Options 227 opts.Comparer = c.comparer 228 opts.Merger = c.merger 229 opts.WALDir = "wal" 230 opts.Levels = make([]LevelOptions, 3) 231 opts.Levels[0].BlockSize = 1024 232 opts.Levels[1].BlockSize = 2048 233 opts.Levels[2].BlockSize = 4096 234 opts.Experimental.CompactionDebtConcurrency = 100 235 opts.FlushDelayDeleteRange = 10 * time.Second 236 opts.FlushDelayRangeKey = 11 * time.Second 237 opts.Experimental.LevelMultiplier = 5 238 opts.TargetByteDeletionRate = 200 239 opts.Experimental.ReadCompactionRate = 300 240 opts.Experimental.ReadSamplingMultiplier = 400 241 opts.Experimental.TableCacheShards = 500 242 opts.Experimental.MaxWriterConcurrency = 1 243 opts.Experimental.ForceWriterParallelism = true 244 opts.Experimental.SecondaryCacheSizeBytes = 1024 245 opts.EnsureDefaults() 246 str := opts.String() 247 248 newCacheSize = 0 249 var parsedOptions Options 250 require.NoError(t, parsedOptions.Parse(str, hooks)) 251 parsedStr := parsedOptions.String() 252 if str != parsedStr { 253 t.Fatalf("expected\n%s\nbut found\n%s", str, parsedStr) 254 } 255 require.Nil(t, parsedOptions.Cache) 256 require.NotEqual(t, newCacheSize, 0) 257 }) 258 } 259 } 260 261 func TestOptionsValidate(t *testing.T) { 262 testCases := []struct { 263 options string 264 expected string 265 }{ 266 {``, ``}, 267 {` 268 [Options] 269 l0_compaction_concurrency=0 270 `, 271 `L0CompactionConcurrency \(0\) must be >= 1`, 272 }, 273 {` 274 [Options] 275 l0_compaction_threshold=2 276 l0_stop_writes_threshold=1 277 `, 278 `L0StopWritesThreshold .* must be >= L0CompactionThreshold .*`, 279 }, 280 {` 281 [Options] 282 mem_table_size=4294967296 283 `, 284 `MemTableSize \(4\.0GB\) must be < [2|4]\.0GB`, 285 }, 286 {` 287 [Options] 288 mem_table_stop_writes_threshold=1 289 `, 290 `MemTableStopWritesThreshold .* must be >= 2`, 291 }, 292 } 293 294 for _, c := range testCases { 295 t.Run("", func(t *testing.T) { 296 var opts Options 297 opts.EnsureDefaults() 298 require.NoError(t, opts.Parse(c.options, nil)) 299 err := opts.Validate() 300 if c.expected == "" { 301 require.NoError(t, err) 302 } else { 303 require.Error(t, err) 304 require.Regexp(t, c.expected, err.Error()) 305 } 306 }) 307 } 308 } 309 310 // This test isn't being done in TestOptionsValidate 311 // cause it doesn't support setting pointers. 312 func TestOptionsValidateCache(t *testing.T) { 313 var opts Options 314 opts.EnsureDefaults() 315 opts.Cache = NewCache(8 << 20) 316 defer opts.Cache.Unref() 317 opts.TableCache = NewTableCache(NewCache(8<<20), 10, 1) 318 defer opts.TableCache.cache.Unref() 319 defer opts.TableCache.Unref() 320 321 err := opts.Validate() 322 require.Error(t, err) 323 if fmt.Sprint(err) != "underlying cache in the TableCache and the Cache dont match" { 324 t.Errorf("Unexpected error message") 325 } 326 }