[WIP] lint a bit, update deps
This commit is contained in:
34
vendor/github.com/cockroachdb/pebble/internal/manifest/btree.go
generated
vendored
34
vendor/github.com/cockroachdb/pebble/internal/manifest/btree.go
generated
vendored
@@ -108,7 +108,7 @@ type annotation struct {
|
||||
}
|
||||
|
||||
type leafNode struct {
|
||||
ref int32
|
||||
ref atomic.Int32
|
||||
count int16
|
||||
leaf bool
|
||||
// subtreeCount holds the count of files in the entire subtree formed by
|
||||
@@ -139,13 +139,13 @@ func leafToNode(ln *leafNode) *node {
|
||||
func newLeafNode() *node {
|
||||
n := leafToNode(new(leafNode))
|
||||
n.leaf = true
|
||||
n.ref = 1
|
||||
n.ref.Store(1)
|
||||
return n
|
||||
}
|
||||
|
||||
func newNode() *node {
|
||||
n := new(node)
|
||||
n.ref = 1
|
||||
n.ref.Store(1)
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ func newNode() *node {
|
||||
// When a node is cloned, the provided pointer will be redirected to the new
|
||||
// mutable node.
|
||||
func mut(n **node) *node {
|
||||
if atomic.LoadInt32(&(*n).ref) == 1 {
|
||||
if (*n).ref.Load() == 1 {
|
||||
// Exclusive ownership. Can mutate in place.
|
||||
|
||||
// Whenever a node will be mutated, reset its annotations to be marked
|
||||
@@ -186,7 +186,7 @@ func mut(n **node) *node {
|
||||
|
||||
// incRef acquires a reference to the node.
|
||||
func (n *node) incRef() {
|
||||
atomic.AddInt32(&n.ref, 1)
|
||||
n.ref.Add(1)
|
||||
}
|
||||
|
||||
// decRef releases a reference to the node. If requested, the method will unref
|
||||
@@ -195,8 +195,8 @@ func (n *node) incRef() {
|
||||
// new nodes pass contentsToo=false to preserve existing reference counts during
|
||||
// operations that should yield a net-zero change to descendant refcounts.
|
||||
// When a node is released, its contained files are dereferenced.
|
||||
func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
||||
if atomic.AddInt32(&n.ref, -1) > 0 {
|
||||
func (n *node) decRef(contentsToo bool, obsolete *[]*FileBacking) {
|
||||
if n.ref.Add(-1) > 0 {
|
||||
// Other references remain. Can't free.
|
||||
return
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
||||
// nodes, and they want to preserve the existing reference count.
|
||||
if contentsToo {
|
||||
for _, f := range n.items[:n.count] {
|
||||
if atomic.AddInt32(&f.Refs, -1) == 0 {
|
||||
if f.Unref() == 0 {
|
||||
// There are two sources of node dereferences: tree mutations
|
||||
// and Version dereferences. Files should only be made obsolete
|
||||
// during Version dereferences, during which `obsolete` will be
|
||||
@@ -215,7 +215,13 @@ func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
||||
if obsolete == nil {
|
||||
panic(fmt.Sprintf("file metadata %s dereferenced to zero during tree mutation", f.FileNum))
|
||||
}
|
||||
*obsolete = append(*obsolete, f)
|
||||
// Reference counting is performed on the FileBacking. In the case
|
||||
// of a virtual sstable, this reference counting is performed on
|
||||
// a FileBacking which is shared by every single virtual sstable
|
||||
// with the same backing sstable. If the reference count hits 0,
|
||||
// then we know that the FileBacking won't be required by any
|
||||
// sstable in Pebble, and that the backing sstable can be deleted.
|
||||
*obsolete = append(*obsolete, f.FileBacking)
|
||||
}
|
||||
}
|
||||
if !n.leaf {
|
||||
@@ -241,7 +247,7 @@ func (n *node) clone() *node {
|
||||
c.subtreeCount = n.subtreeCount
|
||||
// Increase the refcount of each contained item.
|
||||
for _, f := range n.items[:n.count] {
|
||||
atomic.AddInt32(&f.Refs, 1)
|
||||
f.Ref()
|
||||
}
|
||||
if !c.leaf {
|
||||
// Copy children and increase each refcount.
|
||||
@@ -761,8 +767,8 @@ type btree struct {
|
||||
|
||||
// Release dereferences and clears the root node of the btree, removing all
|
||||
// items from the btree. In doing so, it decrements contained file counts.
|
||||
// It returns a slice of newly obsolete files, if any.
|
||||
func (t *btree) Release() (obsolete []*FileMetadata) {
|
||||
// It returns a slice of newly obsolete backing files, if any.
|
||||
func (t *btree) Release() (obsolete []*FileBacking) {
|
||||
if t.root != nil {
|
||||
t.root.decRef(true /* contentsToo */, &obsolete)
|
||||
t.root = nil
|
||||
@@ -800,7 +806,7 @@ func (t *btree) Delete(item *FileMetadata) (obsolete bool) {
|
||||
return false
|
||||
}
|
||||
if out := mut(&t.root).Remove(t.cmp, item); out != nil {
|
||||
obsolete = atomic.AddInt32(&out.Refs, -1) == 0
|
||||
obsolete = out.Unref() == 0
|
||||
}
|
||||
if invariants.Enabled {
|
||||
t.root.verifyInvariants()
|
||||
@@ -832,7 +838,7 @@ func (t *btree) Insert(item *FileMetadata) error {
|
||||
newRoot.subtreeCount = t.root.subtreeCount + splitNode.subtreeCount + 1
|
||||
t.root = newRoot
|
||||
}
|
||||
atomic.AddInt32(&item.Refs, 1)
|
||||
item.Ref()
|
||||
err := mut(&t.root).Insert(t.cmp, item)
|
||||
if invariants.Enabled {
|
||||
t.root.verifyInvariants()
|
||||
|
||||
77
vendor/github.com/cockroachdb/pebble/internal/manifest/level_metadata.go
generated
vendored
77
vendor/github.com/cockroachdb/pebble/internal/manifest/level_metadata.go
generated
vendored
@@ -15,20 +15,28 @@ import (
|
||||
// LevelMetadata contains metadata for all of the files within
|
||||
// a level of the LSM.
|
||||
type LevelMetadata struct {
|
||||
level int
|
||||
tree btree
|
||||
level int
|
||||
totalSize uint64
|
||||
// NumVirtual is the number of virtual sstables in the level.
|
||||
NumVirtual uint64
|
||||
// VirtualSize is the size of the virtual sstables in the level.
|
||||
VirtualSize uint64
|
||||
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(),
|
||||
level: lm.level,
|
||||
totalSize: lm.totalSize,
|
||||
NumVirtual: lm.NumVirtual,
|
||||
VirtualSize: lm.VirtualSize,
|
||||
tree: lm.tree.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
func (lm *LevelMetadata) release() (obsolete []*FileMetadata) {
|
||||
func (lm *LevelMetadata) release() (obsolete []*FileBacking) {
|
||||
return lm.tree.Release()
|
||||
}
|
||||
|
||||
@@ -40,6 +48,13 @@ func makeLevelMetadata(cmp Compare, level int, files []*FileMetadata) LevelMetad
|
||||
var lm LevelMetadata
|
||||
lm.level = level
|
||||
lm.tree, _ = makeBTree(bcmp, files)
|
||||
for _, f := range files {
|
||||
lm.totalSize += f.Size
|
||||
if f.Virtual {
|
||||
lm.NumVirtual++
|
||||
lm.VirtualSize += f.Size
|
||||
}
|
||||
}
|
||||
return lm
|
||||
}
|
||||
|
||||
@@ -52,6 +67,27 @@ func makeBTree(cmp btreeCmp, files []*FileMetadata) (btree, LevelSlice) {
|
||||
return t, newLevelSlice(t.Iter())
|
||||
}
|
||||
|
||||
func (lm *LevelMetadata) insert(f *FileMetadata) error {
|
||||
if err := lm.tree.Insert(f); err != nil {
|
||||
return err
|
||||
}
|
||||
lm.totalSize += f.Size
|
||||
if f.Virtual {
|
||||
lm.NumVirtual++
|
||||
lm.VirtualSize += f.Size
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lm *LevelMetadata) remove(f *FileMetadata) bool {
|
||||
lm.totalSize -= f.Size
|
||||
if f.Virtual {
|
||||
lm.NumVirtual--
|
||||
lm.VirtualSize -= f.Size
|
||||
}
|
||||
return lm.tree.Delete(f)
|
||||
}
|
||||
|
||||
// Empty indicates whether there are any files in the level.
|
||||
func (lm *LevelMetadata) Empty() bool {
|
||||
return lm.tree.Count() == 0
|
||||
@@ -62,6 +98,11 @@ func (lm *LevelMetadata) Len() int {
|
||||
return lm.tree.Count()
|
||||
}
|
||||
|
||||
// Size returns the cumulative size of all the files within the level.
|
||||
func (lm *LevelMetadata) Size() uint64 {
|
||||
return lm.totalSize
|
||||
}
|
||||
|
||||
// Iter constructs a LevelIterator over the entire level.
|
||||
func (lm *LevelMetadata) Iter() LevelIterator {
|
||||
return LevelIterator{iter: lm.tree.Iter()}
|
||||
@@ -280,6 +321,32 @@ func (ls *LevelSlice) SizeSum() uint64 {
|
||||
return sum
|
||||
}
|
||||
|
||||
// NumVirtual returns the number of virtual sstables in the level. Its runtime is
|
||||
// linear in the length of the slice.
|
||||
func (ls *LevelSlice) NumVirtual() uint64 {
|
||||
var n uint64
|
||||
iter := ls.Iter()
|
||||
for f := iter.First(); f != nil; f = iter.Next() {
|
||||
if f.Virtual {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// VirtualSizeSum returns the sum of the sizes of the virtual sstables in the
|
||||
// level.
|
||||
func (ls *LevelSlice) VirtualSizeSum() uint64 {
|
||||
var sum uint64
|
||||
iter := ls.Iter()
|
||||
for f := iter.First(); f != nil; f = iter.Next() {
|
||||
if f.Virtual {
|
||||
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
|
||||
|
||||
464
vendor/github.com/cockroachdb/pebble/internal/manifest/version.go
generated
vendored
464
vendor/github.com/cockroachdb/pebble/internal/manifest/version.go
generated
vendored
@@ -49,6 +49,9 @@ type TableStats struct {
|
||||
// The number of point and range deletion entries in the table.
|
||||
NumDeletions uint64
|
||||
// NumRangeKeySets is the total number of range key sets in the table.
|
||||
//
|
||||
// NB: If there's a chance that the sstable contains any range key sets,
|
||||
// then NumRangeKeySets must be > 0.
|
||||
NumRangeKeySets uint64
|
||||
// Estimate of the total disk space that may be dropped by this table's
|
||||
// point deletions by compacting them.
|
||||
@@ -60,9 +63,11 @@ type TableStats struct {
|
||||
// overlapping data in L0 and ignores L0 sublevels, but the error that
|
||||
// introduces is expected to be small.
|
||||
//
|
||||
// Tables in the bottommost level of the LSM may have a nonzero estimate
|
||||
// if snapshots or move compactions prevented the elision of their range
|
||||
// tombstones.
|
||||
// Tables in the bottommost level of the LSM may have a nonzero estimate if
|
||||
// snapshots or move compactions prevented the elision of their range
|
||||
// tombstones. A table in the bottommost level that was ingested into L6
|
||||
// will have a zero estimate, because the file's sequence numbers indicate
|
||||
// that the tombstone cannot drop any data contained within the file itself.
|
||||
RangeDeletionsBytesEstimate uint64
|
||||
// Total size of value blocks and value index block.
|
||||
ValueBlocksSize uint64
|
||||
@@ -119,43 +124,72 @@ func (s CompactionState) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
// FileMetadata holds the metadata for an on-disk table.
|
||||
// FileMetadata is maintained for leveled-ssts, i.e., they belong to a level of
|
||||
// some version. FileMetadata does not contain the actual level of the sst,
|
||||
// since such leveled-ssts can move across levels in different versions, while
|
||||
// sharing the same FileMetadata. There are two kinds of leveled-ssts, physical
|
||||
// and virtual. Underlying both leveled-ssts is a backing-sst, for which the
|
||||
// only state is FileBacking. A backing-sst is level-less. It is possible for a
|
||||
// backing-sst to be referred to by a physical sst in one version and by one or
|
||||
// more virtual ssts in one or more versions. A backing-sst becomes obsolete
|
||||
// and can be deleted once it is no longer required by any physical or virtual
|
||||
// sst in any version.
|
||||
//
|
||||
// We maintain some invariants:
|
||||
//
|
||||
// 1. Each physical and virtual sst will have a unique FileMetadata.FileNum,
|
||||
// and there will be exactly one FileMetadata associated with the FileNum.
|
||||
//
|
||||
// 2. Within a version, a backing-sst is either only referred to by one
|
||||
// physical sst or one or more virtual ssts.
|
||||
//
|
||||
// 3. Once a backing-sst is referred to by a virtual sst in the latest version,
|
||||
// it cannot go back to being referred to by a physical sst in any future
|
||||
// version.
|
||||
//
|
||||
// Once a physical sst is no longer needed by any version, we will no longer
|
||||
// maintain the file metadata associated with it. We will still maintain the
|
||||
// FileBacking associated with the physical sst if the backing sst is required
|
||||
// by any virtual ssts in any version.
|
||||
type FileMetadata struct {
|
||||
// Atomic contains fields which are accessed atomically. Go allocations
|
||||
// are guaranteed to be 64-bit aligned which we take advantage of by
|
||||
// placing the 64-bit fields which we access atomically at the beginning
|
||||
// of the FileMetadata struct. For more information, see
|
||||
// https://golang.org/pkg/sync/atomic/#pkg-note-BUG.
|
||||
Atomic struct {
|
||||
// AllowedSeeks is used to determine if a file should be picked for
|
||||
// a read triggered compaction. It is decremented when read sampling
|
||||
// in pebble.Iterator after every after every positioning operation
|
||||
// that returns a user key (eg. Next, Prev, SeekGE, SeekLT, etc).
|
||||
AllowedSeeks int64
|
||||
// AllowedSeeks is used to determine if a file should be picked for
|
||||
// a read triggered compaction. It is decremented when read sampling
|
||||
// in pebble.Iterator after every after every positioning operation
|
||||
// that returns a user key (eg. Next, Prev, SeekGE, SeekLT, etc).
|
||||
AllowedSeeks atomic.Int64
|
||||
|
||||
// statsValid is 1 if stats have been loaded for the table. The
|
||||
// TableStats structure is populated only if valid is 1.
|
||||
statsValid uint32
|
||||
}
|
||||
// statsValid indicates if stats have been loaded for the table. The
|
||||
// TableStats structure is populated only if valid is true.
|
||||
statsValid atomic.Bool
|
||||
|
||||
// FileBacking is the state which backs either a physical or virtual
|
||||
// sstables.
|
||||
FileBacking *FileBacking
|
||||
|
||||
// InitAllowedSeeks is the inital value of allowed seeks. This is used
|
||||
// to re-set allowed seeks on a file once it hits 0.
|
||||
InitAllowedSeeks int64
|
||||
|
||||
// Reference count for the file: incremented when a file is added to a
|
||||
// version and decremented when the version is unreferenced. The file is
|
||||
// obsolete when the reference count falls to zero.
|
||||
Refs int32
|
||||
// FileNum is the file number.
|
||||
//
|
||||
// INVARIANT: when !FileMetadata.Virtual, FileNum == FileBacking.DiskFileNum.
|
||||
FileNum base.FileNum
|
||||
// Size is the size of the file, in bytes.
|
||||
// Size is the size of the file, in bytes. Size is an approximate value for
|
||||
// virtual sstables.
|
||||
//
|
||||
// INVARIANTS:
|
||||
// - When !FileMetadata.Virtual, Size == FileBacking.Size.
|
||||
// - Size should be non-zero. Size 0 virtual sstables must not be created.
|
||||
Size uint64
|
||||
// File creation time in seconds since the epoch (1970-01-01 00:00:00
|
||||
// UTC). For ingested sstables, this corresponds to the time the file was
|
||||
// ingested.
|
||||
// ingested. For virtual sstables, this corresponds to the wall clock time
|
||||
// when the FileMetadata for the virtual sstable was first created.
|
||||
CreationTime int64
|
||||
// Smallest and largest sequence numbers in the table, across both point and
|
||||
// range keys.
|
||||
// Lower and upper bounds for the smallest and largest sequence numbers in
|
||||
// the table, across both point and range keys. For physical sstables, these
|
||||
// values are tight bounds. For virtual sstables, there is no guarantee that
|
||||
// there will be keys with SmallestSeqNum or LargestSeqNum within virtual
|
||||
// sstable bounds.
|
||||
SmallestSeqNum uint64
|
||||
LargestSeqNum uint64
|
||||
// SmallestPointKey and LargestPointKey are the inclusive bounds for the
|
||||
@@ -178,8 +212,17 @@ type FileMetadata struct {
|
||||
Smallest InternalKey
|
||||
Largest InternalKey
|
||||
// Stats describe table statistics. Protected by DB.mu.
|
||||
//
|
||||
// For virtual sstables, set stats upon virtual sstable creation as
|
||||
// asynchronous computation of stats is not currently supported.
|
||||
//
|
||||
// TODO(bananabrick): To support manifest replay for virtual sstables, we
|
||||
// probably need to compute virtual sstable stats asynchronously. Otherwise,
|
||||
// we'd have to write virtual sstable stats to the version edit.
|
||||
Stats TableStats
|
||||
|
||||
// For L0 files only. Protected by DB.mu. Used to generate L0 sublevels and
|
||||
// pick L0 compactions. Only accurate for the most recent Version.
|
||||
SubLevel int
|
||||
L0Index int
|
||||
minIntervalIndex int
|
||||
@@ -188,9 +231,6 @@ type FileMetadata struct {
|
||||
// NB: the alignment of this struct is 8 bytes. We pack all the bools to
|
||||
// ensure an optimal packing.
|
||||
|
||||
// For L0 files only. Protected by DB.mu. Used to generate L0 sublevels and
|
||||
// pick L0 compactions. Only accurate for the most recent Version.
|
||||
//
|
||||
// IsIntraL0Compacting is set to True if this file is part of an intra-L0
|
||||
// compaction. When it's true, IsCompacting must also return true. If
|
||||
// Compacting is true and IsIntraL0Compacting is false for an L0 file, the
|
||||
@@ -226,6 +266,212 @@ type FileMetadata struct {
|
||||
// key type (point or range) corresponds to the smallest and largest overall
|
||||
// table bounds.
|
||||
boundTypeSmallest, boundTypeLargest boundType
|
||||
// Virtual is true if the FileMetadata belongs to a virtual sstable.
|
||||
Virtual bool
|
||||
}
|
||||
|
||||
// PhysicalFileMeta is used by functions which want a guarantee that their input
|
||||
// belongs to a physical sst and not a virtual sst.
|
||||
//
|
||||
// NB: This type should only be constructed by calling
|
||||
// FileMetadata.PhysicalMeta.
|
||||
type PhysicalFileMeta struct {
|
||||
*FileMetadata
|
||||
}
|
||||
|
||||
// VirtualFileMeta is used by functions which want a guarantee that their input
|
||||
// belongs to a virtual sst and not a physical sst.
|
||||
//
|
||||
// A VirtualFileMeta inherits all the same fields as a FileMetadata. These
|
||||
// fields have additional invariants imposed on them, and/or slightly varying
|
||||
// meanings:
|
||||
// - Smallest and Largest (and their counterparts
|
||||
// {Smallest, Largest}{Point,Range}Key) remain tight bounds that represent a
|
||||
// key at that exact bound. We make the effort to determine the next smallest
|
||||
// or largest key in an sstable after virtualizing it, to maintain this
|
||||
// tightness. If the largest is a sentinel key (IsExclusiveSentinel()), it
|
||||
// could mean that a rangedel or range key ends at that user key, or has been
|
||||
// truncated to that user key.
|
||||
// - One invariant is that if a rangedel or range key is truncated on its
|
||||
// upper bound, the virtual sstable *must* have a rangedel or range key
|
||||
// sentinel key as its upper bound. This is because truncation yields
|
||||
// an exclusive upper bound for the rangedel/rangekey, and if there are
|
||||
// any points at that exclusive upper bound within the same virtual
|
||||
// sstable, those could get uncovered by this truncation. We enforce this
|
||||
// invariant in calls to keyspan.Truncate.
|
||||
// - Size is an estimate of the size of the virtualized portion of this sstable.
|
||||
// The underlying file's size is stored in FileBacking.Size, though it could
|
||||
// also be estimated or could correspond to just the referenced portion of
|
||||
// a file (eg. if the file originated on another node).
|
||||
// - Size must be > 0.
|
||||
// - SmallestSeqNum and LargestSeqNum are loose bounds for virtual sstables.
|
||||
// This means that all keys in the virtual sstable must have seqnums within
|
||||
// [SmallestSeqNum, LargestSeqNum], however there's no guarantee that there's
|
||||
// a key with a seqnum at either of the bounds. Calculating tight seqnum
|
||||
// bounds would be too expensive and deliver little value.
|
||||
//
|
||||
// NB: This type should only be constructed by calling FileMetadata.VirtualMeta.
|
||||
type VirtualFileMeta struct {
|
||||
*FileMetadata
|
||||
}
|
||||
|
||||
// PhysicalMeta should be the only source of creating the PhysicalFileMeta
|
||||
// wrapper type.
|
||||
func (m *FileMetadata) PhysicalMeta() PhysicalFileMeta {
|
||||
if m.Virtual {
|
||||
panic("pebble: file metadata does not belong to a physical sstable")
|
||||
}
|
||||
return PhysicalFileMeta{
|
||||
m,
|
||||
}
|
||||
}
|
||||
|
||||
// VirtualMeta should be the only source of creating the VirtualFileMeta wrapper
|
||||
// type.
|
||||
func (m *FileMetadata) VirtualMeta() VirtualFileMeta {
|
||||
if !m.Virtual {
|
||||
panic("pebble: file metadata does not belong to a virtual sstable")
|
||||
}
|
||||
return VirtualFileMeta{
|
||||
m,
|
||||
}
|
||||
}
|
||||
|
||||
// FileBacking either backs a single physical sstable, or one or more virtual
|
||||
// sstables.
|
||||
//
|
||||
// See the comment above the FileMetadata type for sstable terminology.
|
||||
type FileBacking struct {
|
||||
// Reference count for the backing file on disk: incremented when a
|
||||
// physical or virtual sstable which is backed by the FileBacking is
|
||||
// added to a version and decremented when the version is unreferenced.
|
||||
// We ref count in order to determine when it is safe to delete a
|
||||
// backing sst file from disk. The backing file is obsolete when the
|
||||
// reference count falls to zero.
|
||||
refs atomic.Int32
|
||||
// latestVersionRefs are the references to the FileBacking in the
|
||||
// latest version. This reference can be through a single physical
|
||||
// sstable in the latest version, or one or more virtual sstables in the
|
||||
// latest version.
|
||||
//
|
||||
// INVARIANT: latestVersionRefs <= refs.
|
||||
latestVersionRefs atomic.Int32
|
||||
// VirtualizedSize is set iff the backing sst is only referred to by
|
||||
// virtual ssts in the latest version. VirtualizedSize is the sum of the
|
||||
// virtual sstable sizes of all of the virtual sstables in the latest
|
||||
// version which are backed by the physical sstable. When a virtual
|
||||
// sstable is removed from the latest version, we will decrement the
|
||||
// VirtualizedSize. During compaction picking, we'll compensate a
|
||||
// virtual sstable file size by
|
||||
// (FileBacking.Size - FileBacking.VirtualizedSize) / latestVersionRefs.
|
||||
// The intuition is that if FileBacking.Size - FileBacking.VirtualizedSize
|
||||
// is high, then the space amplification due to virtual sstables is
|
||||
// high, and we should pick the virtual sstable with a higher priority.
|
||||
//
|
||||
// TODO(bananabrick): Compensate the virtual sstable file size using
|
||||
// the VirtualizedSize during compaction picking and test.
|
||||
VirtualizedSize atomic.Uint64
|
||||
DiskFileNum base.DiskFileNum
|
||||
Size uint64
|
||||
}
|
||||
|
||||
// InitPhysicalBacking allocates and sets the FileBacking which is required by a
|
||||
// physical sstable FileMetadata.
|
||||
//
|
||||
// Ensure that the state required by FileBacking, such as the FileNum, is
|
||||
// already set on the FileMetadata before InitPhysicalBacking is called.
|
||||
// Calling InitPhysicalBacking only after the relevant state has been set in the
|
||||
// FileMetadata is not necessary in tests which don't rely on FileBacking.
|
||||
func (m *FileMetadata) InitPhysicalBacking() {
|
||||
if m.Virtual {
|
||||
panic("pebble: virtual sstables should use a pre-existing FileBacking")
|
||||
}
|
||||
if m.FileBacking == nil {
|
||||
m.FileBacking = &FileBacking{Size: m.Size, DiskFileNum: m.FileNum.DiskFileNum()}
|
||||
}
|
||||
}
|
||||
|
||||
// InitProviderBacking creates a new FileBacking for a file backed by
|
||||
// an objstorage.Provider.
|
||||
func (m *FileMetadata) InitProviderBacking(fileNum base.DiskFileNum) {
|
||||
if !m.Virtual {
|
||||
panic("pebble: provider-backed sstables must be virtual")
|
||||
}
|
||||
if m.FileBacking == nil {
|
||||
m.FileBacking = &FileBacking{DiskFileNum: fileNum}
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateVirtual should be called once the FileMetadata for a virtual sstable
|
||||
// is created to verify that the fields of the virtual sstable are sound.
|
||||
func (m *FileMetadata) ValidateVirtual(createdFrom *FileMetadata) {
|
||||
if !m.Virtual {
|
||||
panic("pebble: invalid virtual sstable")
|
||||
}
|
||||
|
||||
if createdFrom.SmallestSeqNum != m.SmallestSeqNum {
|
||||
panic("pebble: invalid smallest sequence number for virtual sstable")
|
||||
}
|
||||
|
||||
if createdFrom.LargestSeqNum != m.LargestSeqNum {
|
||||
panic("pebble: invalid largest sequence number for virtual sstable")
|
||||
}
|
||||
|
||||
if createdFrom.FileBacking != nil && createdFrom.FileBacking != m.FileBacking {
|
||||
panic("pebble: invalid physical sstable state for virtual sstable")
|
||||
}
|
||||
|
||||
if m.Size == 0 {
|
||||
panic("pebble: virtual sstable size must be set upon creation")
|
||||
}
|
||||
}
|
||||
|
||||
// Refs returns the refcount of backing sstable.
|
||||
func (m *FileMetadata) Refs() int32 {
|
||||
return m.FileBacking.refs.Load()
|
||||
}
|
||||
|
||||
// Ref increments the ref count associated with the backing sstable.
|
||||
func (m *FileMetadata) Ref() {
|
||||
m.FileBacking.refs.Add(1)
|
||||
}
|
||||
|
||||
// Unref decrements the ref count associated with the backing sstable.
|
||||
func (m *FileMetadata) Unref() int32 {
|
||||
v := m.FileBacking.refs.Add(-1)
|
||||
if invariants.Enabled && v < 0 {
|
||||
panic("pebble: invalid FileMetadata refcounting")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// LatestRef increments the latest ref count associated with the backing
|
||||
// sstable.
|
||||
func (m *FileMetadata) LatestRef() {
|
||||
m.FileBacking.latestVersionRefs.Add(1)
|
||||
|
||||
if m.Virtual {
|
||||
m.FileBacking.VirtualizedSize.Add(m.Size)
|
||||
}
|
||||
}
|
||||
|
||||
// LatestUnref decrements the latest ref count associated with the backing
|
||||
// sstable.
|
||||
func (m *FileMetadata) LatestUnref() int32 {
|
||||
if m.Virtual {
|
||||
m.FileBacking.VirtualizedSize.Add(-m.Size)
|
||||
}
|
||||
|
||||
v := m.FileBacking.latestVersionRefs.Add(-1)
|
||||
if invariants.Enabled && v < 0 {
|
||||
panic("pebble: invalid FileMetadata latest refcounting")
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// LatestRefs returns the latest ref count associated with the backing sstable.
|
||||
func (m *FileMetadata) LatestRefs() int32 {
|
||||
return m.FileBacking.latestVersionRefs.Load()
|
||||
}
|
||||
|
||||
// SetCompactionState transitions this file's compaction state to the given
|
||||
@@ -263,22 +509,14 @@ func (m *FileMetadata) IsCompacting() bool {
|
||||
// returns true, the Stats field may be read (with or without holding the
|
||||
// database mutex).
|
||||
func (m *FileMetadata) StatsValid() bool {
|
||||
return atomic.LoadUint32(&m.Atomic.statsValid) == 1
|
||||
}
|
||||
|
||||
// StatsValidLocked returns true if the table stats have been populated.
|
||||
// StatsValidLocked requires DB.mu is held when it's invoked, and it avoids the
|
||||
// overhead of an atomic load. This is possible because table stats validity is
|
||||
// only set while DB.mu is held.
|
||||
func (m *FileMetadata) StatsValidLocked() bool {
|
||||
return m.Atomic.statsValid == 1
|
||||
return m.statsValid.Load()
|
||||
}
|
||||
|
||||
// StatsMarkValid marks the TableStats as valid. The caller must hold DB.mu
|
||||
// while populating TableStats and calling StatsMarkValud. Once stats are
|
||||
// populated, they must not be mutated.
|
||||
func (m *FileMetadata) StatsMarkValid() {
|
||||
atomic.StoreUint32(&m.Atomic.statsValid, 1)
|
||||
m.statsValid.Store(true)
|
||||
}
|
||||
|
||||
// ExtendPointKeyBounds attempts to extend the lower and upper point key bounds
|
||||
@@ -369,6 +607,13 @@ func (m *FileMetadata) Overlaps(cmp Compare, start []byte, end []byte, exclusive
|
||||
return true
|
||||
}
|
||||
|
||||
// ContainedWithinSpan returns true if the file key range completely overlaps with the
|
||||
// given range ("end" is assumed to exclusive).
|
||||
func (m *FileMetadata) ContainedWithinSpan(cmp Compare, start, end []byte) bool {
|
||||
lowerCmp, upperCmp := cmp(m.Smallest.UserKey, start), cmp(m.Largest.UserKey, end)
|
||||
return lowerCmp >= 0 && (upperCmp < 0 || (upperCmp == 0 && m.Largest.IsExclusiveSentinel()))
|
||||
}
|
||||
|
||||
// ContainsKeyType returns whether or not the file contains keys of the provided
|
||||
// type.
|
||||
func (m *FileMetadata) ContainsKeyType(kt KeyType) bool {
|
||||
@@ -466,6 +711,7 @@ func (m *FileMetadata) DebugString(format base.FormatKey, verbose bool) string {
|
||||
if !verbose {
|
||||
return b.String()
|
||||
}
|
||||
fmt.Fprintf(&b, " seqnums:[%d-%d]", m.SmallestSeqNum, m.LargestSeqNum)
|
||||
if m.HasPointKeys {
|
||||
fmt.Fprintf(&b, " points:[%s-%s]",
|
||||
m.SmallestPointKey.Pretty(format), m.LargestPointKey.Pretty(format))
|
||||
@@ -479,9 +725,9 @@ func (m *FileMetadata) DebugString(format base.FormatKey, verbose bool) string {
|
||||
|
||||
// ParseFileMetadataDebug parses a FileMetadata from its DebugString
|
||||
// representation.
|
||||
func ParseFileMetadataDebug(s string) (m FileMetadata, err error) {
|
||||
func ParseFileMetadataDebug(s string) (*FileMetadata, error) {
|
||||
// Split lines of the form:
|
||||
// 000000:[a#0,SET-z#0,SET] points:[...] ranges:[...]
|
||||
// 000000:[a#0,SET-z#0,SET] seqnums:[5-5] points:[...] ranges:[...]
|
||||
fields := strings.FieldsFunc(s, func(c rune) bool {
|
||||
switch c {
|
||||
case ':', '[', '-', ']':
|
||||
@@ -491,10 +737,24 @@ func ParseFileMetadataDebug(s string) (m FileMetadata, err error) {
|
||||
}
|
||||
})
|
||||
if len(fields)%3 != 0 {
|
||||
return m, errors.Newf("malformed input: %s", s)
|
||||
return nil, errors.Newf("malformed input: %s", s)
|
||||
}
|
||||
m := &FileMetadata{}
|
||||
for len(fields) > 0 {
|
||||
prefix := fields[0]
|
||||
if prefix == "seqnums" {
|
||||
smallestSeqNum, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
return m, errors.Newf("malformed input: %s: %s", s, err)
|
||||
}
|
||||
largestSeqNum, err := strconv.ParseUint(fields[2], 10, 64)
|
||||
if err != nil {
|
||||
return m, errors.Newf("malformed input: %s: %s", s, err)
|
||||
}
|
||||
m.SmallestSeqNum, m.LargestSeqNum = smallestSeqNum, largestSeqNum
|
||||
fields = fields[3:]
|
||||
continue
|
||||
}
|
||||
smallest := base.ParsePrettyInternalKey(fields[1])
|
||||
largest := base.ParsePrettyInternalKey(fields[2])
|
||||
switch prefix {
|
||||
@@ -522,7 +782,8 @@ func ParseFileMetadataDebug(s string) (m FileMetadata, err error) {
|
||||
m.SmallestPointKey, m.LargestPointKey = m.Smallest, m.Largest
|
||||
m.HasPointKeys = true
|
||||
}
|
||||
return
|
||||
m.InitPhysicalBacking()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Validate validates the metadata for consistency with itself, returning an
|
||||
@@ -584,6 +845,11 @@ func (m *FileMetadata) Validate(cmp Compare, formatKey base.FormatKey) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that FileMetadata.Init was called.
|
||||
if m.FileBacking == nil {
|
||||
return base.CorruptionErrorf("file metadata FileBacking not set")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -756,6 +1022,9 @@ func NewVersion(
|
||||
} else {
|
||||
v.Levels[l].tree.cmp = btreeCmpSmallestKey(cmp)
|
||||
}
|
||||
for _, f := range files[l] {
|
||||
v.Levels[l].totalSize += f.Size
|
||||
}
|
||||
}
|
||||
if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
|
||||
panic(err)
|
||||
@@ -788,7 +1057,7 @@ func NewVersion(
|
||||
// key in a higher level table that has both the same user key and a higher
|
||||
// sequence number.
|
||||
type Version struct {
|
||||
refs int32
|
||||
refs atomic.Int32
|
||||
|
||||
// The level 0 sstables are organized in a series of sublevels. Similar to
|
||||
// the seqnum invariant in normal levels, there is no internal key in a
|
||||
@@ -805,7 +1074,7 @@ type Version struct {
|
||||
// usage.
|
||||
//
|
||||
// L0Sublevels.Levels contains L0 files ordered by sublevels. All the files
|
||||
// in Files[0] are in L0Sublevels.Levels. L0SublevelFiles is also set to
|
||||
// in Levels[0] are in L0Sublevels.Levels. L0SublevelFiles is also set to
|
||||
// a reference to that slice, as that slice is necessary for iterator
|
||||
// creation and needs to outlast L0Sublevels.
|
||||
L0Sublevels *L0Sublevels
|
||||
@@ -820,7 +1089,7 @@ type Version struct {
|
||||
|
||||
// The callback to invoke when the last reference to a version is
|
||||
// removed. Will be called with list.mu held.
|
||||
Deleted func(obsolete []*FileMetadata)
|
||||
Deleted func(obsolete []*FileBacking)
|
||||
|
||||
// Stats holds aggregated stats about the version maintained from
|
||||
// version to version.
|
||||
@@ -849,15 +1118,21 @@ func (v *Version) DebugString(format base.FormatKey) string {
|
||||
return v.string(format, true)
|
||||
}
|
||||
|
||||
func describeSublevels(format base.FormatKey, verbose bool, sublevels []LevelSlice) string {
|
||||
var buf bytes.Buffer
|
||||
for sublevel := len(sublevels) - 1; sublevel >= 0; sublevel-- {
|
||||
fmt.Fprintf(&buf, "0.%d:\n", sublevel)
|
||||
sublevels[sublevel].Each(func(f *FileMetadata) {
|
||||
fmt.Fprintf(&buf, " %s\n", f.DebugString(format, verbose))
|
||||
})
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (v *Version) string(format base.FormatKey, verbose bool) string {
|
||||
var buf bytes.Buffer
|
||||
if len(v.L0SublevelFiles) > 0 {
|
||||
for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- {
|
||||
fmt.Fprintf(&buf, "0.%d:\n", sublevel)
|
||||
v.L0SublevelFiles[sublevel].Each(func(f *FileMetadata) {
|
||||
fmt.Fprintf(&buf, " %s\n", f.DebugString(format, verbose))
|
||||
})
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s", describeSublevels(format, verbose, v.L0SublevelFiles))
|
||||
}
|
||||
for level := 1; level < NumLevels; level++ {
|
||||
if v.Levels[level].Empty() {
|
||||
@@ -898,7 +1173,7 @@ func ParseVersionDebug(
|
||||
m.SmallestPointKey, m.LargestPointKey = m.Smallest, m.Largest
|
||||
m.HasPointKeys = true
|
||||
}
|
||||
files[level] = append(files[level], &m)
|
||||
files[level] = append(files[level], m)
|
||||
}
|
||||
}
|
||||
// Reverse the order of L0 files. This ensures we construct the same
|
||||
@@ -912,12 +1187,12 @@ func ParseVersionDebug(
|
||||
|
||||
// Refs returns the number of references to the version.
|
||||
func (v *Version) Refs() int32 {
|
||||
return atomic.LoadInt32(&v.refs)
|
||||
return v.refs.Load()
|
||||
}
|
||||
|
||||
// Ref increments the version refcount.
|
||||
func (v *Version) Ref() {
|
||||
atomic.AddInt32(&v.refs, 1)
|
||||
v.refs.Add(1)
|
||||
}
|
||||
|
||||
// Unref decrements the version refcount. If the last reference to the version
|
||||
@@ -925,12 +1200,11 @@ func (v *Version) Ref() {
|
||||
// Deleted callback is invoked. Requires that the VersionList mutex is NOT
|
||||
// locked.
|
||||
func (v *Version) Unref() {
|
||||
if atomic.AddInt32(&v.refs, -1) == 0 {
|
||||
obsolete := v.unrefFiles()
|
||||
if v.refs.Add(-1) == 0 {
|
||||
l := v.list
|
||||
l.mu.Lock()
|
||||
l.Remove(v)
|
||||
v.Deleted(obsolete)
|
||||
v.Deleted(v.unrefFiles())
|
||||
l.mu.Unlock()
|
||||
}
|
||||
}
|
||||
@@ -940,14 +1214,14 @@ func (v *Version) Unref() {
|
||||
// the Deleted callback is invoked. Requires that the VersionList mutex is
|
||||
// already locked.
|
||||
func (v *Version) UnrefLocked() {
|
||||
if atomic.AddInt32(&v.refs, -1) == 0 {
|
||||
if v.refs.Add(-1) == 0 {
|
||||
v.list.Remove(v)
|
||||
v.Deleted(v.unrefFiles())
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Version) unrefFiles() []*FileMetadata {
|
||||
var obsolete []*FileMetadata
|
||||
func (v *Version) unrefFiles() []*FileBacking {
|
||||
var obsolete []*FileBacking
|
||||
for _, lm := range v.Levels {
|
||||
obsolete = append(obsolete, lm.release()...)
|
||||
}
|
||||
@@ -1079,16 +1353,20 @@ func (v *Version) Overlaps(
|
||||
// CheckOrdering checks that the files are consistent with respect to
|
||||
// increasing file numbers (for level 0 files) and increasing and non-
|
||||
// overlapping internal key ranges (for level non-0 files).
|
||||
func (v *Version) CheckOrdering(cmp Compare, format base.FormatKey) error {
|
||||
func (v *Version) CheckOrdering(
|
||||
cmp Compare, format base.FormatKey, order OrderingInvariants,
|
||||
) error {
|
||||
for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- {
|
||||
sublevelIter := v.L0SublevelFiles[sublevel].Iter()
|
||||
if err := CheckOrdering(cmp, format, L0Sublevel(sublevel), sublevelIter); err != nil {
|
||||
// Sublevels have NEVER allowed split user keys, so we can pass
|
||||
// ProhibitSplitUserKeys.
|
||||
if err := CheckOrdering(cmp, format, L0Sublevel(sublevel), sublevelIter, ProhibitSplitUserKeys); err != nil {
|
||||
return base.CorruptionErrorf("%s\n%s", err, v.DebugString(format))
|
||||
}
|
||||
}
|
||||
|
||||
for level, lm := range v.Levels {
|
||||
if err := CheckOrdering(cmp, format, Level(level), lm.Iter()); err != nil {
|
||||
if err := CheckOrdering(cmp, format, Level(level), lm.Iter(), order); err != nil {
|
||||
return base.CorruptionErrorf("%s\n%s", err, v.DebugString(format))
|
||||
}
|
||||
}
|
||||
@@ -1157,10 +1435,34 @@ func (l *VersionList) Remove(v *Version) {
|
||||
v.list = nil // avoid memory leaks
|
||||
}
|
||||
|
||||
// OrderingInvariants dictates the file ordering invariants active.
|
||||
type OrderingInvariants int8
|
||||
|
||||
const (
|
||||
// ProhibitSplitUserKeys indicates that adjacent files within a level cannot
|
||||
// contain the same user key.
|
||||
ProhibitSplitUserKeys OrderingInvariants = iota
|
||||
// AllowSplitUserKeys indicates that adjacent files within a level may
|
||||
// contain the same user key. This is only allowed by historical format
|
||||
// major versions.
|
||||
//
|
||||
// TODO(jackson): Remove.
|
||||
AllowSplitUserKeys
|
||||
)
|
||||
|
||||
// CheckOrdering checks that the files are consistent with respect to
|
||||
// seqnums (for level 0 files -- see detailed comment below) and increasing and non-
|
||||
// overlapping internal key ranges (for non-level 0 files).
|
||||
func CheckOrdering(cmp Compare, format base.FormatKey, level Level, files LevelIterator) error {
|
||||
//
|
||||
// The ordering field may be passed AllowSplitUserKeys to allow adjacent files that are both
|
||||
// inclusive of the same user key. Pebble no longer creates version edits
|
||||
// installing such files, and Pebble databases with sufficiently high format
|
||||
// major version should no longer have any such files within their LSM.
|
||||
// TODO(jackson): Remove AllowSplitUserKeys when we remove support for the
|
||||
// earlier format major versions.
|
||||
func CheckOrdering(
|
||||
cmp Compare, format base.FormatKey, level Level, files LevelIterator, ordering OrderingInvariants,
|
||||
) error {
|
||||
// The invariants to check for L0 sublevels are the same as the ones to
|
||||
// check for all other levels. However, if L0 is not organized into
|
||||
// sublevels, or if all L0 files are being passed in, we do the legacy L0
|
||||
@@ -1238,11 +1540,29 @@ func CheckOrdering(cmp Compare, format base.FormatKey, level Level, files LevelI
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
}
|
||||
if base.InternalCompare(cmp, prev.Largest, f.Smallest) >= 0 {
|
||||
return base.CorruptionErrorf("%s files %s and %s have overlapping ranges: [%s-%s] vs [%s-%s]",
|
||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
|
||||
// What's considered "overlapping" is dependent on the format
|
||||
// major version. If ordering=ProhibitSplitUserKeys, then both
|
||||
// files cannot contain keys with the same user keys. If the
|
||||
// bounds have the same user key, the previous file's boundary
|
||||
// must have a Trailer indicating that it's exclusive.
|
||||
switch ordering {
|
||||
case AllowSplitUserKeys:
|
||||
if base.InternalCompare(cmp, prev.Largest, f.Smallest) >= 0 {
|
||||
return base.CorruptionErrorf("%s files %s and %s have overlapping ranges: [%s-%s] vs [%s-%s]",
|
||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
}
|
||||
case ProhibitSplitUserKeys:
|
||||
if v := cmp(prev.Largest.UserKey, f.Smallest.UserKey); v > 0 || (v == 0 && !prev.Largest.IsExclusiveSentinel()) {
|
||||
return base.CorruptionErrorf("%s files %s and %s have overlapping ranges: [%s-%s] vs [%s-%s]",
|
||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
403
vendor/github.com/cockroachdb/pebble/internal/manifest/version_edit.go
generated
vendored
403
vendor/github.com/cockroachdb/pebble/internal/manifest/version_edit.go
generated
vendored
@@ -11,11 +11,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"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++
|
||||
@@ -51,7 +51,9 @@ const (
|
||||
tagMaxColumnFamily = 203
|
||||
|
||||
// Pebble tags.
|
||||
tagNewFile5 = 104 // Range keys.
|
||||
tagNewFile5 = 104 // Range keys.
|
||||
tagCreatedBackingTable = 105
|
||||
tagRemovedBackingTable = 106
|
||||
|
||||
// The custom tags sub-format used by tagNewFile4 and above.
|
||||
customTagTerminate = 1
|
||||
@@ -59,6 +61,7 @@ const (
|
||||
customTagCreationTime = 6
|
||||
customTagPathID = 65
|
||||
customTagNonSafeIgnoreMask = 1 << 6
|
||||
customTagVirtual = 66
|
||||
)
|
||||
|
||||
// DeletedFileEntry holds the state for a file deletion from a level. The file
|
||||
@@ -73,6 +76,9 @@ type DeletedFileEntry struct {
|
||||
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
|
||||
@@ -110,9 +116,42 @@ type VersionEdit struct {
|
||||
// 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 {
|
||||
@@ -165,6 +204,28 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
// 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 {
|
||||
@@ -270,6 +331,10 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
var markedForCompaction bool
|
||||
var creationTime uint64
|
||||
virtualState := struct {
|
||||
virtual bool
|
||||
backingFileNum uint64
|
||||
}{}
|
||||
if tag == tagNewFile4 || tag == tagNewFile5 {
|
||||
for {
|
||||
customTag, err := d.readUvarint()
|
||||
@@ -278,7 +343,16 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
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
|
||||
@@ -314,6 +388,7 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
SmallestSeqNum: smallestSeqNum,
|
||||
LargestSeqNum: largestSeqNum,
|
||||
MarkedForCompaction: markedForCompaction,
|
||||
Virtual: virtualState.virtual,
|
||||
}
|
||||
if tag != tagNewFile5 { // no range keys present
|
||||
m.SmallestPointKey = base.DecodeInternalKey(smallestPointKey)
|
||||
@@ -345,10 +420,18 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
}
|
||||
m.boundsSet = true
|
||||
v.NewFiles = append(v.NewFiles, NewFileEntry{
|
||||
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()
|
||||
@@ -367,8 +450,7 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer for a VersionEdit.
|
||||
func (v *VersionEdit) String() string {
|
||||
func (v *VersionEdit) string(verbose bool, fmtKey base.FormatKey) string {
|
||||
var buf bytes.Buffer
|
||||
if v.ComparerName != "" {
|
||||
fmt.Fprintf(&buf, " comparer: %s", v.ComparerName)
|
||||
@@ -399,7 +481,12 @@ func (v *VersionEdit) String() string {
|
||||
fmt.Fprintf(&buf, " deleted: L%d %s\n", df.Level, df.FileNum)
|
||||
}
|
||||
for _, nf := range v.NewFiles {
|
||||
fmt.Fprintf(&buf, " added: L%d %s", nf.Level, nf.Meta.String())
|
||||
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))
|
||||
@@ -409,6 +496,16 @@ func (v *VersionEdit) String() string {
|
||||
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)}
|
||||
@@ -429,6 +526,15 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
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.
|
||||
@@ -442,7 +548,7 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
e.writeUvarint(uint64(x.FileNum))
|
||||
}
|
||||
for _, x := range v.NewFiles {
|
||||
customFields := x.Meta.MarkedForCompaction || x.Meta.CreationTime != 0
|
||||
customFields := x.Meta.MarkedForCompaction || x.Meta.CreationTime != 0 || x.Meta.Virtual
|
||||
var tag uint64
|
||||
switch {
|
||||
case x.Meta.HasRangeKeys:
|
||||
@@ -495,6 +601,10 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -502,6 +612,7 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// versionEditDecoder should be used to decode version edits.
|
||||
type versionEditDecoder struct {
|
||||
byteReader
|
||||
}
|
||||
@@ -581,10 +692,30 @@ func (e versionEditEncoder) writeUvarint(u uint64) {
|
||||
|
||||
// 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][]*FileMetadata
|
||||
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
|
||||
@@ -603,6 +734,19 @@ type BulkVersionEdit struct {
|
||||
|
||||
// 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]
|
||||
@@ -624,7 +768,28 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
||||
if m.MarkedForCompaction {
|
||||
b.MarkedForCompactionCountDiff--
|
||||
}
|
||||
dmap[df.FileNum] = m
|
||||
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 {
|
||||
@@ -635,7 +800,21 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
||||
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 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
|
||||
}
|
||||
@@ -643,36 +822,101 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
||||
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, 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.
|
||||
// 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,
|
||||
) (_ *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) {
|
||||
zombies map[base.DiskFileNum]uint64,
|
||||
orderingInvariants OrderingInvariants,
|
||||
) (*Version, error) {
|
||||
addZombie := func(state *FileBacking) {
|
||||
if zombies != nil {
|
||||
delete(zombies, fileNum)
|
||||
zombies[state.DiskFileNum] = state.Size
|
||||
}
|
||||
}
|
||||
removeZombie := func(state *FileBacking) {
|
||||
if zombies != nil {
|
||||
delete(zombies, state.DiskFileNum)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,7 +928,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
}
|
||||
v.Stats.MarkedForCompaction += b.MarkedForCompactionCountDiff
|
||||
if v.Stats.MarkedForCompaction < 0 {
|
||||
return nil, nil, base.CorruptionErrorf("pebble: version marked for compaction count negative")
|
||||
return nil, base.CorruptionErrorf("pebble: version marked for compaction count negative")
|
||||
}
|
||||
|
||||
for level := range v.Levels {
|
||||
@@ -705,7 +949,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
// 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")
|
||||
return nil, errors.Wrap(err, "pebble: internal error")
|
||||
}
|
||||
} else {
|
||||
v.L0Sublevels = curr.L0Sublevels
|
||||
@@ -718,47 +962,70 @@ func (b *BulkVersionEdit) Apply(
|
||||
// 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(
|
||||
|
||||
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(deletedMap)))
|
||||
errors.Safe(len(deletedFilesMap)))
|
||||
}
|
||||
|
||||
// NB: addedFiles may be empty and it also is not necessarily
|
||||
// internally consistent: it does not reflect deletions in deletedMap.
|
||||
// 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 deletedMap {
|
||||
addZombie(f.FileNum, f.Size)
|
||||
if obsolete := v.Levels[level].tree.Delete(f); obsolete {
|
||||
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, nil, err
|
||||
return nil, err
|
||||
}
|
||||
if f.HasRangeKeys {
|
||||
if obsolete := v.RangeKeyLevels[level].tree.Delete(f); obsolete {
|
||||
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, nil, err
|
||||
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
|
||||
// addedFiles in any of the levels is referencing the backing sst.
|
||||
// This is possible if a physical sstable is virtualized, or if it
|
||||
// is moved.
|
||||
latestRefCount := f.LatestRefs()
|
||||
if latestRefCount <= 0 {
|
||||
// If a file is present in deletedFilesMap for a level, then it
|
||||
// must have already been added to the level previously, which
|
||||
// means that its latest ref count cannot be 0.
|
||||
err := errors.Errorf("pebble: internal error: incorrect latestRefs reference counting for file", f.FileNum)
|
||||
return nil, err
|
||||
} else if f.LatestUnref() == 0 {
|
||||
addZombie(f.FileBacking)
|
||||
}
|
||||
}
|
||||
|
||||
addedFiles := make([]*FileMetadata, 0, len(addedFilesMap))
|
||||
for _, f := range addedFilesMap {
|
||||
addedFiles = append(addedFiles, f)
|
||||
}
|
||||
// Sort addedFiles by file number. This isn't necessary, but tests which
|
||||
// replay invalid manifests check the error output, and the error output
|
||||
// depends on the order in which files are added to the btree.
|
||||
sort.Slice(addedFiles, func(i, j int) bool {
|
||||
return addedFiles[i].FileNum < addedFiles[j].FileNum
|
||||
})
|
||||
|
||||
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
|
||||
@@ -768,20 +1035,23 @@ func (b *BulkVersionEdit) Apply(
|
||||
if allowedSeeks < 100 {
|
||||
allowedSeeks = 100
|
||||
}
|
||||
atomic.StoreInt64(&f.Atomic.AllowedSeeks, allowedSeeks)
|
||||
f.AllowedSeeks.Store(allowedSeeks)
|
||||
f.InitAllowedSeeks = allowedSeeks
|
||||
|
||||
err := lm.tree.Insert(f)
|
||||
err := lm.insert(f)
|
||||
// We're adding this file to the new version, so increment the
|
||||
// latest refs count.
|
||||
f.LatestRef()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "pebble")
|
||||
return nil, errors.Wrap(err, "pebble")
|
||||
}
|
||||
if f.HasRangeKeys {
|
||||
err = lmRange.tree.Insert(f)
|
||||
err = lmRange.insert(f)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "pebble")
|
||||
return nil, errors.Wrap(err, "pebble")
|
||||
}
|
||||
}
|
||||
removeZombie(f.FileNum)
|
||||
removeZombie(f.FileBacking)
|
||||
// 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 {
|
||||
@@ -793,27 +1063,36 @@ func (b *BulkVersionEdit) Apply(
|
||||
}
|
||||
|
||||
if level == 0 {
|
||||
if curr != nil && curr.L0Sublevels != nil && len(deletedMap) == 0 {
|
||||
if curr != nil && curr.L0Sublevels != nil && len(deletedFilesMap) == 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)
|
||||
} else if invariants.Enabled && err == nil {
|
||||
copyOfSublevels, err := NewL0Sublevels(&v.Levels[0], cmp, formatKey, flushSplitBytes)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error when regenerating sublevels: %s", err))
|
||||
}
|
||||
s1 := describeSublevels(base.DefaultFormatter, false /* verbose */, copyOfSublevels.Levels)
|
||||
s2 := describeSublevels(base.DefaultFormatter, false /* verbose */, v.L0Sublevels.Levels)
|
||||
if s1 != s2 {
|
||||
panic(fmt.Sprintf("incremental L0 sublevel generation produced different output than regeneration: %s != %s", s1, s2))
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "pebble: internal error")
|
||||
return 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")
|
||||
return 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")
|
||||
if err := CheckOrdering(cmp, formatKey, Level(0), v.Levels[level].Iter(), orderingInvariants); err != nil {
|
||||
return nil, errors.Wrap(err, "pebble: internal error")
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -833,10 +1112,10 @@ func (b *BulkVersionEdit) Apply(
|
||||
end.Prev()
|
||||
}
|
||||
})
|
||||
if err := CheckOrdering(cmp, formatKey, Level(level), check.Iter()); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "pebble: internal error")
|
||||
if err := CheckOrdering(cmp, formatKey, Level(level), check.Iter(), orderingInvariants); err != nil {
|
||||
return nil, errors.Wrap(err, "pebble: internal error")
|
||||
}
|
||||
}
|
||||
}
|
||||
return v, zombies, nil
|
||||
return v, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user