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 }