github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/sstable/writer_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 sstable
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"math/rand"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"testing"
    16  
    17  	"github.com/cockroachdb/errors"
    18  	"github.com/stretchr/testify/require"
    19  	"github.com/zuoyebang/bitalostable/bloom"
    20  	"github.com/zuoyebang/bitalostable/internal/base"
    21  	"github.com/zuoyebang/bitalostable/internal/cache"
    22  	"github.com/zuoyebang/bitalostable/internal/datadriven"
    23  	"github.com/zuoyebang/bitalostable/internal/humanize"
    24  	"github.com/zuoyebang/bitalostable/internal/testkeys"
    25  	"github.com/zuoyebang/bitalostable/vfs"
    26  )
    27  
    28  func TestWriter(t *testing.T) {
    29  	runDataDriven(t, "testdata/writer", false)
    30  }
    31  
    32  func TestRewriter(t *testing.T) {
    33  	runDataDriven(t, "testdata/rewriter", false)
    34  }
    35  
    36  func TestWriterParallel(t *testing.T) {
    37  	runDataDriven(t, "testdata/writer", true)
    38  }
    39  
    40  func TestRewriterParallel(t *testing.T) {
    41  	runDataDriven(t, "testdata/rewriter", true)
    42  }
    43  
    44  func runDataDriven(t *testing.T, file string, parallelism bool) {
    45  	var r *Reader
    46  	defer func() {
    47  		if r != nil {
    48  			require.NoError(t, r.Close())
    49  		}
    50  	}()
    51  	formatVersion := TableFormatMax
    52  
    53  	format := func(m *WriterMetadata) string {
    54  		var b bytes.Buffer
    55  		if m.HasPointKeys {
    56  			fmt.Fprintf(&b, "point:    [%s-%s]\n", m.SmallestPoint, m.LargestPoint)
    57  		}
    58  		if m.HasRangeDelKeys {
    59  			fmt.Fprintf(&b, "rangedel: [%s-%s]\n", m.SmallestRangeDel, m.LargestRangeDel)
    60  		}
    61  		if m.HasRangeKeys {
    62  			fmt.Fprintf(&b, "rangekey: [%s-%s]\n", m.SmallestRangeKey, m.LargestRangeKey)
    63  		}
    64  		fmt.Fprintf(&b, "seqnums:  [%d-%d]\n", m.SmallestSeqNum, m.LargestSeqNum)
    65  		return b.String()
    66  	}
    67  
    68  	datadriven.RunTest(t, file, func(td *datadriven.TestData) string {
    69  		switch td.Cmd {
    70  		case "build":
    71  			if r != nil {
    72  				_ = r.Close()
    73  				r = nil
    74  			}
    75  			var meta *WriterMetadata
    76  			var err error
    77  			meta, r, err = runBuildCmd(td, &WriterOptions{
    78  				TableFormat: formatVersion,
    79  				Parallelism: parallelism,
    80  			}, 0)
    81  			if err != nil {
    82  				return err.Error()
    83  			}
    84  			return format(meta)
    85  
    86  		case "build-raw":
    87  			if r != nil {
    88  				_ = r.Close()
    89  				r = nil
    90  			}
    91  			var meta *WriterMetadata
    92  			var err error
    93  			meta, r, err = runBuildRawCmd(td, &WriterOptions{
    94  				TableFormat: formatVersion,
    95  			})
    96  			if err != nil {
    97  				return err.Error()
    98  			}
    99  			return format(meta)
   100  
   101  		case "scan":
   102  			origIter, err := r.NewIter(nil /* lower */, nil /* upper */)
   103  			if err != nil {
   104  				return err.Error()
   105  			}
   106  			iter := newIterAdapter(origIter)
   107  			defer iter.Close()
   108  
   109  			var buf bytes.Buffer
   110  			for valid := iter.First(); valid; valid = iter.Next() {
   111  				fmt.Fprintf(&buf, "%s:%s\n", iter.Key(), iter.Value())
   112  			}
   113  			return buf.String()
   114  
   115  		case "get":
   116  			var buf bytes.Buffer
   117  			for _, k := range strings.Split(td.Input, "\n") {
   118  				value, err := r.get([]byte(k))
   119  				if err != nil {
   120  					fmt.Fprintf(&buf, "get %s: %s\n", k, err.Error())
   121  				} else {
   122  					fmt.Fprintf(&buf, "%s\n", value)
   123  				}
   124  			}
   125  			return buf.String()
   126  
   127  		case "scan-range-del":
   128  			iter, err := r.NewRawRangeDelIter()
   129  			if err != nil {
   130  				return err.Error()
   131  			}
   132  			if iter == nil {
   133  				return ""
   134  			}
   135  			defer iter.Close()
   136  
   137  			var buf bytes.Buffer
   138  			for s := iter.First(); s != nil; s = iter.Next() {
   139  				fmt.Fprintf(&buf, "%s\n", s)
   140  			}
   141  			return buf.String()
   142  
   143  		case "scan-range-key":
   144  			iter, err := r.NewRawRangeKeyIter()
   145  			if err != nil {
   146  				return err.Error()
   147  			}
   148  			if iter == nil {
   149  				return ""
   150  			}
   151  			defer iter.Close()
   152  
   153  			var buf bytes.Buffer
   154  			for s := iter.First(); s != nil; s = iter.Next() {
   155  				fmt.Fprintf(&buf, "%s\n", s)
   156  			}
   157  			return buf.String()
   158  
   159  		case "layout":
   160  			l, err := r.Layout()
   161  			if err != nil {
   162  				return err.Error()
   163  			}
   164  			verbose := false
   165  			if len(td.CmdArgs) > 0 {
   166  				if td.CmdArgs[0].Key == "verbose" {
   167  					verbose = true
   168  				} else {
   169  					return "unknown arg"
   170  				}
   171  			}
   172  			var buf bytes.Buffer
   173  			l.Describe(&buf, verbose, r, nil)
   174  			return buf.String()
   175  
   176  		case "rewrite":
   177  			var meta *WriterMetadata
   178  			var err error
   179  			meta, r, err = runRewriteCmd(td, r, WriterOptions{
   180  				TableFormat: formatVersion,
   181  			})
   182  			if err != nil {
   183  				return err.Error()
   184  			}
   185  			if err != nil {
   186  				return err.Error()
   187  			}
   188  			return format(meta)
   189  
   190  		default:
   191  			return fmt.Sprintf("unknown command: %s", td.Cmd)
   192  		}
   193  	})
   194  }
   195  
   196  func testBlockBufClear(t *testing.T, b1, b2 *blockBuf) {
   197  	require.Equal(t, b1.tmp, b2.tmp)
   198  }
   199  
   200  func TestBlockBufClear(t *testing.T) {
   201  	b1 := &blockBuf{}
   202  	b1.tmp[0] = 1
   203  	b1.compressedBuf = make([]byte, 1)
   204  	b1.clear()
   205  	testBlockBufClear(t, b1, &blockBuf{})
   206  }
   207  
   208  func TestClearDataBlockBuf(t *testing.T) {
   209  	d := newDataBlockBuf(1, ChecksumTypeCRC32c)
   210  	d.blockBuf.compressedBuf = make([]byte, 1)
   211  	d.dataBlock.add(ikey("apple"), nil)
   212  	d.dataBlock.add(ikey("banana"), nil)
   213  
   214  	d.clear()
   215  	testBlockCleared(t, &d.dataBlock, &blockWriter{})
   216  	testBlockBufClear(t, &d.blockBuf, &blockBuf{})
   217  
   218  	dataBlockBufPool.Put(d)
   219  }
   220  
   221  func TestClearIndexBlockBuf(t *testing.T) {
   222  	i := newIndexBlockBuf(false)
   223  	i.block.add(ikey("apple"), nil)
   224  	i.block.add(ikey("banana"), nil)
   225  	i.clear()
   226  
   227  	testBlockCleared(t, &i.block, &blockWriter{})
   228  	require.Equal(
   229  		t, i.size.estimate, sizeEstimate{emptySize: i.size.estimate.emptySize},
   230  	)
   231  	indexBlockBufPool.Put(i)
   232  }
   233  
   234  func TestClearWriteTask(t *testing.T) {
   235  	w := writeTaskPool.Get().(*writeTask)
   236  	ch := make(chan bool, 1)
   237  	w.compressionDone = ch
   238  	w.buf = &dataBlockBuf{}
   239  	w.flushableIndexBlock = &indexBlockBuf{}
   240  	w.currIndexBlock = &indexBlockBuf{}
   241  	w.indexEntrySep = ikey("apple")
   242  	w.inflightSize = 1
   243  	w.indexInflightSize = 1
   244  	w.finishedIndexProps = []byte{'a', 'v'}
   245  
   246  	w.clear()
   247  
   248  	var nilDataBlockBuf *dataBlockBuf
   249  	var nilIndexBlockBuf *indexBlockBuf
   250  	// Channels should be the same(no new channel should be allocated)
   251  	require.Equal(t, w.compressionDone, ch)
   252  	require.Equal(t, w.buf, nilDataBlockBuf)
   253  	require.Equal(t, w.flushableIndexBlock, nilIndexBlockBuf)
   254  	require.Equal(t, w.currIndexBlock, nilIndexBlockBuf)
   255  	require.Equal(t, w.indexEntrySep, base.InvalidInternalKey)
   256  	require.Equal(t, w.inflightSize, 0)
   257  	require.Equal(t, w.indexInflightSize, 0)
   258  	require.Equal(t, w.finishedIndexProps, []byte(nil))
   259  
   260  	writeTaskPool.Put(w)
   261  }
   262  
   263  func TestDoubleClose(t *testing.T) {
   264  	// There is code in Cockroach land which relies on Writer.Close being
   265  	// idempotent. We should test this in Pebble, so that we don't cause
   266  	// Cockroach test failures.
   267  	f := &discardFile{}
   268  	w := NewWriter(f, WriterOptions{
   269  		BlockSize:   1,
   270  		TableFormat: TableFormatPebblev1,
   271  	})
   272  	w.Set(ikey("a").UserKey, nil)
   273  	w.Set(ikey("b").UserKey, nil)
   274  	err := w.Close()
   275  	require.NoError(t, err)
   276  	err = w.Close()
   277  	require.Equal(t, err, errWriterClosed)
   278  }
   279  
   280  func TestParallelWriterErrorProp(t *testing.T) {
   281  	fs := vfs.NewMem()
   282  	f, err := fs.Create("test")
   283  	require.NoError(t, err)
   284  	opts := WriterOptions{
   285  		TableFormat: TableFormatPebblev1, BlockSize: 1, Parallelism: true,
   286  	}
   287  
   288  	w := NewWriter(f, opts)
   289  	// Directly testing this, because it's difficult to get the Writer to
   290  	// encounter an error, precisely when the writeQueue is doing block writes.
   291  	w.coordination.writeQueue.err = errors.New("write queue write error")
   292  	w.Set(ikey("a").UserKey, nil)
   293  	w.Set(ikey("b").UserKey, nil)
   294  	err = w.Close()
   295  	require.Equal(t, err.Error(), "write queue write error")
   296  }
   297  
   298  func TestSizeEstimate(t *testing.T) {
   299  	var sizeEstimate sizeEstimate
   300  	datadriven.RunTest(t, "testdata/size_estimate",
   301  		func(td *datadriven.TestData) string {
   302  			switch td.Cmd {
   303  			case "init":
   304  				if len(td.CmdArgs) != 1 {
   305  					return "init <empty size>"
   306  				}
   307  				emptySize, err := strconv.Atoi(td.CmdArgs[0].String())
   308  				if err != nil {
   309  					return "invalid empty size"
   310  				}
   311  				sizeEstimate.init(uint64(emptySize))
   312  				return "success"
   313  			case "clear":
   314  				sizeEstimate.clear()
   315  				return fmt.Sprintf("%d", sizeEstimate.size())
   316  			case "size":
   317  				return fmt.Sprintf("%d", sizeEstimate.size())
   318  			case "add_inflight":
   319  				if len(td.CmdArgs) != 1 {
   320  					return "add_inflight <inflight size estimate>"
   321  				}
   322  				inflightSize, err := strconv.Atoi(td.CmdArgs[0].String())
   323  				if err != nil {
   324  					return "invalid inflight size"
   325  				}
   326  				sizeEstimate.addInflight(inflightSize)
   327  				return fmt.Sprintf("%d", sizeEstimate.size())
   328  			case "entry_written":
   329  				if len(td.CmdArgs) != 3 {
   330  					return "entry_written <new_size> <prev_inflight_size> <entry_size>"
   331  				}
   332  				newSize, err := strconv.Atoi(td.CmdArgs[0].String())
   333  				if err != nil {
   334  					return "invalid inflight size"
   335  				}
   336  				inflightSize, err := strconv.Atoi(td.CmdArgs[1].String())
   337  				if err != nil {
   338  					return "invalid inflight size"
   339  				}
   340  				entrySize, err := strconv.Atoi(td.CmdArgs[2].String())
   341  				if err != nil {
   342  					return "invalid inflight size"
   343  				}
   344  				sizeEstimate.written(uint64(newSize), inflightSize, entrySize)
   345  				return fmt.Sprintf("%d", sizeEstimate.size())
   346  			case "num_written_entries":
   347  				return fmt.Sprintf("%d", sizeEstimate.numWrittenEntries)
   348  			case "num_inflight_entries":
   349  				return fmt.Sprintf("%d", sizeEstimate.numInflightEntries)
   350  			case "num_entries":
   351  				return fmt.Sprintf("%d", sizeEstimate.numWrittenEntries+sizeEstimate.numInflightEntries)
   352  			default:
   353  				return fmt.Sprintf("unknown command: %s", td.Cmd)
   354  			}
   355  		})
   356  }
   357  func TestWriterClearCache(t *testing.T) {
   358  	// Verify that Writer clears the cache of blocks that it writes.
   359  	mem := vfs.NewMem()
   360  	opts := ReaderOptions{Cache: cache.New(64 << 20)}
   361  	defer opts.Cache.Unref()
   362  
   363  	writerOpts := WriterOptions{Cache: opts.Cache}
   364  	cacheOpts := &cacheOpts{cacheID: 1, fileNum: 1}
   365  	invalidData := func() *cache.Value {
   366  		invalid := []byte("invalid data")
   367  		v := opts.Cache.Alloc(len(invalid))
   368  		copy(v.Buf(), invalid)
   369  		return v
   370  	}
   371  
   372  	build := func(name string) {
   373  		f, err := mem.Create(name)
   374  		require.NoError(t, err)
   375  
   376  		w := NewWriter(f, writerOpts, cacheOpts)
   377  		require.NoError(t, w.Set([]byte("hello"), []byte("world")))
   378  		require.NoError(t, w.Close())
   379  	}
   380  
   381  	// Build the sstable a first time so that we can determine the locations of
   382  	// all of the blocks.
   383  	build("test")
   384  
   385  	f, err := mem.Open("test")
   386  	require.NoError(t, err)
   387  
   388  	r, err := NewReader(f, opts)
   389  	require.NoError(t, err)
   390  
   391  	layout, err := r.Layout()
   392  	require.NoError(t, err)
   393  
   394  	foreachBH := func(layout *Layout, f func(bh BlockHandle)) {
   395  		for _, bh := range layout.Data {
   396  			f(bh.BlockHandle)
   397  		}
   398  		for _, bh := range layout.Index {
   399  			f(bh)
   400  		}
   401  		f(layout.TopIndex)
   402  		f(layout.Filter)
   403  		f(layout.RangeDel)
   404  		f(layout.Properties)
   405  		f(layout.MetaIndex)
   406  	}
   407  
   408  	// Poison the cache for each of the blocks.
   409  	poison := func(bh BlockHandle) {
   410  		opts.Cache.Set(cacheOpts.cacheID, cacheOpts.fileNum, bh.Offset, invalidData()).Release()
   411  	}
   412  	foreachBH(layout, poison)
   413  
   414  	// Build the table a second time. This should clear the cache for the blocks
   415  	// that are written.
   416  	build("test")
   417  
   418  	// Verify that the written blocks have been cleared from the cache.
   419  	check := func(bh BlockHandle) {
   420  		h := opts.Cache.Get(cacheOpts.cacheID, cacheOpts.fileNum, bh.Offset)
   421  		if h.Get() != nil {
   422  			t.Fatalf("%d: expected cache to be cleared, but found %q", bh.Offset, h.Get())
   423  		}
   424  	}
   425  	foreachBH(layout, check)
   426  
   427  	require.NoError(t, r.Close())
   428  }
   429  
   430  type discardFile struct{ wrote int64 }
   431  
   432  func (f discardFile) Close() error {
   433  	return nil
   434  }
   435  
   436  func (f *discardFile) Write(p []byte) (int, error) {
   437  	f.wrote += int64(len(p))
   438  	return len(p), nil
   439  }
   440  
   441  func (f discardFile) Sync() error {
   442  	return nil
   443  }
   444  
   445  type blockPropErrSite uint
   446  
   447  const (
   448  	errSiteAdd blockPropErrSite = iota
   449  	errSiteFinishBlock
   450  	errSiteFinishIndex
   451  	errSiteFinishTable
   452  	errSiteNone
   453  )
   454  
   455  type testBlockPropCollector struct {
   456  	errSite blockPropErrSite
   457  	err     error
   458  }
   459  
   460  func (c *testBlockPropCollector) Name() string { return "testBlockPropCollector" }
   461  
   462  func (c *testBlockPropCollector) Add(_ InternalKey, _ []byte) error {
   463  	if c.errSite == errSiteAdd {
   464  		return c.err
   465  	}
   466  	return nil
   467  }
   468  
   469  func (c *testBlockPropCollector) FinishDataBlock(_ []byte) ([]byte, error) {
   470  	if c.errSite == errSiteFinishBlock {
   471  		return nil, c.err
   472  	}
   473  	return nil, nil
   474  }
   475  
   476  func (c *testBlockPropCollector) AddPrevDataBlockToIndexBlock() {}
   477  
   478  func (c *testBlockPropCollector) FinishIndexBlock(_ []byte) ([]byte, error) {
   479  	if c.errSite == errSiteFinishIndex {
   480  		return nil, c.err
   481  	}
   482  	return nil, nil
   483  }
   484  
   485  func (c *testBlockPropCollector) FinishTable(_ []byte) ([]byte, error) {
   486  	if c.errSite == errSiteFinishTable {
   487  		return nil, c.err
   488  	}
   489  	return nil, nil
   490  }
   491  
   492  func TestWriterBlockPropertiesErrors(t *testing.T) {
   493  	blockPropErr := errors.Newf("block property collector failed")
   494  	testCases := []blockPropErrSite{
   495  		errSiteAdd,
   496  		errSiteFinishBlock,
   497  		errSiteFinishIndex,
   498  		errSiteFinishTable,
   499  		errSiteNone,
   500  	}
   501  
   502  	var (
   503  		k1 = base.MakeInternalKey([]byte("a"), 0, base.InternalKeyKindSet)
   504  		v1 = []byte("apples")
   505  		k2 = base.MakeInternalKey([]byte("b"), 0, base.InternalKeyKindSet)
   506  		v2 = []byte("bananas")
   507  		k3 = base.MakeInternalKey([]byte("c"), 0, base.InternalKeyKindSet)
   508  		v3 = []byte("carrots")
   509  	)
   510  
   511  	for _, tc := range testCases {
   512  		t.Run("", func(t *testing.T) {
   513  			fs := vfs.NewMem()
   514  			f, err := fs.Create("test")
   515  			require.NoError(t, err)
   516  
   517  			w := NewWriter(f, WriterOptions{
   518  				BlockSize: 1,
   519  				BlockPropertyCollectors: []func() BlockPropertyCollector{
   520  					func() BlockPropertyCollector {
   521  						return &testBlockPropCollector{
   522  							errSite: tc,
   523  							err:     blockPropErr,
   524  						}
   525  					},
   526  				},
   527  				TableFormat: TableFormatPebblev1,
   528  			})
   529  
   530  			err = w.Add(k1, v1)
   531  			switch tc {
   532  			case errSiteAdd:
   533  				require.Error(t, err)
   534  				require.Equal(t, blockPropErr, err)
   535  				return
   536  			case errSiteFinishBlock:
   537  				require.NoError(t, err)
   538  				// Addition of a second key completes the first block.
   539  				err = w.Add(k2, v2)
   540  				require.Error(t, err)
   541  				require.Equal(t, blockPropErr, err)
   542  				return
   543  			case errSiteFinishIndex:
   544  				require.NoError(t, err)
   545  				// Addition of a second key completes the first block.
   546  				err = w.Add(k2, v2)
   547  				require.NoError(t, err)
   548  				// The index entry for the first block is added after the completion of
   549  				// the second block, which is triggered by adding a third key.
   550  				err = w.Add(k3, v3)
   551  				require.Error(t, err)
   552  				require.Equal(t, blockPropErr, err)
   553  				return
   554  			}
   555  
   556  			err = w.Close()
   557  			if tc == errSiteFinishTable {
   558  				require.Error(t, err)
   559  				require.Equal(t, blockPropErr, err)
   560  			} else {
   561  				require.NoError(t, err)
   562  			}
   563  		})
   564  	}
   565  }
   566  
   567  func TestWriter_TableFormatCompatibility(t *testing.T) {
   568  	testCases := []struct {
   569  		name        string
   570  		minFormat   TableFormat
   571  		configureFn func(opts *WriterOptions)
   572  		writeFn     func(w *Writer) error
   573  	}{
   574  		{
   575  			name:      "block properties",
   576  			minFormat: TableFormatPebblev1,
   577  			configureFn: func(opts *WriterOptions) {
   578  				opts.BlockPropertyCollectors = []func() BlockPropertyCollector{
   579  					func() BlockPropertyCollector {
   580  						return NewBlockIntervalCollector(
   581  							"collector", &valueCharBlockIntervalCollector{charIdx: 0}, nil,
   582  						)
   583  					},
   584  				}
   585  			},
   586  		},
   587  		{
   588  			name:      "range keys",
   589  			minFormat: TableFormatPebblev2,
   590  			writeFn: func(w *Writer) error {
   591  				return w.RangeKeyDelete([]byte("a"), []byte("b"))
   592  			},
   593  		},
   594  	}
   595  
   596  	for _, tc := range testCases {
   597  		t.Run(tc.name, func(t *testing.T) {
   598  			for tf := TableFormatLevelDB; tf <= TableFormatMax; tf++ {
   599  				t.Run(tf.String(), func(t *testing.T) {
   600  					fs := vfs.NewMem()
   601  					f, err := fs.Create("sst")
   602  					require.NoError(t, err)
   603  
   604  					opts := WriterOptions{TableFormat: tf}
   605  					if tc.configureFn != nil {
   606  						tc.configureFn(&opts)
   607  					}
   608  
   609  					w := NewWriter(f, opts)
   610  					if tc.writeFn != nil {
   611  						err = tc.writeFn(w)
   612  						require.NoError(t, err)
   613  					}
   614  
   615  					err = w.Close()
   616  					if tf < tc.minFormat {
   617  						require.Error(t, err)
   618  					} else {
   619  						require.NoError(t, err)
   620  					}
   621  				})
   622  			}
   623  		})
   624  	}
   625  }
   626  
   627  // Tests for races, such as https://github.com/cockroachdb/cockroach/issues/77194,
   628  // in the Writer.
   629  func TestWriterRace(t *testing.T) {
   630  	ks := testkeys.Alpha(5)
   631  	ks = ks.EveryN(ks.Count() / 1_000)
   632  	keys := make([][]byte, ks.Count())
   633  	for ki := 0; ki < len(keys); ki++ {
   634  		keys[ki] = testkeys.Key(ks, ki)
   635  	}
   636  	readerOpts := ReaderOptions{
   637  		Comparer: testkeys.Comparer,
   638  		Filters:  map[string]base.FilterPolicy{},
   639  	}
   640  
   641  	var wg sync.WaitGroup
   642  	for i := 0; i < 16; i++ {
   643  		wg.Add(1)
   644  		go func() {
   645  			val := make([]byte, rand.Intn(1000))
   646  			opts := WriterOptions{
   647  				Comparer:    testkeys.Comparer,
   648  				BlockSize:   rand.Intn(1 << 10),
   649  				Compression: NoCompression,
   650  			}
   651  			defer wg.Done()
   652  			f := &memFile{}
   653  			w := NewWriter(f, opts)
   654  			for ki := 0; ki < len(keys); ki++ {
   655  				require.NoError(
   656  					t,
   657  					w.Add(base.MakeInternalKey(keys[ki], uint64(ki), InternalKeyKindSet), val),
   658  				)
   659  				require.Equal(
   660  					t, base.DecodeInternalKey(w.dataBlockBuf.dataBlock.curKey).UserKey, keys[ki],
   661  				)
   662  			}
   663  			require.NoError(t, w.Close())
   664  			require.Equal(t, w.meta.LargestPoint.UserKey, keys[len(keys)-1])
   665  			r, err := NewMemReader(f.Bytes(), readerOpts)
   666  			require.NoError(t, err)
   667  			defer r.Close()
   668  			it, err := r.NewIter(nil, nil)
   669  			require.NoError(t, err)
   670  			defer it.Close()
   671  			ki := 0
   672  			for k, v := it.First(); k != nil; k, v = it.Next() {
   673  				require.Equal(t, k.UserKey, keys[ki])
   674  				require.Equal(t, v, val)
   675  				ki++
   676  			}
   677  		}()
   678  	}
   679  	wg.Wait()
   680  }
   681  
   682  func BenchmarkWriter(b *testing.B) {
   683  	keys := make([][]byte, 1e6)
   684  	const keyLen = 24
   685  	keySlab := make([]byte, keyLen*len(keys))
   686  	for i := range keys {
   687  		key := keySlab[i*keyLen : i*keyLen+keyLen]
   688  		binary.BigEndian.PutUint64(key[:8], 123) // 16-byte shared prefix
   689  		binary.BigEndian.PutUint64(key[8:16], 456)
   690  		binary.BigEndian.PutUint64(key[16:], uint64(i))
   691  		keys[i] = key
   692  	}
   693  
   694  	b.ResetTimer()
   695  
   696  	for _, bs := range []int{base.DefaultBlockSize, 32 << 10} {
   697  		b.Run(fmt.Sprintf("block=%s", humanize.IEC.Int64(int64(bs))), func(b *testing.B) {
   698  			for _, filter := range []bool{true, false} {
   699  				b.Run(fmt.Sprintf("filter=%t", filter), func(b *testing.B) {
   700  					for _, comp := range []Compression{NoCompression, SnappyCompression, ZstdCompression} {
   701  						b.Run(fmt.Sprintf("compression=%s", comp), func(b *testing.B) {
   702  							opts := WriterOptions{
   703  								BlockRestartInterval: 16,
   704  								BlockSize:            bs,
   705  								Compression:          comp,
   706  							}
   707  							if filter {
   708  								opts.FilterPolicy = bloom.FilterPolicy(10)
   709  							}
   710  							f := &discardFile{}
   711  							for i := 0; i < b.N; i++ {
   712  								f.wrote = 0
   713  								w := NewWriter(f, opts)
   714  
   715  								for j := range keys {
   716  									if err := w.Set(keys[j], keys[j]); err != nil {
   717  										b.Fatal(err)
   718  									}
   719  								}
   720  								if err := w.Close(); err != nil {
   721  									b.Fatal(err)
   722  								}
   723  								b.SetBytes(int64(f.wrote))
   724  							}
   725  						})
   726  					}
   727  				})
   728  			}
   729  		})
   730  	}
   731  }
   732  
   733  var test4bSuffixComparer = &base.Comparer{
   734  	Compare:   base.DefaultComparer.Compare,
   735  	Equal:     base.DefaultComparer.Equal,
   736  	Separator: base.DefaultComparer.Separator,
   737  	Successor: base.DefaultComparer.Successor,
   738  	Split: func(key []byte) int {
   739  		if len(key) > 4 {
   740  			return len(key) - 4
   741  		}
   742  		return len(key)
   743  	},
   744  	Name: "comparer-split-4b-suffix",
   745  }