github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/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 "sort" 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 cmp := base.DefaultComparer.Compare 254 m := (&FileMetadata{ 255 FileNum: 4, 256 Size: 986, 257 SmallestSeqNum: 3, 258 LargestSeqNum: 5, 259 }).ExtendPointKeyBounds( 260 cmp, 261 base.MakeInternalKey([]byte("bar"), 5, base.InternalKeyKindDelete), 262 base.MakeInternalKey([]byte("foo"), 4, base.InternalKeyKindSet), 263 ) 264 m.InitPhysicalBacking() 265 266 testCases := []struct { 267 filename string 268 encodedEdits []string 269 edits []VersionEdit 270 }{ 271 // db-stage-1 and db-stage-2 have the same manifest. 272 { 273 filename: "db-stage-1/MANIFEST-000001", 274 encodedEdits: []string{ 275 "\x02\x00\x03\x02\x04\x00", 276 }, 277 edits: []VersionEdit{ 278 { 279 NextFileNum: 2, 280 }, 281 }, 282 }, 283 // db-stage-3 and db-stage-4 have the same manifest. 284 { 285 filename: "db-stage-3/MANIFEST-000005", 286 encodedEdits: []string{ 287 "\x01\x1aleveldb.BytewiseComparator", 288 "\x02\x00", 289 "\x02\x04\t\x00\x03\x06\x04\x05d\x00\x04\xda\a\vbar" + 290 "\x00\x05\x00\x00\x00\x00\x00\x00\vfoo\x01\x04\x00" + 291 "\x00\x00\x00\x00\x00\x03\x05", 292 }, 293 edits: []VersionEdit{ 294 { 295 ComparerName: "leveldb.BytewiseComparator", 296 }, 297 {}, 298 { 299 MinUnflushedLogNum: 4, 300 ObsoletePrevLogNum: 0, 301 NextFileNum: 6, 302 LastSeqNum: 5, 303 NewFiles: []NewFileEntry{ 304 { 305 Level: 0, 306 Meta: m, 307 }, 308 }, 309 }, 310 }, 311 }, 312 } 313 314 for _, tc := range testCases { 315 t.Run("", func(t *testing.T) { 316 f, err := os.Open("../../testdata/" + tc.filename) 317 if err != nil { 318 t.Fatalf("filename=%q: open error: %v", tc.filename, err) 319 } 320 defer f.Close() 321 i, r := 0, record.NewReader(f, 0 /* logNum */) 322 for { 323 rr, err := r.Next() 324 if err == io.EOF { 325 break 326 } 327 if err != nil { 328 t.Fatalf("filename=%q i=%d: record reader error: %v", tc.filename, i, err) 329 } 330 if i >= len(tc.edits) { 331 t.Fatalf("filename=%q i=%d: too many version edits", tc.filename, i+1) 332 } 333 334 encodedEdit, err := io.ReadAll(rr) 335 if err != nil { 336 t.Fatalf("filename=%q i=%d: read error: %v", tc.filename, i, err) 337 } 338 if s := string(encodedEdit); s != tc.encodedEdits[i] { 339 t.Fatalf("filename=%q i=%d: got encoded %q, want %q", tc.filename, i, s, tc.encodedEdits[i]) 340 } 341 342 var edit VersionEdit 343 err = edit.Decode(bytes.NewReader(encodedEdit)) 344 if err != nil { 345 t.Fatalf("filename=%q i=%d: decode error: %v", tc.filename, i, err) 346 } 347 if !reflect.DeepEqual(edit, tc.edits[i]) { 348 t.Fatalf("filename=%q i=%d: decode\n\tgot %#v\n\twant %#v\n%s", tc.filename, i, edit, tc.edits[i], 349 strings.Join(pretty.Diff(edit, tc.edits[i]), "\n")) 350 } 351 if err := checkRoundTrip(edit); err != nil { 352 t.Fatalf("filename=%q i=%d: round trip: %v", tc.filename, i, err) 353 } 354 355 i++ 356 } 357 if i != len(tc.edits) { 358 t.Fatalf("filename=%q: got %d edits, want %d", tc.filename, i, len(tc.edits)) 359 } 360 }) 361 } 362 } 363 364 func TestVersionEditEncodeLastSeqNum(t *testing.T) { 365 testCases := []struct { 366 edit VersionEdit 367 encoded string 368 }{ 369 // If ComparerName is unset, LastSeqNum is only encoded if non-zero. 370 {VersionEdit{LastSeqNum: 0}, ""}, 371 {VersionEdit{LastSeqNum: 1}, "\x04\x01"}, 372 // For compatibility with RocksDB, if ComparerName is set we always encode 373 // LastSeqNum. 374 {VersionEdit{ComparerName: "foo", LastSeqNum: 0}, "\x01\x03\x66\x6f\x6f\x04\x00"}, 375 {VersionEdit{ComparerName: "foo", LastSeqNum: 1}, "\x01\x03\x66\x6f\x6f\x04\x01"}, 376 } 377 for _, c := range testCases { 378 t.Run("", func(t *testing.T) { 379 var buf bytes.Buffer 380 require.NoError(t, c.edit.Encode(&buf)) 381 if result := buf.String(); c.encoded != result { 382 t.Fatalf("expected %x, but found %x", c.encoded, result) 383 } 384 385 if c.edit.ComparerName != "" { 386 // Manually decode the version edit so that we can verify the contents 387 // even if the LastSeqNum decodes to 0. 388 d := versionEditDecoder{strings.NewReader(c.encoded)} 389 390 // Decode ComparerName. 391 tag, err := d.readUvarint() 392 require.NoError(t, err) 393 if tag != tagComparator { 394 t.Fatalf("expected %d, but found %d", tagComparator, tag) 395 } 396 s, err := d.readBytes() 397 require.NoError(t, err) 398 if c.edit.ComparerName != string(s) { 399 t.Fatalf("expected %q, but found %q", c.edit.ComparerName, s) 400 } 401 402 // Decode LastSeqNum. 403 tag, err = d.readUvarint() 404 require.NoError(t, err) 405 if tag != tagLastSequence { 406 t.Fatalf("expected %d, but found %d", tagLastSequence, tag) 407 } 408 val, err := d.readUvarint() 409 require.NoError(t, err) 410 if c.edit.LastSeqNum != val { 411 t.Fatalf("expected %d, but found %d", c.edit.LastSeqNum, val) 412 } 413 } 414 }) 415 } 416 } 417 418 func TestVersionEditApply(t *testing.T) { 419 parseMeta := func(s string) (*FileMetadata, error) { 420 m, err := ParseFileMetadataDebug(s) 421 if err != nil { 422 return nil, err 423 } 424 m.SmallestSeqNum = m.Smallest.SeqNum() 425 m.LargestSeqNum = m.Largest.SeqNum() 426 if m.SmallestSeqNum > m.LargestSeqNum { 427 m.SmallestSeqNum, m.LargestSeqNum = m.LargestSeqNum, m.SmallestSeqNum 428 } 429 m.InitPhysicalBacking() 430 return m, nil 431 } 432 433 // TODO(bananabrick): Improve the parsing logic in this test. 434 datadriven.RunTest(t, "testdata/version_edit_apply", 435 func(t *testing.T, d *datadriven.TestData) string { 436 switch d.Cmd { 437 case "apply": 438 // TODO(sumeer): move this Version parsing code to utils, to 439 // avoid repeating it, and make it the inverse of 440 // Version.DebugString(). 441 var v *Version 442 var veList []*VersionEdit 443 isVersion := true 444 isDelete := true 445 var level int 446 var err error 447 versionFiles := map[base.FileNum]*FileMetadata{} 448 for _, data := range strings.Split(d.Input, "\n") { 449 data = strings.TrimSpace(data) 450 switch data { 451 case "edit": 452 isVersion = false 453 veList = append(veList, &VersionEdit{}) 454 case "delete": 455 isVersion = false 456 isDelete = true 457 case "add": 458 isVersion = false 459 isDelete = false 460 case "L0", "L1", "L2", "L3", "L4", "L5", "L6": 461 level, err = strconv.Atoi(data[1:]) 462 if err != nil { 463 return err.Error() 464 } 465 default: 466 var ve *VersionEdit 467 if len(veList) > 0 { 468 ve = veList[len(veList)-1] 469 } 470 if isVersion || !isDelete { 471 meta, err := parseMeta(data) 472 if err != nil { 473 return err.Error() 474 } 475 if isVersion { 476 if v == nil { 477 v = new(Version) 478 for l := 0; l < NumLevels; l++ { 479 v.Levels[l] = makeLevelMetadata(base.DefaultComparer.Compare, l, nil /* files */) 480 } 481 } 482 versionFiles[meta.FileNum] = meta 483 v.Levels[level].insert(meta) 484 meta.LatestRef() 485 } else { 486 ve.NewFiles = 487 append(ve.NewFiles, NewFileEntry{Level: level, Meta: meta}) 488 } 489 } else { 490 fileNum, err := strconv.Atoi(data) 491 if err != nil { 492 return err.Error() 493 } 494 dfe := DeletedFileEntry{Level: level, FileNum: base.FileNum(fileNum)} 495 if ve.DeletedFiles == nil { 496 ve.DeletedFiles = make(map[DeletedFileEntry]*FileMetadata) 497 } 498 ve.DeletedFiles[dfe] = versionFiles[dfe.FileNum] 499 } 500 } 501 } 502 503 if v != nil { 504 if err := v.InitL0Sublevels(base.DefaultComparer.Compare, base.DefaultFormatter, 10<<20); err != nil { 505 return err.Error() 506 } 507 } 508 509 bve := BulkVersionEdit{} 510 bve.AddedByFileNum = make(map[base.FileNum]*FileMetadata) 511 for _, ve := range veList { 512 if err := bve.Accumulate(ve); err != nil { 513 return err.Error() 514 } 515 } 516 zombies := make(map[base.DiskFileNum]uint64) 517 newv, err := bve.Apply(v, base.DefaultComparer.Compare, base.DefaultFormatter, 10<<20, 32000, zombies, ProhibitSplitUserKeys) 518 if err != nil { 519 return err.Error() 520 } 521 522 zombieFileNums := make([]base.DiskFileNum, 0, len(zombies)) 523 if len(veList) == 1 { 524 // Only care about zombies if a single version edit was 525 // being applied. 526 for fileNum := range zombies { 527 zombieFileNums = append(zombieFileNums, fileNum) 528 } 529 sort.Slice(zombieFileNums, func(i, j int) bool { 530 return zombieFileNums[i].FileNum() < zombieFileNums[j].FileNum() 531 }) 532 } 533 534 return fmt.Sprintf("%szombies %d\n", newv, zombieFileNums) 535 536 default: 537 return fmt.Sprintf("unknown command: %s", d.Cmd) 538 } 539 }) 540 }