0

Initial commit

This commit is contained in:
eleksir
2022-09-04 02:34:54 +03:00
commit d9de157ad8
1017 changed files with 407553 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
// Copyright 2020 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 "fmt"
const (
// 3 bits are necessary to represent level values from 0-6.
levelBits = 3
levelMask = (1 << levelBits) - 1
// invalidSublevel denotes an invalid or non-applicable sublevel.
invalidSublevel = -1
)
// Level encodes a level and optional sublevel for use in log and error
// messages. The encoding has the property that Level(0) ==
// L0Sublevel(invalidSublevel).
type Level uint32
func makeLevel(level, sublevel int) Level {
return Level(((sublevel + 1) << levelBits) | level)
}
// LevelToInt returns the int representation of a Level
func LevelToInt(l Level) int {
return int(l) & levelMask
}
// L0Sublevel returns a Level representing the specified L0 sublevel.
func L0Sublevel(sublevel int) Level {
if sublevel < 0 {
panic(fmt.Sprintf("invalid L0 sublevel: %d", sublevel))
}
return makeLevel(0, sublevel)
}
func (l Level) String() string {
level := int(l) & levelMask
sublevel := (int(l) >> levelBits) - 1
if sublevel != invalidSublevel {
return fmt.Sprintf("L%d.%d", level, sublevel)
}
return fmt.Sprintf("L%d", level)
}

View File

