-
Notifications
You must be signed in to change notification settings - Fork 422
/
version_edit.go
1121 lines (1044 loc) · 35.8 KB
/
version_edit.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2012 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
package manifest
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"io"
"sort"
"time"
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/invariants"
)
// TODO(peter): describe the MANIFEST file format, independently of the C++
// project.
var errCorruptManifest = base.CorruptionErrorf("pebble: corrupt manifest")
type byteReader interface {
io.ByteReader
io.Reader
}
// Tags for the versionEdit disk format.
// Tag 8 is no longer used.
const (
// LevelDB tags.
tagComparator = 1
tagLogNumber = 2
tagNextFileNumber = 3
tagLastSequence = 4
tagCompactPointer = 5
tagDeletedFile = 6
tagNewFile = 7
tagPrevLogNumber = 9
// RocksDB tags.
tagNewFile2 = 100
tagNewFile3 = 102
tagNewFile4 = 103
tagColumnFamily = 200
tagColumnFamilyAdd = 201
tagColumnFamilyDrop = 202
tagMaxColumnFamily = 203
// Pebble tags.
tagNewFile5 = 104 // Range keys.
tagCreatedBackingTable = 105
tagRemovedBackingTable = 106
// The custom tags sub-format used by tagNewFile4 and above.
customTagTerminate = 1
customTagNeedsCompaction = 2
customTagCreationTime = 6
customTagPathID = 65
customTagNonSafeIgnoreMask = 1 << 6
customTagVirtual = 66
)
// DeletedFileEntry holds the state for a file deletion from a level. The file
// itself might still be referenced by another level.
type DeletedFileEntry struct {
Level int
FileNum base.FileNum
}
// NewFileEntry holds the state for a new file or one moved from a different
// level.
type NewFileEntry struct {
Level int
Meta *FileMetadata
// BackingFileNum is only set during manifest replay, and only for virtual
// sstables.
BackingFileNum base.DiskFileNum
}
// VersionEdit holds the state for an edit to a Version along with other
// on-disk state (log numbers, next file number, and the last sequence number).
type VersionEdit struct {
// ComparerName is the value of Options.Comparer.Name. This is only set in
// the first VersionEdit in a manifest (either when the DB is created, or
// when a new manifest is created) and is used to verify that the comparer
// specified at Open matches the comparer that was previously used.
ComparerName string
// MinUnflushedLogNum is the smallest WAL log file number corresponding to
// mutations that have not been flushed to an sstable.
//
// This is an optional field, and 0 represents it is not set.
MinUnflushedLogNum base.FileNum
// ObsoletePrevLogNum is a historic artifact from LevelDB that is not used by
// Pebble, RocksDB, or even LevelDB. Its use in LevelDB was deprecated in
// 6/2011. We keep it around purely for informational purposes when
// displaying MANIFEST contents.
ObsoletePrevLogNum uint64
// The next file number. A single counter is used to assign file numbers
// for the WAL, MANIFEST, sstable, and OPTIONS files.
NextFileNum base.FileNum
// LastSeqNum is an upper bound on the sequence numbers that have been
// assigned in flushed WALs. Unflushed WALs (that will be replayed during
// recovery) may contain sequence numbers greater than this value.
LastSeqNum uint64
// A file num may be present in both deleted files and new files when it
// is moved from a lower level to a higher level (when the compaction
// found that there was no overlapping file at the higher level).
DeletedFiles map[DeletedFileEntry]*FileMetadata
NewFiles []NewFileEntry
// CreatedBackingTables can be used to preserve the FileBacking associated
// with a physical sstable. This is useful when virtual sstables in the
// latest version are reconstructed during manifest replay, and we also need
// to reconstruct the FileBacking which is required by these virtual
// sstables.
//
// INVARIANT: The FileBacking associated with a physical sstable must only
// be added as a backing file in the same version edit where the physical
// sstable is first virtualized. This means that the physical sstable must
// be present in DeletedFiles and that there must be at least one virtual
// sstable with the same FileBacking as the physical sstable in NewFiles. A
// file must be present in CreatedBackingTables in exactly one version edit.
// The physical sstable associated with the FileBacking must also not be
// present in NewFiles.
CreatedBackingTables []*FileBacking
// RemovedBackingTables is used to remove the FileBacking associated with a
// virtual sstable. Note that a backing sstable can be removed as soon as
// there are no virtual sstables in the latest version which are using the
// backing sstable, but the backing sstable doesn't necessarily have to be
// removed atomically with the version edit which removes the last virtual
// sstable associated with the backing sstable. The removal can happen in a
// future version edit.
//
// INVARIANT: A file must only be added to RemovedBackingTables if it was
// added to CreateBackingTables in a prior version edit. The same version
// edit also cannot have the same file present in both CreateBackingTables
// and RemovedBackingTables. A file must be present in RemovedBackingTables
// in exactly one version edit.
RemovedBackingTables []base.DiskFileNum
}
// Decode decodes an edit from the specified reader.
//
// Note that the Decode step will not set the FileBacking for virtual sstables
// and the responsibility is left to the caller. However, the Decode step will
// populate the NewFileEntry.BackingFileNum in VersionEdit.NewFiles.
func (v *VersionEdit) Decode(r io.Reader) error {
br, ok := r.(byteReader)
if !ok {
br = bufio.NewReader(r)
}
d := versionEditDecoder{br}
for {
tag, err := binary.ReadUvarint(br)
if err == io.EOF {
break
}
if err != nil {
return err
}
switch tag {
case tagComparator:
s, err := d.readBytes()
if err != nil {
return err
}
v.ComparerName = string(s)
case tagLogNumber:
n, err := d.readFileNum()
if err != nil {
return err
}
v.MinUnflushedLogNum = n
case tagNextFileNumber:
n, err := d.readFileNum()
if err != nil {
return err
}
v.NextFileNum = n
case tagLastSequence:
n, err := d.readUvarint()
if err != nil {
return err
}
v.LastSeqNum = n
case tagCompactPointer:
if _, err := d.readLevel(); err != nil {
return err
}
if _, err := d.readBytes(); err != nil {
return err
}
// NB: RocksDB does not use compaction pointers anymore.
case tagRemovedBackingTable:
n, err := d.readUvarint()
if err != nil {
return err
}
v.RemovedBackingTables = append(
v.RemovedBackingTables, base.FileNum(n).DiskFileNum(),
)
case tagCreatedBackingTable:
dfn, err := d.readUvarint()
if err != nil {
return err
}
size, err := d.readUvarint()
if err != nil {
return err
}
fileBacking := &FileBacking{
DiskFileNum: base.FileNum(dfn).DiskFileNum(),
Size: size,
}
v.CreatedBackingTables = append(v.CreatedBackingTables, fileBacking)
case tagDeletedFile:
level, err := d.readLevel()
if err != nil {
return err
}
fileNum, err := d.readFileNum()
if err != nil {
return err
}
if v.DeletedFiles == nil {
v.DeletedFiles = make(map[DeletedFileEntry]*FileMetadata)
}
v.DeletedFiles[DeletedFileEntry{level, fileNum}] = nil
case tagNewFile, tagNewFile2, tagNewFile3, tagNewFile4, tagNewFile5:
level, err := d.readLevel()
if err != nil {
return err
}
fileNum, err := d.readFileNum()
if err != nil {
return err
}
if tag == tagNewFile3 {
// The pathID field appears unused in RocksDB.
_ /* pathID */, err := d.readUvarint()
if err != nil {
return err
}
}
size, err := d.readUvarint()
if err != nil {
return err
}
// We read the smallest / largest key bounds differently depending on
// whether we have point, range or both types of keys present in the
// table.
var (
smallestPointKey, largestPointKey []byte
smallestRangeKey, largestRangeKey []byte
parsedPointBounds bool
boundsMarker byte
)
if tag != tagNewFile5 {
// Range keys not present in the table. Parse the point key bounds.
smallestPointKey, err = d.readBytes()
if err != nil {
return err
}
largestPointKey, err = d.readBytes()
if err != nil {
return err
}
} else {
// Range keys are present in the table. Determine whether we have point
// keys to parse, in addition to the bounds.
boundsMarker, err = d.ReadByte()
if err != nil {
return err
}
// Parse point key bounds, if present.
if boundsMarker&maskContainsPointKeys > 0 {
smallestPointKey, err = d.readBytes()
if err != nil {
return err
}
largestPointKey, err = d.readBytes()
if err != nil {
return err
}
parsedPointBounds = true
} else {
// The table does not have point keys.
// Sanity check: the bounds must be range keys.
if boundsMarker&maskSmallest != 0 || boundsMarker&maskLargest != 0 {
return base.CorruptionErrorf(
"new-file-4-range-keys: table without point keys has point key bounds: marker=%x",
boundsMarker,
)
}
}
// Parse range key bounds.
smallestRangeKey, err = d.readBytes()
if err != nil {
return err
}
largestRangeKey, err = d.readBytes()
if err != nil {
return err
}
}
var smallestSeqNum uint64
var largestSeqNum uint64
if tag != tagNewFile {
smallestSeqNum, err = d.readUvarint()
if err != nil {
return err
}
largestSeqNum, err = d.readUvarint()
if err != nil {
return err
}
}
var markedForCompaction bool
var creationTime uint64
virtualState := struct {
virtual bool
backingFileNum uint64
}{}
if tag == tagNewFile4 || tag == tagNewFile5 {
for {
customTag, err := d.readUvarint()
if err != nil {
return err
}
if customTag == customTagTerminate {
break
} else if customTag == customTagVirtual {
virtualState.virtual = true
n, err := d.readUvarint()
if err != nil {
return err
}
virtualState.backingFileNum = n
continue
}
field, err := d.readBytes()
if err != nil {
return err
}
switch customTag {
case customTagNeedsCompaction:
if len(field) != 1 {
return base.CorruptionErrorf("new-file4: need-compaction field wrong size")
}
markedForCompaction = (field[0] == 1)
case customTagCreationTime:
var n int
creationTime, n = binary.Uvarint(field)
if n != len(field) {
return base.CorruptionErrorf("new-file4: invalid file creation time")
}
case customTagPathID:
return base.CorruptionErrorf("new-file4: path-id field not supported")
default:
if (customTag & customTagNonSafeIgnoreMask) != 0 {
return base.CorruptionErrorf("new-file4: custom field not supported: %d", customTag)
}
}
}
}
m := &FileMetadata{
FileNum: fileNum,
Size: size,
CreationTime: int64(creationTime),
SmallestSeqNum: smallestSeqNum,
LargestSeqNum: largestSeqNum,
MarkedForCompaction: markedForCompaction,
Virtual: virtualState.virtual,
}
if tag != tagNewFile5 { // no range keys present
m.SmallestPointKey = base.DecodeInternalKey(smallestPointKey)
m.LargestPointKey = base.DecodeInternalKey(largestPointKey)
m.HasPointKeys = true
m.Smallest, m.Largest = m.SmallestPointKey, m.LargestPointKey
m.boundTypeSmallest, m.boundTypeLargest = boundTypePointKey, boundTypePointKey
} else { // range keys present
// Set point key bounds, if parsed.
if parsedPointBounds {
m.SmallestPointKey = base.DecodeInternalKey(smallestPointKey)
m.LargestPointKey = base.DecodeInternalKey(largestPointKey)
m.HasPointKeys = true
}
// Set range key bounds.
m.SmallestRangeKey = base.DecodeInternalKey(smallestRangeKey)
m.LargestRangeKey = base.DecodeInternalKey(largestRangeKey)
m.HasRangeKeys = true
// Set overall bounds (by default assume range keys).
m.Smallest, m.Largest = m.SmallestRangeKey, m.LargestRangeKey
m.boundTypeSmallest, m.boundTypeLargest = boundTypeRangeKey, boundTypeRangeKey
if boundsMarker&maskSmallest == maskSmallest {
m.Smallest = m.SmallestPointKey
m.boundTypeSmallest = boundTypePointKey
}
if boundsMarker&maskLargest == maskLargest {
m.Largest = m.LargestPointKey
m.boundTypeLargest = boundTypePointKey
}
}
m.boundsSet = true
if !virtualState.virtual {
m.InitPhysicalBacking()
}
nfe := NewFileEntry{
Level: level,
Meta: m,
}
if virtualState.virtual {
nfe.BackingFileNum = base.FileNum(virtualState.backingFileNum).DiskFileNum()
}
v.NewFiles = append(v.NewFiles, nfe)
case tagPrevLogNumber:
n, err := d.readUvarint()
if err != nil {
return err
}
v.ObsoletePrevLogNum = n
case tagColumnFamily, tagColumnFamilyAdd, tagColumnFamilyDrop, tagMaxColumnFamily:
return base.CorruptionErrorf("column families are not supported")
default:
return errCorruptManifest
}
}
return nil
}
func (v *VersionEdit) string(verbose bool, fmtKey base.FormatKey) string {
var buf bytes.Buffer
if v.ComparerName != "" {
fmt.Fprintf(&buf, " comparer: %s", v.ComparerName)
}
if v.MinUnflushedLogNum != 0 {
fmt.Fprintf(&buf, " log-num: %d\n", v.MinUnflushedLogNum)
}
if v.ObsoletePrevLogNum != 0 {
fmt.Fprintf(&buf, " prev-log-num: %d\n", v.ObsoletePrevLogNum)
}
if v.NextFileNum != 0 {
fmt.Fprintf(&buf, " next-file-num: %d\n", v.NextFileNum)
}
if v.LastSeqNum != 0 {
fmt.Fprintf(&buf, " last-seq-num: %d\n", v.LastSeqNum)
}
entries := make([]DeletedFileEntry, 0, len(v.DeletedFiles))
for df := range v.DeletedFiles {
entries = append(entries, df)
}
sort.Slice(entries, func(i, j int) bool {
if entries[i].Level != entries[j].Level {
return entries[i].Level < entries[j].Level
}
return entries[i].FileNum < entries[j].FileNum
})
for _, df := range entries {
fmt.Fprintf(&buf, " deleted: L%d %s\n", df.Level, df.FileNum)
}
for _, nf := range v.NewFiles {
fmt.Fprintf(&buf, " added: L%d", nf.Level)
if verbose {
fmt.Fprintf(&buf, " %s", nf.Meta.DebugString(fmtKey, true /* verbose */))
} else {
fmt.Fprintf(&buf, " %s", nf.Meta.String())
}
if nf.Meta.CreationTime != 0 {
fmt.Fprintf(&buf, " (%s)",
time.Unix(nf.Meta.CreationTime, 0).UTC().Format(time.RFC3339))
}
fmt.Fprintln(&buf)
}
return buf.String()
}
// DebugString is a more verbose version of String(). Use this in tests.
func (v *VersionEdit) DebugString(fmtKey base.FormatKey) string {
return v.string(true /* verbose */, fmtKey)
}
// String implements fmt.Stringer for a VersionEdit.
func (v *VersionEdit) String() string {
return v.string(false /* verbose */, base.DefaultFormatter)
}
// Encode encodes an edit to the specified writer.
func (v *VersionEdit) Encode(w io.Writer) error {
e := versionEditEncoder{new(bytes.Buffer)}
if v.ComparerName != "" {
e.writeUvarint(tagComparator)
e.writeString(v.ComparerName)
}
if v.MinUnflushedLogNum != 0 {
e.writeUvarint(tagLogNumber)
e.writeUvarint(uint64(v.MinUnflushedLogNum))
}
if v.ObsoletePrevLogNum != 0 {
e.writeUvarint(tagPrevLogNumber)
e.writeUvarint(v.ObsoletePrevLogNum)
}
if v.NextFileNum != 0 {
e.writeUvarint(tagNextFileNumber)
e.writeUvarint(uint64(v.NextFileNum))
}
for _, dfn := range v.RemovedBackingTables {
e.writeUvarint(tagRemovedBackingTable)
e.writeUvarint(uint64(dfn.FileNum()))
}
for _, fileBacking := range v.CreatedBackingTables {
e.writeUvarint(tagCreatedBackingTable)
e.writeUvarint(uint64(fileBacking.DiskFileNum.FileNum()))
e.writeUvarint(fileBacking.Size)
}
// RocksDB requires LastSeqNum to be encoded for the first MANIFEST entry,
// even though its value is zero. We detect this by encoding LastSeqNum when
// ComparerName is set.
if v.LastSeqNum != 0 || v.ComparerName != "" {
e.writeUvarint(tagLastSequence)
e.writeUvarint(v.LastSeqNum)
}
for x := range v.DeletedFiles {
e.writeUvarint(tagDeletedFile)
e.writeUvarint(uint64(x.Level))
e.writeUvarint(uint64(x.FileNum))
}
for _, x := range v.NewFiles {
customFields := x.Meta.MarkedForCompaction || x.Meta.CreationTime != 0 || x.Meta.Virtual
var tag uint64
switch {
case x.Meta.HasRangeKeys:
tag = tagNewFile5
case customFields:
tag = tagNewFile4
default:
tag = tagNewFile2
}
e.writeUvarint(tag)
e.writeUvarint(uint64(x.Level))
e.writeUvarint(uint64(x.Meta.FileNum))
e.writeUvarint(x.Meta.Size)
if !x.Meta.HasRangeKeys {
// If we have no range keys, preserve the original format and write the
// smallest and largest point keys.
e.writeKey(x.Meta.SmallestPointKey)
e.writeKey(x.Meta.LargestPointKey)
} else {
// When range keys are present, we first write a marker byte that
// indicates if the table also contains point keys, in addition to how the
// overall bounds for the table should be reconstructed. This byte is
// followed by the keys themselves.
b, err := x.Meta.boundsMarker()
if err != nil {
return err
}
if err = e.WriteByte(b); err != nil {
return err
}
// Write point key bounds (if present).
if x.Meta.HasPointKeys {
e.writeKey(x.Meta.SmallestPointKey)
e.writeKey(x.Meta.LargestPointKey)
}
// Write range key bounds.
e.writeKey(x.Meta.SmallestRangeKey)
e.writeKey(x.Meta.LargestRangeKey)
}
e.writeUvarint(x.Meta.SmallestSeqNum)
e.writeUvarint(x.Meta.LargestSeqNum)
if customFields {
if x.Meta.CreationTime != 0 {
e.writeUvarint(customTagCreationTime)
var buf [binary.MaxVarintLen64]byte
n := binary.PutUvarint(buf[:], uint64(x.Meta.CreationTime))
e.writeBytes(buf[:n])
}
if x.Meta.MarkedForCompaction {
e.writeUvarint(customTagNeedsCompaction)
e.writeBytes([]byte{1})
}
if x.Meta.Virtual {
e.writeUvarint(customTagVirtual)
e.writeUvarint(uint64(x.Meta.FileBacking.DiskFileNum.FileNum()))
}
e.writeUvarint(customTagTerminate)
}
}
_, err := w.Write(e.Bytes())
return err
}
// versionEditDecoder should be used to decode version edits.
type versionEditDecoder struct {
byteReader
}
func (d versionEditDecoder) readBytes() ([]byte, error) {
n, err := d.readUvarint()
if err != nil {
return nil, err
}
s := make([]byte, n)
_, err = io.ReadFull(d, s)
if err != nil {
if err == io.ErrUnexpectedEOF {
return nil, errCorruptManifest
}
return nil, err
}
return s, nil
}
func (d versionEditDecoder) readLevel() (int, error) {
u, err := d.readUvarint()
if err != nil {
return 0, err
}
if u >= NumLevels {
return 0, errCorruptManifest
}
return int(u), nil
}
func (d versionEditDecoder) readFileNum() (base.FileNum, error) {
u, err := d.readUvarint()
if err != nil {
return 0, err
}
return base.FileNum(u), nil
}
func (d versionEditDecoder) readUvarint() (uint64, error) {
u, err := binary.ReadUvarint(d)
if err != nil {
if err == io.EOF {
return 0, errCorruptManifest
}
return 0, err
}
return u, nil
}
type versionEditEncoder struct {
*bytes.Buffer
}
func (e versionEditEncoder) writeBytes(p []byte) {
e.writeUvarint(uint64(len(p)))
e.Write(p)
}
func (e versionEditEncoder) writeKey(k InternalKey) {
e.writeUvarint(uint64(k.Size()))
e.Write(k.UserKey)
buf := k.EncodeTrailer()
e.Write(buf[:])
}
func (e versionEditEncoder) writeString(s string) {
e.writeUvarint(uint64(len(s)))
e.WriteString(s)
}
func (e versionEditEncoder) writeUvarint(u uint64) {
var buf [binary.MaxVarintLen64]byte
n := binary.PutUvarint(buf[:], u)
e.Write(buf[:n])
}
// BulkVersionEdit summarizes the files added and deleted from a set of version
// edits.
//
// INVARIANTS:
// No file can be added to a level more than once. This is true globally, and
// also true for all of the calls to Accumulate for a single bulk version edit.
//
// No file can be removed from a level more than once. This is true globally,
// and also true for all of the calls to Accumulate for a single bulk version
// edit.
//
// A file must not be added and removed from a given level in the same version
// edit.
//
// A file that is being removed from a level must have been added to that level
// before (in a prior version edit). Note that a given file can be deleted from
// a level and added to another level in a single version edit
type BulkVersionEdit struct {
Added [NumLevels]map[base.FileNum]*FileMetadata
Deleted [NumLevels]map[base.FileNum]*FileMetadata
// AddedFileBacking is a map to support lookup so that we can populate the
// FileBacking of virtual sstables during manifest replay.
AddedFileBacking map[base.DiskFileNum]*FileBacking
RemovedFileBacking []base.DiskFileNum
// AddedByFileNum maps file number to file metadata for all added files
// from accumulated version edits. AddedByFileNum is only populated if set
// to non-nil by a caller. It must be set to non-nil when replaying
// version edits read from a MANIFEST (as opposed to VersionEdits
// constructed in-memory). While replaying a MANIFEST file,
// VersionEdit.DeletedFiles map entries have nil values, because the
// on-disk deletion record encodes only the file number. Accumulate
// uses AddedByFileNum to correctly populate the BulkVersionEdit's Deleted
// field with non-nil *FileMetadata.
AddedByFileNum map[base.FileNum]*FileMetadata
// MarkedForCompactionCountDiff holds the aggregated count of files
// marked for compaction added or removed.
MarkedForCompactionCountDiff int
}
// Accumulate adds the file addition and deletions in the specified version
// edit to the bulk edit's internal state.
//
// INVARIANTS:
// If a file is added to a given level in a call to Accumulate and then removed
// from that level in a subsequent call, the file will not be present in the
// resulting BulkVersionEdit.Deleted for that level.
//
// After accumulation of version edits, the bulk version edit may have
// information about a file which has been deleted from a level, but it may
// not have information about the same file added to the same level. The add
// could've occurred as part of a previous bulk version edit. In this case,
// the deleted file must be present in BulkVersionEdit.Deleted, at the end
// of the accumulation, because we need to decrease the refcount of the
// deleted file in Apply.
func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
for df, m := range ve.DeletedFiles {
dmap := b.Deleted[df.Level]
if dmap == nil {
dmap = make(map[base.FileNum]*FileMetadata)
b.Deleted[df.Level] = dmap
}
if m == nil {
// m is nil only when replaying a MANIFEST.
if b.AddedByFileNum == nil {
return errors.Errorf("deleted file L%d.%s's metadata is absent and bve.AddedByFileNum is nil", df.Level, df.FileNum)
}
m = b.AddedByFileNum[df.FileNum]
if m == nil {
return base.CorruptionErrorf("pebble: file deleted L%d.%s before it was inserted", df.Level, df.FileNum)
}
}
if m.MarkedForCompaction {
b.MarkedForCompactionCountDiff--
}
if _, ok := b.Added[df.Level][df.FileNum]; !ok {
dmap[df.FileNum] = m
} else {
// Present in b.Added for the same level.
delete(b.Added[df.Level], df.FileNum)
}
}
// Generate state for Added backing files. Note that these must be generated
// before we loop through the NewFiles, because we need to populate the
// FileBackings which might be used by the NewFiles loop.
if b.AddedFileBacking == nil {
b.AddedFileBacking = make(map[base.DiskFileNum]*FileBacking)
}
for _, fb := range ve.CreatedBackingTables {
if _, ok := b.AddedFileBacking[fb.DiskFileNum]; ok {
// There is already a FileBacking associated with fb.DiskFileNum.
// This should never happen. There must always be only one FileBacking
// associated with a backing sstable.
panic(fmt.Sprintf("pebble: duplicate file backing %s", fb.DiskFileNum.String()))
}
b.AddedFileBacking[fb.DiskFileNum] = fb
}
for _, nf := range ve.NewFiles {
// A new file should not have been deleted in this or a preceding
// VersionEdit at the same level (though files can move across levels).
if dmap := b.Deleted[nf.Level]; dmap != nil {
if _, ok := dmap[nf.Meta.FileNum]; ok {
return base.CorruptionErrorf("pebble: file deleted L%d.%s before it was inserted", nf.Level, nf.Meta.FileNum)
}
}
if nf.Meta.Virtual && nf.Meta.FileBacking == nil {
// FileBacking for a virtual sstable must only be nil if we're performing
// manifest replay.
nf.Meta.FileBacking = b.AddedFileBacking[nf.BackingFileNum]
if nf.Meta.FileBacking == nil {
return errors.Errorf("FileBacking for virtual sstable must not be nil")
}
} else if nf.Meta.FileBacking == nil {
return errors.Errorf("Added file L%d.%s's has no FileBacking", nf.Level, nf.Meta.FileNum)
}
if b.Added[nf.Level] == nil {
b.Added[nf.Level] = make(map[base.FileNum]*FileMetadata)
}
b.Added[nf.Level][nf.Meta.FileNum] = nf.Meta
if b.AddedByFileNum != nil {
b.AddedByFileNum[nf.Meta.FileNum] = nf.Meta
}
if nf.Meta.MarkedForCompaction {
b.MarkedForCompactionCountDiff++
}
}
// Since a file can be removed from backing files in exactly one version
// edit it is safe to just append without any de-duplication.
b.RemovedFileBacking = append(b.RemovedFileBacking, ve.RemovedBackingTables...)
return nil
}
// AccumulateIncompleteAndApplySingleVE should be called if a single version edit
// is to be applied to the provided curr Version and if the caller needs to
// update the versionSet.zombieTables map. This function exists separately from
// BulkVersionEdit.Apply because it is easier to reason about properties
// regarding BulkVersionedit.Accumulate/Apply and zombie table generation, if we
// know that exactly one version edit is being accumulated.
//
// Note that the version edit passed into this function may be incomplete
// because compactions don't have the ref counting information necessary to
// populate VersionEdit.RemovedBackingTables. This function will complete such a
// version edit by populating RemovedBackingTables.
//
// Invariant: Any file being deleted through ve must belong to the curr Version.
// We can't have a delete for some arbitrary file which does not exist in curr.
func AccumulateIncompleteAndApplySingleVE(
ve *VersionEdit,
curr *Version,
cmp Compare,
formatKey base.FormatKey,
flushSplitBytes int64,
readCompactionRate int64,
backingStateMap map[base.DiskFileNum]*FileBacking,
addBackingFunc func(*FileBacking),
removeBackingFunc func(base.DiskFileNum),
orderingInvariants OrderingInvariants,
) (_ *Version, zombies map[base.DiskFileNum]uint64, _ error) {
if len(ve.RemovedBackingTables) != 0 {
panic("pebble: invalid incomplete version edit")
}
var b BulkVersionEdit
err := b.Accumulate(ve)
if err != nil {
return nil, nil, err
}
zombies = make(map[base.DiskFileNum]uint64)
v, err := b.Apply(
curr, cmp, formatKey, flushSplitBytes, readCompactionRate, zombies, orderingInvariants,
)
if err != nil {
return nil, nil, err
}
for _, s := range b.AddedFileBacking {
addBackingFunc(s)
}
for fileNum := range zombies {
if _, ok := backingStateMap[fileNum]; ok {
// This table was backing some virtual sstable in the latest version,
// but is now a zombie. We add RemovedBackingTables entries for
// these, before the version edit is written to disk.
ve.RemovedBackingTables = append(
ve.RemovedBackingTables, fileNum,
)
removeBackingFunc(fileNum)
}
}
return v, zombies, nil
}
// Apply applies the delta b to the current version to produce a new
// version. The new version is consistent with respect to the comparer cmp.
//
// curr may be nil, which is equivalent to a pointer to a zero version.
//
// On success, if a non-nil zombies map is provided to Apply, the map is updated
// with file numbers and files sizes of deleted files. These files are
// considered zombies because they are no longer referenced by the returned
// Version, but cannot be deleted from disk as they are still in use by the
// incoming Version.
func (b *BulkVersionEdit) Apply(
curr *Version,
cmp Compare,
formatKey base.FormatKey,
flushSplitBytes int64,
readCompactionRate int64,
zombies map[base.DiskFileNum]uint64,
orderingInvariants OrderingInvariants,
) (*Version, error) {
addZombie := func(state *FileBacking) {
if zombies != nil {
zombies[state.DiskFileNum] = state.Size
}
}
removeZombie := func(state *FileBacking) {
if zombies != nil {
delete(zombies, state.DiskFileNum)
}
}
v := new(Version)
// Adjust the count of files marked for compaction.
if curr != nil {
v.Stats.MarkedForCompaction = curr.Stats.MarkedForCompaction
}
v.Stats.MarkedForCompaction += b.MarkedForCompactionCountDiff
if v.Stats.MarkedForCompaction < 0 {
return nil, base.CorruptionErrorf("pebble: version marked for compaction count negative")
}
for level := range v.Levels {
if curr == nil || curr.Levels[level].tree.root == nil {
v.Levels[level] = makeLevelMetadata(cmp, level, nil /* files */)
} else {
v.Levels[level] = curr.Levels[level].clone()
}
if curr == nil || curr.RangeKeyLevels[level].tree.root == nil {
v.RangeKeyLevels[level] = makeLevelMetadata(cmp, level, nil /* files */)
} else {
v.RangeKeyLevels[level] = curr.RangeKeyLevels[level].clone()
}
if len(b.Added[level]) == 0 && len(b.Deleted[level]) == 0 {
// There are no edits on this level.
if level == 0 {
// Initialize L0Sublevels.
if curr == nil || curr.L0Sublevels == nil {
if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
return nil, errors.Wrap(err, "pebble: internal error")
}
} else {
v.L0Sublevels = curr.L0Sublevels
v.L0SublevelFiles = v.L0Sublevels.Levels
}
}
continue
}
// Some edits on this level.
lm := &v.Levels[level]
lmRange := &v.RangeKeyLevels[level]
addedFilesMap := b.Added[level]
deletedFilesMap := b.Deleted[level]
if n := v.Levels[level].Len() + len(addedFilesMap); n == 0 {
return nil, base.CorruptionErrorf(
"pebble: internal error: No current or added files but have deleted files: %d",
errors.Safe(len(deletedFilesMap)))
}
// NB: addedFilesMap may be empty. If a file is present in addedFilesMap
// for a level, it won't be present in deletedFilesMap for the same
// level.
for _, f := range deletedFilesMap {
if obsolete := v.Levels[level].remove(f); obsolete {
// Deleting a file from the B-Tree may decrement its
// reference count. However, because we cloned the
// previous level's B-Tree, this should never result in a
// file's reference count dropping to zero.
err := errors.Errorf("pebble: internal error: file L%d.%s obsolete during B-Tree removal", level, f.FileNum)
return nil, err
}
if f.HasRangeKeys {
if obsolete := v.RangeKeyLevels[level].remove(f); obsolete {
// Deleting a file from the B-Tree may decrement its
// reference count. However, because we cloned the
// previous level's B-Tree, this should never result in a
// file's reference count dropping to zero.
err := errors.Errorf("pebble: internal error: file L%d.%s obsolete during range-key B-Tree removal", level, f.FileNum)
return nil, err
}
}
// Note that a backing sst will only become a zombie if the
// references to it in the latest version is 0. We will remove the
// backing sst from the zombie list in the next loop if one of the