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  }