@@ -0,0 +1,626 @@
// Copyright 2020 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 (
"bytes"
"fmt"
"github.com/cockroachdb/pebble/internal/base"
)
// LevelMetadata contains metadata for all of the files within
// a level of the LSM.
type LevelMetadata struct {
level int
tree btree
}
// clone makes a copy of the level metadata, implicitly increasing the ref
// count of every file contained within lm.
func (lm *LevelMetadata) clone() LevelMetadata {
return LevelMetadata{
level: lm.level,
tree: lm.tree.clone(),
}
}
func (lm *LevelMetadata) release() (obsolete []*FileMetadata) {
return lm.tree.release()
}
func makeLevelMetadata(cmp Compare, level int, files []*FileMetadata) LevelMetadata {
bcmp := btreeCmpSeqNum
if level > 0 {
bcmp = btreeCmpSmallestKey(cmp)
}
var lm LevelMetadata
lm.level = level
lm.tree, _ = makeBTree(bcmp, files)
return lm
}
func makeBTree(cmp btreeCmp, files []*FileMetadata) (btree, LevelSlice) {
var t btree
t.cmp = cmp
for _, f := range files {
t.insert(f)
}
return t, LevelSlice{iter: t.iter(), length: t.length}
}
// Empty indicates whether there are any files in the level.
func (lm *LevelMetadata) Empty() bool {
return lm.tree.length == 0
}
// Len returns the number of files within the level.
func (lm *LevelMetadata) Len() int {
return lm.tree.length
}
// Iter constructs a LevelIterator over the entire level.
func (lm *LevelMetadata) Iter() LevelIterator {
return LevelIterator{iter: lm.tree.iter()}
}
// Slice constructs a slice containing the entire level.
func (lm *LevelMetadata) Slice() LevelSlice {
return LevelSlice{iter: lm.tree.iter(), length: lm.tree.length}
}
// Find finds the provided file in the level if it exists.
func (lm *LevelMetadata) Find(cmp base.Compare, m *FileMetadata) *LevelFile {
iter := lm.Iter()
if lm.level != 0 {
// If lm holds files for levels >0, we can narrow our search by binary
// searching by bounds.
o := overlaps(iter, cmp, m.Smallest.UserKey,
m.Largest.UserKey, m.Largest.IsExclusiveSentinel())
iter = o.Iter()
}
for f := iter.First(); f != nil; f = iter.Next() {
if f == m {
lf := iter.Take()
return &lf
}
}
return nil
}
// Annotation lazily calculates and returns the annotation defined by
// Annotator. The Annotator is used as the key for pre-calculated
// values, so equal Annotators must be used to avoid duplicate computations
// and cached annotations. Annotation must not be called concurrently, and in
// practice this is achieved by requiring callers to hold DB.mu.
func (lm *LevelMetadata) Annotation(annotator Annotator) interface{} {
if lm.Empty() {
return annotator.Zero(nil)
}
v, _ := lm.tree.root.annotation(annotator)
return v
}
// InvalidateAnnotation clears any cached annotations defined by Annotator. The
// Annotator is used as the key for pre-calculated values, so equal Annotators
// must be used to clear the appropriate cached annotation. InvalidateAnnotation
// must not be called concurrently, and in practice this is achieved by
// requiring callers to hold DB.mu.
func (lm *LevelMetadata) InvalidateAnnotation(annotator Annotator) {
if lm.Empty() {
return
}
lm.tree.root.invalidateAnnotation(annotator)
}
// LevelFile holds a file's metadata along with its position
// within a level of the LSM.
type LevelFile struct {
*FileMetadata
slice LevelSlice
}
// Slice constructs a LevelSlice containing only this file.
func (lf LevelFile) Slice() LevelSlice {
return lf.slice
}
// NewLevelSliceSeqSorted constructs a LevelSlice over the provided files,
// sorted by the L0 sequence number sort order.
// TODO(jackson): Can we improve this interface or avoid needing to export
// a slice constructor like this?
func NewLevelSliceSeqSorted(files []*FileMetadata) LevelSlice {
tr, slice := makeBTree(btreeCmpSeqNum, files)
tr.release()
return slice
}
// NewLevelSliceKeySorted constructs a LevelSlice over the provided files,
// sorted by the files smallest keys.
// TODO(jackson): Can we improve this interface or avoid needing to export
// a slice constructor like this?
func NewLevelSliceKeySorted(cmp base.Compare, files []*FileMetadata) LevelSlice {
tr, slice := makeBTree(btreeCmpSmallestKey(cmp), files)
tr.release()
return slice
}
// NewLevelSliceSpecificOrder constructs a LevelSlice over the provided files,
// ordering the files by their order in the provided slice. It's used in
// tests.
// TODO(jackson): Update tests to avoid requiring this and remove it.
func NewLevelSliceSpecificOrder(files []*FileMetadata) LevelSlice {
tr, slice := makeBTree(btreeCmpSpecificOrder(files), files)
tr.release()
return slice
}
// LevelSlice contains a slice of the files within a level of the LSM.
// A LevelSlice is immutable once created, but may be used to construct a
// mutable LevelIterator over the slice's files.
type LevelSlice struct {
iter iterator
length int
// start and end form the inclusive bounds of a slice of files within a
// level of the LSM. They may be nil if the entire B-Tree backing iter is
// accessible.
start *iterator
end *iterator
}
// Each invokes fn for each element in the slice.
func (ls LevelSlice) Each(fn func(*FileMetadata)) {
iter := ls.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
fn(f)
}
}
// String implements fmt.Stringer.
func (ls LevelSlice) String() string {
var buf bytes.Buffer
ls.Each(func(f *FileMetadata) {
if buf.Len() > 0 {
fmt.Fprintf(&buf, " ")
}
fmt.Fprint(&buf, f)
})
return buf.String()
}
// Empty indicates whether the slice contains any files.
func (ls *LevelSlice) Empty() bool {
return emptyWithBounds(ls.iter, ls.start, ls.end)
}
// Iter constructs a LevelIterator that iterates over the slice.
func (ls *LevelSlice) Iter() LevelIterator {
return LevelIterator{
start: ls.start,
end: ls.end,
iter: ls.iter.clone(),
}
}
// Len returns the number of files in the slice. Its runtime is constant.
func (ls *LevelSlice) Len() int {
return ls.length
}
// SizeSum sums the size of all files in the slice. Its runtime is linear in
// the length of the slice.
func (ls *LevelSlice) SizeSum() uint64 {
var sum uint64
iter := ls.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
sum += f.Size
}
return sum
}
// Reslice constructs a new slice backed by the same underlying level, with
// new start and end positions. Reslice invokes the provided function, passing
// two LevelIterators: one positioned to i's inclusive start and one
// positioned to i's inclusive end. The resliceFunc may move either iterator
// forward or backwards, including beyond the callee's original bounds to
// capture additional files from the underlying level. Reslice constructs and
// returns a new LevelSlice with the final bounds of the iterators after
// calling resliceFunc.
func (ls LevelSlice) Reslice(resliceFunc func(start, end *LevelIterator)) LevelSlice {
if ls.iter.r == nil {
return ls
}
var start, end LevelIterator
if ls.start == nil {
start.iter = ls.iter.clone()
start.iter.first()
} else {
start.iter = ls.start.clone()
}
if ls.end == nil {
end.iter = ls.iter.clone()
end.iter.last()
} else {
end.iter = ls.end.clone()
}
resliceFunc(&start, &end)
s := LevelSlice{
iter: start.iter.clone(),
start: &start.iter,
end: &end.iter,
}
// Calculate the new slice's length.
iter := s.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
s.length++
}
return s
}
// KeyType is used to specify the type of keys we're looking for in
// LevelIterator positioning operations. Files not containing any keys of the
// desired type are skipped.
type KeyType int8
const (
// KeyTypePointAndRange denotes a search among the entire keyspace, including
// both point keys and range keys. No sstables are skipped.
KeyTypePointAndRange KeyType = iota
// KeyTypePoint denotes a search among the point keyspace. SSTables with no
// point keys will be skipped. Note that the point keyspace includes rangedels.
KeyTypePoint
// KeyTypeRange denotes a search among the range keyspace. SSTables with no
// range keys will be skipped.
KeyTypeRange
)
type keyTypeAnnotator struct{}
var _ Annotator = keyTypeAnnotator{}
func (k keyTypeAnnotator) Zero(dst interface{}) interface{} {
var val *KeyType
if dst != nil {
val = dst.(*KeyType)
} else {
val = new(KeyType)
}
*val = KeyTypePoint
return val
}
func (k keyTypeAnnotator) Accumulate(m *FileMetadata, dst interface{}) (interface{}, bool) {
v := dst.(*KeyType)
switch *v {
case KeyTypePoint:
if m.HasRangeKeys {
*v = KeyTypePointAndRange
}
case KeyTypePointAndRange:
// Do nothing.
default:
panic("unexpected key type")
}
return v, true
}
func (k keyTypeAnnotator) Merge(src interface{}, dst interface{}) interface{} {
v := dst.(*KeyType)
srcVal := src.(*KeyType)
switch *v {
case KeyTypePoint:
if *srcVal == KeyTypePointAndRange {
*v = KeyTypePointAndRange
}
case KeyTypePointAndRange:
// Do nothing.
default:
panic("unexpected key type")
}
return v
}
// LevelIterator iterates over a set of files' metadata. Its zero value is an
// empty iterator.
type LevelIterator struct {
iter iterator
start *iterator
end *iterator
filter KeyType
}
func (i LevelIterator) String() string {
var buf bytes.Buffer
iter := i.iter.clone()
iter.first()
iter.prev()
if i.iter.pos == -1 {
fmt.Fprint(&buf, "(<start>)*")
}
iter.next()
for ; iter.valid(); iter.next() {
if buf.Len() > 0 {
fmt.Fprint(&buf, " ")
}
if i.start != nil && cmpIter(iter, *i.start) == 0 {
fmt.Fprintf(&buf, " [ ")
}
isCurrentPos := cmpIter(iter, i.iter) == 0
if isCurrentPos {
fmt.Fprint(&buf, " ( ")
}
fmt.Fprint(&buf, iter.cur().String())
if isCurrentPos {
fmt.Fprint(&buf, " )*")
}
if i.end != nil && cmpIter(iter, *i.end) == 0 {
fmt.Fprintf(&buf, " ]")
}
}
if i.iter.n != nil && i.iter.pos >= i.iter.n.count {
if buf.Len() > 0 {
fmt.Fprint(&buf, " ")
}
fmt.Fprint(&buf, "(<end>)*")
}
return buf.String()
}
// Clone copies the iterator, returning an independent iterator at the same
// position.
func (i *LevelIterator) Clone() LevelIterator {
if i.iter.r == nil {
return *i
}
// The start and end iterators are not cloned and are treated as
// immutable.
return LevelIterator{
iter: i.iter.clone(),
start: i.start,
end: i.end,
filter: i.filter,
}
}
// Current returns the item at the current iterator position.
func (i *LevelIterator) Current() *FileMetadata {
if !i.iter.valid() {
return nil
}
return i.iter.cur()
}
func (i *LevelIterator) empty() bool {
return emptyWithBounds(i.iter, i.start, i.end)
}
// Filter clones the iterator and sets the desired KeyType as the key to filter
// files on.
func (i *LevelIterator) Filter(keyType KeyType) LevelIterator {
l := i.Clone()
l.filter = keyType
return l
}
func emptyWithBounds(i iterator, start, end *iterator) bool {
// If i.r is nil, the iterator was constructed from an empty btree.
// If the end bound is before the start bound, the bounds represent an
// empty slice of the B-Tree.
return i.r == nil || (start != nil && end != nil && cmpIter(*end, *start) < 0)
}
// First seeks to the first file in the iterator and returns it.
func (i *LevelIterator) First() *FileMetadata {
if i.empty() {
return nil
}
if i.start != nil {
i.iter = i.start.clone()
} else {
i.iter.first()
}
if !i.iter.valid() {
return nil
}
return i.filteredNextFile(i.iter.cur())
}
// Last seeks to the last file in the iterator and returns it.
func (i *LevelIterator) Last() *FileMetadata {
if i.empty() {
return nil
}
if i.end != nil {
i.iter = i.end.clone()
} else {
i.iter.last()
}
if !i.iter.valid() {
return nil
}
return i.filteredPrevFile(i.iter.cur())
}
// Next advances the iterator to the next file and returns it.
func (i *LevelIterator) Next() *FileMetadata {
i.iter.next()
if !i.iter.valid() {
return nil
}
if i.end != nil && cmpIter(i.iter, *i.end) > 0 {
return nil
}
return i.filteredNextFile(i.iter.cur())
}
// Prev moves the iterator the previous file and returns it.
func (i *LevelIterator) Prev() *FileMetadata {
i.iter.prev()
if !i.iter.valid() {
return nil
}
if i.start != nil && cmpIter(i.iter, *i.start) < 0 {
return nil
}
return i.filteredPrevFile(i.iter.cur())
}
// SeekGE seeks to the first file in the iterator's file set with a largest
// user key greater than or equal to the provided user key. The iterator must
// have been constructed from L1+, because it requires the underlying files to
// be sorted by user keys and non-overlapping.
func (i *LevelIterator) SeekGE(cmp Compare, userKey []byte) *FileMetadata {
// TODO(jackson): Assert that i.iter.cmp == btreeCmpSmallestKey.
if i.empty() {
return nil
}
meta := i.seek(func(m *FileMetadata) bool {
return cmp(m.Largest.UserKey, userKey) >= 0
})
for meta != nil {
switch i.filter {
case KeyTypePointAndRange:
return meta
case KeyTypePoint:
if meta.HasPointKeys && cmp(meta.LargestPointKey.UserKey, userKey) >= 0 {
return meta
}
case KeyTypeRange:
if meta.HasRangeKeys && cmp(meta.LargestRangeKey.UserKey, userKey) >= 0 {
return meta
}
}
meta = i.Next()
}
return i.filteredNextFile(meta)
}
// SeekLT seeks to the last file in the iterator's file set with a smallest
// user key less than the provided user key. The iterator must have been
// constructed from L1+, because it requires the underlying files to be sorted
// by user keys and non-overlapping.
func (i *LevelIterator) SeekLT(cmp Compare, userKey []byte) *FileMetadata {
// TODO(jackson): Assert that i.iter.cmp == btreeCmpSmallestKey.
if i.empty() {
return nil
}
i.seek(func(m *FileMetadata) bool {
return cmp(m.Smallest.UserKey, userKey) >= 0
})
meta := i.Prev()
for meta != nil {
switch i.filter {
case KeyTypePointAndRange:
return meta
case KeyTypePoint:
if meta.HasPointKeys && cmp(meta.SmallestPointKey.UserKey, userKey) < 0 {
return meta
}
case KeyTypeRange:
if meta.HasRangeKeys && cmp(meta.SmallestRangeKey.UserKey, userKey) < 0 {
return meta
}
}
meta = i.Prev()
}
return i.filteredPrevFile(meta)
}
func (i *LevelIterator) filteredNextFile(meta *FileMetadata) *FileMetadata {
switch i.filter {
case KeyTypePoint:
for meta != nil && !meta.HasPointKeys {
meta = i.Next()
}
return meta
case KeyTypeRange:
// TODO(bilal): Range keys are expected to be rare and sparse. Add an
// optimization to annotate the tree and efficiently skip over files that
// do not contain range keys right at the seek step, to reduce iterations
// here.
for meta != nil && !meta.HasRangeKeys {
meta = i.Next()
}
return meta
default:
return meta
}
}
func (i *LevelIterator) filteredPrevFile(meta *FileMetadata) *FileMetadata {
switch i.filter {
case KeyTypePoint:
for meta != nil && !meta.HasPointKeys {
meta = i.Prev()
}
return meta
case KeyTypeRange:
// TODO(bilal): Range keys are expected to be rare and sparse. Add an
// optimization to annotate the tree and efficiently skip over files that
// do not contain range keys right at the seek step, to reduce iterations
// here.
for meta != nil && !meta.HasRangeKeys {
meta = i.Prev()
}
return meta
default:
return meta
}
}
func (i *LevelIterator) seek(fn func(*FileMetadata) bool) *FileMetadata {
i.iter.seek(fn)
// i.iter.seek seeked in the unbounded underlying B-Tree. If the iterator
// has start or end bounds, we may have exceeded them. Reset to the bounds
// if necessary.
//
// NB: The LevelIterator and LevelSlice semantics require that a bounded
// LevelIterator/LevelSlice containing files x0, x1, ..., xn behave
// identically to an unbounded LevelIterator/LevelSlice of a B-Tree
// containing x0, x1, ..., xn. In other words, any files outside the
// LevelIterator's bounds should not influence the iterator's behavior.
// When seeking, this means a SeekGE that seeks beyond the end bound,
// followed by a Prev should return the last element within bounds.
if i.end != nil && cmpIter(i.iter, *i.end) > 0 {
i.iter = i.end.clone()
// Since seek(fn) positioned beyond i.end, we know there is nothing to
// return within bounds.
i.iter.next()
return nil
} else if i.start != nil && cmpIter(i.iter, *i.start) < 0 {
i.iter = i.start.clone()
return i.iter.cur()
}
if !i.iter.valid() {
return nil
}
return i.iter.cur()
}
// Take constructs a LevelFile containing the file at the iterator's current
// position. Take panics if the iterator is not currently positioned over a
// file.
func (i *LevelIterator) Take() LevelFile {
m := i.Current()
if m == nil {
panic("Take called on invalid LevelIterator")
}
// LevelSlice's start and end fields are immutable and are positioned to
// the same position for a LevelFile because they're inclusive, so we can
// share one iterator stack between the two bounds.
boundsIter := i.iter.clone()
return LevelFile{
FileMetadata: m,
slice: LevelSlice{
iter: i.iter.clone(),
start: &boundsIter,
end: &boundsIter,
length: 1,
},
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,797 @@
// 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"
"io"
"sync/atomic"
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/base"
)
// 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.
// The custom tags sub-format used by tagNewFile4 and above.
customTagTerminate = 1
customTagNeedsCompaction = 2
customTagCreationTime = 6
customTagPathID = 65
customTagNonSafeIgnoreMask = 1 << 6
)
// 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
}
// 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
}
// Decode decodes an edit from the specified reader.
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 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
if tag == tagNewFile4 || tag == tagNewFile5 {
for {
customTag, err := d.readUvarint()
if err != nil {
return err
}
if customTag == customTagTerminate {
break
}
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,
}
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
v.NewFiles = append(v.NewFiles, NewFileEntry{
Level: level,
Meta: m,
})
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
}
// 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))
}
// 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
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})
}
e.writeUvarint(customTagTerminate)
}
}
_, err := w.Write(e.Bytes())
return err
}
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.
type BulkVersionEdit struct {
Added [NumLevels][]*FileMetadata
Deleted [NumLevels]map[base.FileNum]*FileMetadata
// 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.
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--
}
dmap[df.FileNum] = m
}
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)
}
}
b.Added[nf.Level] = append(b.Added[nf.Level], nf.Meta)
if b.AddedByFileNum != nil {
b.AddedByFileNum[nf.Meta.FileNum] = nf.Meta
}
if nf.Meta.MarkedForCompaction {
b.MarkedForCompactionCountDiff++
}
}
return 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, a map of zombie files containing the file numbers and sizes of
// deleted files is returned. 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,
) (_ *Version, zombies map[base.FileNum]uint64, _ error) {
addZombie := func(fileNum base.FileNum, size uint64) {
if zombies == nil {
zombies = make(map[base.FileNum]uint64)
}
zombies[fileNum] = size
}
// The remove zombie function is used to handle tables that are moved from
// one level to another during a version edit (i.e. a "move" compaction).
removeZombie := func(fileNum base.FileNum) {
if zombies != nil {
delete(zombies, fileNum)
}
}
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, 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, 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]
addedFiles := b.Added[level]
deletedMap := b.Deleted[level]
if n := v.Levels[level].Len() + len(addedFiles); n == 0 {
return nil, nil, base.CorruptionErrorf(
"pebble: internal error: No current or added files but have deleted files: %d",
errors.Safe(len(deletedMap)))
}
// NB: addedFiles may be empty and it also is not necessarily
// internally consistent: it does not reflect deletions in deletedMap.
for _, f := range deletedMap {
addZombie(f.FileNum, f.Size)
if obsolete := v.Levels[level].tree.delete(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, nil, err
}
if f.HasRangeKeys {
if obsolete := v.RangeKeyLevels[level].tree.delete(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, nil, err
}
}
}
var sm, la *FileMetadata
for _, f := range addedFiles {
if _, ok := deletedMap[f.FileNum]; ok {
// Already called addZombie on this file in the preceding
// loop, so we don't need to do it here.
continue
}
// NB: allowedSeeks is used for read triggered compactions. It is set using
// Options.Experimental.ReadCompactionRate which defaults to 32KB.
var allowedSeeks int64
if readCompactionRate != 0 {
allowedSeeks = int64(f.Size) / readCompactionRate
}
if allowedSeeks < 100 {
allowedSeeks = 100
}
atomic.StoreInt64(&f.Atomic.AllowedSeeks, allowedSeeks)
f.InitAllowedSeeks = allowedSeeks
err := lm.tree.insert(f)
if err != nil {
return nil, nil, errors.Wrap(err, "pebble")
}
if f.HasRangeKeys {
err = lmRange.tree.insert(f)
if err != nil {
return nil, nil, errors.Wrap(err, "pebble")
}
}
removeZombie(f.FileNum)
// Track the keys with the smallest and largest keys, so that we can
// check consistency of the modified span.
if sm == nil || base.InternalCompare(cmp, sm.Smallest, f.Smallest) > 0 {
sm = f
}
if la == nil || base.InternalCompare(cmp, la.Largest, f.Largest) < 0 {
la = f
}
}
if level == 0 {
if curr != nil && curr.L0Sublevels != nil && len(deletedMap) == 0 {
// Flushes and ingestions that do not delete any L0 files do not require
// a regeneration of L0Sublevels from scratch. We can instead generate
// it incrementally.
var err error
// AddL0Files requires addedFiles to be sorted in seqnum order.
addedFiles = append([]*FileMetadata(nil), addedFiles...)
SortBySeqNum(addedFiles)
v.L0Sublevels, err = curr.L0Sublevels.AddL0Files(addedFiles, flushSplitBytes, &v.Levels[0])
if errors.Is(err, errInvalidL0SublevelsOpt) {
err = v.InitL0Sublevels(cmp, formatKey, flushSplitBytes)
}
if err != nil {
return nil, nil, errors.Wrap(err, "pebble: internal error")
}
v.L0SublevelFiles = v.L0Sublevels.Levels
} else if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
return nil, nil, errors.Wrap(err, "pebble: internal error")
}
if err := CheckOrdering(cmp, formatKey, Level(0), v.Levels[level].Iter()); err != nil {
return nil, nil, errors.Wrap(err, "pebble: internal error")
}
continue
}
// Check consistency of the level in the vicinity of our edits.
if sm != nil && la != nil {
overlap := overlaps(v.Levels[level].Iter(), cmp, sm.Smallest.UserKey,
la.Largest.UserKey, la.Largest.IsExclusiveSentinel())
// overlap contains all of the added files. We want to ensure that
// the added files are consistent with neighboring existing files
// too, so reslice the overlap to pull in a neighbor on each side.
check := overlap.Reslice(func(start, end *LevelIterator) {
if m := start.Prev(); m == nil {
start.Next()
}
if m := end.Next(); m == nil {
end.Prev()
}
})
if err := CheckOrdering(cmp, formatKey, Level(level), check.Iter()); err != nil {
return nil, nil, errors.Wrap(err, "pebble: internal error")
}
}
}
return v, zombies, nil
}