github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/internal/manifest/version_edit_test.go (about)

     1  // Copyright 2012 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 manifest
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"reflect"
    13  	"slices"
    14  	"strconv"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/cockroachdb/datadriven"
    19  	"github.com/cockroachdb/errors"
    20  	"github.com/cockroachdb/pebble/internal/base"
    21  	"github.com/cockroachdb/pebble/record"
    22  	"github.com/kr/pretty"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  func checkRoundTrip(e0 VersionEdit) error {
    27  	var e1 VersionEdit
    28  	buf := new(bytes.Buffer)
    29  	if err := e0.Encode(buf); err != nil {
    30  		return errors.Wrap(err, "encode")
    31  	}
    32  	if err := e1.Decode(buf); err != nil {
    33  		return errors.Wrap(err, "decode")
    34  	}
    35  	if diff := pretty.Diff(e0, e1); diff != nil {
    36  		return errors.Errorf("%s", strings.Join(diff, "\n"))
    37  	}
    38  	return nil
    39  }
    40  
    41  // Version edits with virtual sstables will not be the same after a round trip
    42  // as the Decode function will not set the FileBacking for a virtual sstable.
    43  // We test round trip + bve accumulation here, after which the virtual sstable
    44  // FileBacking should be set.
    45  func TestVERoundTripAndAccumulate(t *testing.T) {
    46  	cmp := base.DefaultComparer.Compare
    47  	m1 := (&FileMetadata{
    48  		FileNum:        810,
    49  		Size:           8090,
    50  		CreationTime:   809060,
    51  		SmallestSeqNum: 9,
    52  		LargestSeqNum:  11,
    53  	}).ExtendPointKeyBounds(
    54  		cmp,
    55  		base.MakeInternalKey([]byte("a"), 0, base.InternalKeyKindSet),
    56  		base.MakeInternalKey([]byte("m"), 0, base.InternalKeyKindSet),
    57  	).ExtendRangeKeyBounds(
    58  		cmp,
    59  		base.MakeInternalKey([]byte("l"), 0, base.InternalKeyKindRangeKeySet),
    60  		base.MakeExclusiveSentinelKey(base.InternalKeyKindRangeKeySet, []byte("z")),
    61  	)
    62  	m1.InitPhysicalBacking()
    63  
    64  	m2 := (&FileMetadata{
    65  		FileNum:        812,
    66  		Size:           8090,
    67  		CreationTime:   809060,
    68  		SmallestSeqNum: 9,
    69  		LargestSeqNum:  11,
    70  		Virtual:        true,
    71  		FileBacking:    m1.FileBacking,
    72  	}).ExtendPointKeyBounds(
    73  		cmp,
    74  		base.MakeInternalKey([]byte("a"), 0, base.InternalKeyKindSet),
    75  		base.MakeInternalKey([]byte("c"), 0, base.InternalKeyKindSet),
    76  	)
    77  
    78  	ve1 := VersionEdit{
    79  		ComparerName:         "11",
    80  		MinUnflushedLogNum:   22,
    81  		ObsoletePrevLogNum:   33,
    82  		NextFileNum:          44,
    83  		LastSeqNum:           55,
    84  		CreatedBackingTables: []*FileBacking{m1.FileBacking},
    85  		NewFiles: []NewFileEntry{
    86  			{
    87  				Level: 4,
    88  				Meta:  m2,
    89  				// Only set for the test.
    90  				BackingFileNum: m2.FileBacking.DiskFileNum,
    91  			},
    92  		},
    93  	}
    94  	var err error
    95  	buf := new(bytes.Buffer)
    96  	if err = ve1.Encode(buf); err != nil {
    97  		t.Error(err)
    98  	}
    99  	var ve2 VersionEdit
   100  	if err = ve2.Decode(buf); err != nil {
   101  		t.Error(err)
   102  	}
   103  	// Perform accumulation to set the FileBacking on the files in the Decoded
   104  	// version edit.
   105  	var bve BulkVersionEdit
   106  	require.NoError(t, bve.Accumulate(&ve2))
   107  	if diff := pretty.Diff(ve1, ve2); diff != nil {
   108  		t.Error(errors.Errorf("%s", strings.Join(diff, "\n")))
   109  	}
   110  }
   111  
   112  func TestVersionEditRoundTrip(t *testing.T) {
   113  	cmp := base.DefaultComparer.Compare
   114  	m1 := (&FileMetadata{
   115  		FileNum:      805,
   116  		Size:         8050,
   117  		CreationTime: 805030,
   118  	}).ExtendPointKeyBounds(
   119  		cmp,
   120  		base.DecodeInternalKey([]byte("abc\x00\x01\x02\x03\x04\x05\x06\x07")),
   121  		base.DecodeInternalKey([]byte("xyz\x01\xff\xfe\xfd\xfc\xfb\xfa\xf9")),
   122  	)
   123  	m1.InitPhysicalBacking()
   124  
   125  	m2 := (&FileMetadata{
   126  		FileNum:             806,
   127  		Size:                8060,
   128  		CreationTime:        806040,
   129  		SmallestSeqNum:      3,
   130  		LargestSeqNum:       5,
   131  		MarkedForCompaction: true,
   132  	}).ExtendPointKeyBounds(
   133  		cmp,
   134  		base.DecodeInternalKey([]byte("A\x00\x01\x02\x03\x04\x05\x06\x07")),
   135  		base.DecodeInternalKey([]byte("Z\x01\xff\xfe\xfd\xfc\xfb\xfa\xf9")),
   136  	)
   137  	m2.InitPhysicalBacking()
   138  
   139  	m3 := (&FileMetadata{
   140  		FileNum:      807,
   141  		Size:         8070,
   142  		CreationTime: 807050,
   143  	}).ExtendRangeKeyBounds(
   144  		cmp,
   145  		base.MakeInternalKey([]byte("aaa"), 0, base.InternalKeyKindRangeKeySet),
   146  		base.MakeExclusiveSentinelKey(base.InternalKeyKindRangeKeySet, []byte("zzz")),
   147  	)
   148  	m3.InitPhysicalBacking()
   149  
   150  	m4 := (&FileMetadata{
   151  		FileNum:        809,
   152  		Size:           8090,
   153  		CreationTime:   809060,
   154  		SmallestSeqNum: 9,
   155  		LargestSeqNum:  11,
   156  	}).ExtendPointKeyBounds(
   157  		cmp,
   158  		base.MakeInternalKey([]byte("a"), 0, base.InternalKeyKindSet),
   159  		base.MakeInternalKey([]byte("m"), 0, base.InternalKeyKindSet),
   160  	).ExtendRangeKeyBounds(
   161  		cmp,
   162  		base.MakeInternalKey([]byte("l"), 0, base.InternalKeyKindRangeKeySet),
   163  		base.MakeExclusiveSentinelKey(base.InternalKeyKindRangeKeySet, []byte("z")),
   164  	)
   165  	m4.InitPhysicalBacking()
   166  
   167  	m5 := (&FileMetadata{
   168  		FileNum:        810,
   169  		Size:           8090,
   170  		CreationTime:   809060,
   171  		SmallestSeqNum: 9,
   172  		LargestSeqNum:  11,
   173  	}).ExtendPointKeyBounds(
   174  		cmp,
   175  		base.MakeInternalKey([]byte("a"), 0, base.InternalKeyKindSet),
   176  		base.MakeInternalKey([]byte("m"), 0, base.InternalKeyKindSet),
   177  	).ExtendRangeKeyBounds(
   178  		cmp,
   179  		base.MakeInternalKey([]byte("l"), 0, base.InternalKeyKindRangeKeySet),
   180  		base.MakeExclusiveSentinelKey(base.InternalKeyKindRangeKeySet, []byte("z")),
   181  	)
   182  	m5.InitPhysicalBacking()
   183  
   184  	m6 := (&FileMetadata{
   185  		FileNum:        811,
   186  		Size:           8090,
   187  		CreationTime:   809060,
   188  		SmallestSeqNum: 9,
   189  		LargestSeqNum:  11,
   190  	}).ExtendPointKeyBounds(
   191  		cmp,
   192  		base.MakeInternalKey([]byte("a"), 0, base.InternalKeyKindSet),
   193  		base.MakeInternalKey([]byte("m"), 0, base.InternalKeyKindSet),
   194  	).ExtendRangeKeyBounds(
   195  		cmp,
   196  		base.MakeInternalKey([]byte("l"), 0, base.InternalKeyKindRangeKeySet),
   197  		base.MakeExclusiveSentinelKey(base.InternalKeyKindRangeKeySet, []byte("z")),
   198  	)
   199  	m6.InitPhysicalBacking()
   200  
   201  	testCases := []VersionEdit{
   202  		// An empty version edit.
   203  		{},
   204  		// A complete version edit.
   205  		{
   206  			ComparerName:       "11",
   207  			MinUnflushedLogNum: 22,
   208  			ObsoletePrevLogNum: 33,
   209  			NextFileNum:        44,
   210  			LastSeqNum:         55,
   211  			RemovedBackingTables: []base.DiskFileNum{
   212  				base.FileNum(10).DiskFileNum(), base.FileNum(11).DiskFileNum(),
   213  			},
   214  			CreatedBackingTables: []*FileBacking{m5.FileBacking, m6.FileBacking},
   215  			DeletedFiles: map[DeletedFileEntry]*FileMetadata{
   216  				{
   217  					Level:   3,
   218  					FileNum: 703,
   219  				}: nil,
   220  				{
   221  					Level:   4,
   222  					FileNum: 704,
   223  				}: nil,
   224  			},
   225  			NewFiles: []NewFileEntry{
   226  				{
   227  					Level: 4,
   228  					Meta:  m1,
   229  				},
   230  				{
   231  					Level: 5,
   232  					Meta:  m2,
   233  				},
   234  				{
   235  					Level: 6,
   236  					Meta:  m3,
   237  				},
   238  				{
   239  					Level: 6,
   240  					Meta:  m4,
   241  				},
   242  			},
   243  		},
   244  	}
   245  	for _, tc := range testCases {
   246  		if err := checkRoundTrip(tc); err != nil {
   247  			t.Error(err)
   248  		}
   249  	}
   250  }
   251  
   252  func TestVersionEditDecode(t *testing.T) {
   253  	// TODO(radu): these should be datadriven tests that output the encoded and
   254  	// decoded edits.
   255  	cmp := base.DefaultComparer.Compare
   256  	m := (&FileMetadata{
   257  		FileNum:        4,
   258  		Size:           709,
   259  		SmallestSeqNum: 12,
   260  		LargestSeqNum:  14,
   261  		CreationTime:   1701712644,
   262  	}).ExtendPointKeyBounds(
   263  		cmp,
   264  		base.MakeInternalKey([]byte("bar"), 14, base.InternalKeyKindDelete),
   265  		base.MakeInternalKey([]byte("foo"), 13, base.InternalKeyKindSet),
   266  	)
   267  	m.InitPhysicalBacking()
   268  
   269  	testCases := []struct {
   270  		filename     string
   271  		encodedEdits []string
   272  		edits        []VersionEdit
   273  	}{
   274  		// db-stage-1 and db-stage-2 have the same manifest.
   275  		{
   276  			filename: "db-stage-1/MANIFEST-000001",
   277  			encodedEdits: []string{
   278  				"\x01\x1aleveldb.BytewiseComparator\x03\x02\x04\x00",
   279  				"\x02\x02\x03\x03\x04\t",
   280  			},
   281  			edits: []VersionEdit{
   282  				{
   283  					ComparerName: "leveldb.BytewiseComparator",
   284  					NextFileNum:  2,
   285  				},
   286  				{
   287  					MinUnflushedLogNum: 0x2,
   288  					NextFileNum:        0x3,
   289  					LastSeqNum:         0x9,
   290  				},
   291  			},
   292  		},
   293  		// db-stage-3 and db-stage-4 have the same manifest.
   294  		{
   295  			filename: "db-stage-3/MANIFEST-000006",
   296  			encodedEdits: []string{
   297  				"\x01\x1aleveldb.BytewiseComparator\x02\x02\x03\a\x04\x00",
   298  				"\x02\x05\x03\x06\x04\x0eg\x00\x04\xc5\x05\vbar\x00\x0e\x00\x00\x00\x00\x00\x00\vfoo\x01\r\x00\x00\x00\x00\x00\x00\f\x0e\x06\x05\x84\xa6\xb8\xab\x06\x01",
   299  			},
   300  			edits: []VersionEdit{
   301  				{
   302  					ComparerName:       "leveldb.BytewiseComparator",
   303  					MinUnflushedLogNum: 0x2,
   304  					NextFileNum:        0x7,
   305  				},
   306  				{
   307  					MinUnflushedLogNum: 0x5,
   308  					NextFileNum:        0x6,
   309  					LastSeqNum:         0xe,
   310  					NewFiles: []NewFileEntry{
   311  						{
   312  							Level: 0,
   313  							Meta:  m,
   314  						},
   315  					},
   316  				},
   317  			},
   318  		},
   319  	}
   320  
   321  	for _, tc := range testCases {
   322  		t.Run("", func(t *testing.T) {
   323  			f, err := os.Open("../../testdata/" + tc.filename)
   324  			if err != nil {
   325  				t.Fatalf("filename=%q: open error: %v", tc.filename, err)
   326  			}
   327  			defer f.Close()
   328  			i, r := 0, record.NewReader(f, 0 /* logNum */)
   329  			for {
   330  				rr, err := r.Next()
   331  				if err == io.EOF {
   332  					break
   333  				}
   334  				if err != nil {
   335  					t.Fatalf("filename=%q i=%d: record reader error: %v", tc.filename, i, err)
   336  				}
   337  				if i >= len(tc.edits) {
   338  					t.Fatalf("filename=%q i=%d: too many version edits", tc.filename, i+1)
   339  				}
   340  
   341  				encodedEdit, err := io.ReadAll(rr)
   342  				if err != nil {
   343  					t.Fatalf("filename=%q i=%d: read error: %v", tc.filename, i, err)
   344  				}
   345  				if s := string(encodedEdit); s != tc.encodedEdits[i] {
   346  					t.Fatalf("filename=%q i=%d: got encoded %q, want %q", tc.filename, i, s, tc.encodedEdits[i])
   347  				}
   348  
   349  				var edit VersionEdit
   350  				err = edit.Decode(bytes.NewReader(encodedEdit))
   351  				if err != nil {
   352  					t.Fatalf("filename=%q i=%d: decode error: %v", tc.filename, i, err)
   353  				}
   354  				if !reflect.DeepEqual(edit, tc.edits[i]) {
   355  					t.Fatalf("filename=%q i=%d: decode\n\tgot  %#v\n\twant %#v\n%s", tc.filename, i, edit, tc.edits[i],
   356  						strings.Join(pretty.Diff(edit, tc.edits[i]), "\n"))
   357  				}
   358  				if err := checkRoundTrip(edit); err != nil {
   359  					t.Fatalf("filename=%q i=%d: round trip: %v", tc.filename, i, err)
   360  				}
   361  
   362  				i++
   363  			}
   364  			if i != len(tc.edits) {
   365  				t.Fatalf("filename=%q: got %d edits, want %d", tc.filename, i, len(tc.edits))
   366  			}
   367  		})
   368  	}
   369  }
   370  
   371  func TestVersionEditEncodeLastSeqNum(t *testing.T) {
   372  	testCases := []struct {
   373  		edit    VersionEdit
   374  		encoded string
   375  	}{
   376  		// If ComparerName is unset, LastSeqNum is only encoded if non-zero.
   377  		{VersionEdit{LastSeqNum: 0}, ""},
   378  		{VersionEdit{LastSeqNum: 1}, "\x04\x01"},
   379  		// For compatibility with RocksDB, if ComparerName is set we always encode
   380  		// LastSeqNum.
   381  		{VersionEdit{ComparerName: "foo", LastSeqNum: 0}, "\x01\x03\x66\x6f\x6f\x04\x00"},
   382  		{VersionEdit{ComparerName: "foo", LastSeqNum: 1}, "\x01\x03\x66\x6f\x6f\x04\x01"},
   383  	}
   384  	for _, c := range testCases {
   385  		t.Run("", func(t *testing.T) {
   386  			var buf bytes.Buffer
   387  			require.NoError(t, c.edit.Encode(&buf))
   388  			if result := buf.String(); c.encoded != result {
   389  				t.Fatalf("expected %x, but found %x", c.encoded, result)
   390  			}
   391  
   392  			if c.edit.ComparerName != "" {
   393  				// Manually decode the version edit so that we can verify the contents
   394  				// even if the LastSeqNum decodes to 0.
   395  				d := versionEditDecoder{strings.NewReader(c.encoded)}
   396  
   397  				// Decode ComparerName.
   398  				tag, err := d.readUvarint()
   399  				require.NoError(t, err)
   400  				if tag != tagComparator {
   401  					t.Fatalf("expected %d, but found %d", tagComparator, tag)
   402  				}
   403  				s, err := d.readBytes()
   404  				require.NoError(t, err)
   405  				if c.edit.ComparerName != string(s) {
   406  					t.Fatalf("expected %q, but found %q", c.edit.ComparerName, s)
   407  				}
   408  
   409  				// Decode LastSeqNum.
   410  				tag, err = d.readUvarint()
   411  				require.NoError(t, err)
   412  				if tag != tagLastSequence {
   413  					t.Fatalf("expected %d, but found %d", tagLastSequence, tag)
   414  				}
   415  				val, err := d.readUvarint()
   416  				require.NoError(t, err)
   417  				if c.edit.LastSeqNum != val {
   418  					t.Fatalf("expected %d, but found %d", c.edit.LastSeqNum, val)
   419  				}
   420  			}
   421  		})
   422  	}
   423  }
   424  
   425  func TestVersionEditApply(t *testing.T) {
   426  	parseMeta := func(s string) (*FileMetadata, error) {
   427  		m, err := ParseFileMetadataDebug(s)
   428  		if err != nil {
   429  			return nil, err
   430  		}
   431  		m.SmallestSeqNum = m.Smallest.SeqNum()
   432  		m.LargestSeqNum = m.Largest.SeqNum()
   433  		if m.SmallestSeqNum > m.LargestSeqNum {
   434  			m.SmallestSeqNum, m.LargestSeqNum = m.LargestSeqNum, m.SmallestSeqNum
   435  		}
   436  		m.InitPhysicalBacking()
   437  		return m, nil
   438  	}
   439  
   440  	// TODO(bananabrick): Improve the parsing logic in this test.
   441  	datadriven.RunTest(t, "testdata/version_edit_apply",
   442  		func(t *testing.T, d *datadriven.TestData) string {
   443  			switch d.Cmd {
   444  			case "apply":
   445  				// TODO(sumeer): move this Version parsing code to utils, to
   446  				// avoid repeating it, and make it the inverse of
   447  				// Version.DebugString().
   448  				var v *Version
   449  				var veList []*VersionEdit
   450  				isVersion := true
   451  				isDelete := true
   452  				var level int
   453  				var err error
   454  				versionFiles := map[base.FileNum]*FileMetadata{}
   455  				for _, data := range strings.Split(d.Input, "\n") {
   456  					data = strings.TrimSpace(data)
   457  					switch data {
   458  					case "edit":
   459  						isVersion = false
   460  						veList = append(veList, &VersionEdit{})
   461  					case "delete":
   462  						isVersion = false
   463  						isDelete = true
   464  					case "add":
   465  						isVersion = false
   466  						isDelete = false
   467  					case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
   468  						level, err = strconv.Atoi(data[1:])
   469  						if err != nil {
   470  							return err.Error()
   471  						}
   472  					default:
   473  						var ve *VersionEdit
   474  						if len(veList) > 0 {
   475  							ve = veList[len(veList)-1]
   476  						}
   477  						if isVersion || !isDelete {
   478  							meta, err := parseMeta(data)
   479  							if err != nil {
   480  								return err.Error()
   481  							}
   482  							if isVersion {
   483  								if v == nil {
   484  									v = new(Version)
   485  									for l := 0; l < NumLevels; l++ {
   486  										v.Levels[l] = makeLevelMetadata(base.DefaultComparer.Compare, l, nil /* files */)
   487  									}
   488  								}
   489  								versionFiles[meta.FileNum] = meta
   490  								v.Levels[level].insert(meta)
   491  								meta.LatestRef()
   492  							} else {
   493  								ve.NewFiles =
   494  									append(ve.NewFiles, NewFileEntry{Level: level, Meta: meta})
   495  							}
   496  						} else {
   497  							fileNum, err := strconv.Atoi(data)
   498  							if err != nil {
   499  								return err.Error()
   500  							}
   501  							dfe := DeletedFileEntry{Level: level, FileNum: base.FileNum(fileNum)}
   502  							if ve.DeletedFiles == nil {
   503  								ve.DeletedFiles = make(map[DeletedFileEntry]*FileMetadata)
   504  							}
   505  							ve.DeletedFiles[dfe] = versionFiles[dfe.FileNum]
   506  						}
   507  					}
   508  				}
   509  
   510  				if v != nil {
   511  					if err := v.InitL0Sublevels(base.DefaultComparer.Compare, base.DefaultFormatter, 10<<20); err != nil {
   512  						return err.Error()
   513  					}
   514  				}
   515  
   516  				bve := BulkVersionEdit{}
   517  				bve.AddedByFileNum = make(map[base.FileNum]*FileMetadata)
   518  				for _, ve := range veList {
   519  					if err := bve.Accumulate(ve); err != nil {
   520  						return err.Error()
   521  					}
   522  				}
   523  				zombies := make(map[base.DiskFileNum]uint64)
   524  				newv, err := bve.Apply(v, base.DefaultComparer.Compare, base.DefaultFormatter, 10<<20, 32000, zombies, ProhibitSplitUserKeys)
   525  				if err != nil {
   526  					return err.Error()
   527  				}
   528  
   529  				zombieFileNums := make([]base.DiskFileNum, 0, len(zombies))
   530  				if len(veList) == 1 {
   531  					// Only care about zombies if a single version edit was
   532  					// being applied.
   533  					for fileNum := range zombies {
   534  						zombieFileNums = append(zombieFileNums, fileNum)
   535  					}
   536  					slices.Sort(zombieFileNums)
   537  				}
   538  
   539  				return fmt.Sprintf("%szombies %d\n", newv, zombieFileNums)
   540  
   541  			default:
   542  				return fmt.Sprintf("unknown command: %s", d.Cmd)
   543  			}
   544  		})
   545  }