Upgrade versions of vendored libs
This commit is contained in:
479
vendor/github.com/cockroachdb/pebble/table_stats.go
generated
vendored
479
vendor/github.com/cockroachdb/pebble/table_stats.go
generated
vendored
@@ -5,6 +5,7 @@
|
||||
package pebble
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
@@ -52,7 +53,7 @@ func (d *DB) maybeCollectTableStatsLocked() {
|
||||
func (d *DB) updateTableStatsLocked(newFiles []manifest.NewFileEntry) {
|
||||
var needStats bool
|
||||
for _, nf := range newFiles {
|
||||
if !nf.Meta.Stats.Valid {
|
||||
if !nf.Meta.StatsValidLocked() {
|
||||
needStats = true
|
||||
break
|
||||
}
|
||||
@@ -125,7 +126,8 @@ func (d *DB) collectTableStats() bool {
|
||||
maybeCompact := false
|
||||
for _, c := range collected {
|
||||
c.fileMetadata.Stats = c.TableStats
|
||||
maybeCompact = maybeCompact || c.fileMetadata.Stats.RangeDeletionsBytesEstimate > 0
|
||||
maybeCompact = maybeCompact || c.TableStats.RangeDeletionsBytesEstimate > 0
|
||||
c.fileMetadata.StatsMarkValid()
|
||||
}
|
||||
d.mu.tableStats.cond.Broadcast()
|
||||
d.maybeCollectTableStatsLocked()
|
||||
@@ -173,7 +175,7 @@ func (d *DB) loadNewFileStats(
|
||||
// collectTableStats updates f.Stats for active files, and we
|
||||
// ensure only one goroutine runs it at a time through
|
||||
// d.mu.tableStats.loading.
|
||||
if nf.Meta.Stats.Valid {
|
||||
if nf.Meta.StatsValidLocked() {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -215,9 +217,9 @@ func (d *DB) scanReadStateTableStats(
|
||||
// NB: We're not holding d.mu which protects f.Stats, but only the
|
||||
// active stats collection job updates f.Stats for active files,
|
||||
// and we ensure only one goroutine runs it at a time through
|
||||
// d.mu.tableStats.loading. This makes it safe to read
|
||||
// f.Stats.Valid despite not holding d.mu.
|
||||
if f.Stats.Valid {
|
||||
// d.mu.tableStats.loading. This makes it safe to read validity
|
||||
// through f.Stats.ValidLocked despite not holding d.mu.
|
||||
if f.StatsValidLocked() {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -261,7 +263,7 @@ func (d *DB) loadTableStats(
|
||||
return
|
||||
}
|
||||
}
|
||||
if r.Properties.NumRangeDeletions > 0 {
|
||||
if r.Properties.NumRangeDeletions > 0 || r.Properties.NumRangeKeyDels > 0 {
|
||||
if compactionHints, err = d.loadTableRangeDelStats(r, v, level, meta, &stats); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -269,13 +271,12 @@ func (d *DB) loadTableStats(
|
||||
// TODO(travers): Once we have real-world data, consider collecting
|
||||
// additional stats that may provide improved heuristics for compaction
|
||||
// picking.
|
||||
stats.NumRangeKeys = r.Properties.NumRangeKeys()
|
||||
stats.NumRangeKeySets = r.Properties.NumRangeKeySets
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
return stats, nil, err
|
||||
}
|
||||
stats.Valid = true
|
||||
return stats, compactionHints, nil
|
||||
}
|
||||
|
||||
@@ -299,68 +300,85 @@ func (d *DB) loadTablePointKeyStats(
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadTableRangeDelStats calculates the range deletion statistics for the given
|
||||
// table.
|
||||
// loadTableRangeDelStats calculates the range deletion and range key deletion
|
||||
// statistics for the given table.
|
||||
func (d *DB) loadTableRangeDelStats(
|
||||
r *sstable.Reader, v *version, level int, meta *fileMetadata, stats *manifest.TableStats,
|
||||
) ([]deleteCompactionHint, error) {
|
||||
var compactionHints []deleteCompactionHint
|
||||
// We iterate over the defragmented range tombstones, which ensures
|
||||
// we don't double count ranges deleted at different sequence numbers.
|
||||
// Also, merging abutting tombstones reduces the number of calls to
|
||||
// estimateSizeBeneath which is costly, and improves the accuracy of
|
||||
// our overall estimate.
|
||||
rangeDelIter, err := r.NewRawRangeDelIter()
|
||||
iter, err := newCombinedDeletionKeyspanIter(d.opts.Comparer, r, meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rangeDelIter.Close()
|
||||
// Truncate tombstones to the containing file's bounds if necessary.
|
||||
// See docs/range_deletions.md for why this is necessary.
|
||||
rangeDelIter = keyspan.Truncate(
|
||||
d.cmp, rangeDelIter, meta.Smallest.UserKey, meta.Largest.UserKey, nil, nil)
|
||||
err = foreachDefragmentedTombstone(rangeDelIter, d.cmp,
|
||||
func(startUserKey, endUserKey []byte, smallestSeqNum, largestSeqNum uint64) error {
|
||||
// If the file is in the last level of the LSM, there is no
|
||||
// data beneath it. The fact that there is still a range
|
||||
// tombstone in a bottommost file suggests that an open
|
||||
// snapshot kept the tombstone around. Estimate disk usage
|
||||
// within the file itself.
|
||||
if level == numLevels-1 {
|
||||
size, err := r.EstimateDiskUsage(startUserKey, endUserKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stats.RangeDeletionsBytesEstimate += size
|
||||
return nil
|
||||
defer iter.Close()
|
||||
var compactionHints []deleteCompactionHint
|
||||
// We iterate over the defragmented range tombstones and range key deletions,
|
||||
// which ensures we don't double count ranges deleted at different sequence
|
||||
// numbers. Also, merging abutting tombstones reduces the number of calls to
|
||||
// estimateReclaimedSizeBeneath which is costly, and improves the accuracy of
|
||||
// our overall estimate.
|
||||
for s := iter.First(); s != nil; s = iter.Next() {
|
||||
start, end := s.Start, s.End
|
||||
// We only need to consider deletion size estimates for tables that contain
|
||||
// point keys.
|
||||
var hasPoints bool
|
||||
for _, k := range s.Keys {
|
||||
if k.Kind() == base.InternalKeyKindRangeDelete {
|
||||
hasPoints = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
estimate, hintSeqNum, err := d.estimateSizeBeneath(v, level, meta, startUserKey, endUserKey)
|
||||
// If the file is in the last level of the LSM, there is no data beneath
|
||||
// it. The fact that there is still a range tombstone in a bottommost file
|
||||
// suggests that an open snapshot kept the tombstone around. Estimate disk
|
||||
// usage within the file itself.
|
||||
// NOTE: If the span `s` wholly contains a table containing range keys,
|
||||
// the returned size estimate will be slightly inflated by the range key
|
||||
// block. However, in practice, range keys are expected to be rare, and
|
||||
// the size of the range key block relative to the overall size of the
|
||||
// table is expected to be small.
|
||||
if hasPoints && level == numLevels-1 {
|
||||
size, err := r.EstimateDiskUsage(start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
stats.RangeDeletionsBytesEstimate += estimate
|
||||
stats.RangeDeletionsBytesEstimate += size
|
||||
|
||||
// If any files were completely contained with the range,
|
||||
// hintSeqNum is the smallest sequence number contained in any
|
||||
// such file.
|
||||
if hintSeqNum == math.MaxUint64 {
|
||||
return nil
|
||||
}
|
||||
hint := deleteCompactionHint{
|
||||
start: make([]byte, len(startUserKey)),
|
||||
end: make([]byte, len(endUserKey)),
|
||||
tombstoneFile: meta,
|
||||
tombstoneLevel: level,
|
||||
tombstoneLargestSeqNum: largestSeqNum,
|
||||
tombstoneSmallestSeqNum: smallestSeqNum,
|
||||
fileSmallestSeqNum: hintSeqNum,
|
||||
}
|
||||
copy(hint.start, startUserKey)
|
||||
copy(hint.end, endUserKey)
|
||||
compactionHints = append(compactionHints, hint)
|
||||
return nil
|
||||
})
|
||||
// As the file is in the bottommost level, there is no need to collect a
|
||||
// deletion hint.
|
||||
continue
|
||||
}
|
||||
|
||||
// While the size estimates for point keys should only be updated if this
|
||||
// span contains a range del, the sequence numbers are required for the
|
||||
// hint. Unconditionally descend, but conditionally update the estimates.
|
||||
hintType := compactionHintFromKeys(s.Keys)
|
||||
estimate, hintSeqNum, err := d.estimateReclaimedSizeBeneath(v, level, start, end, hintType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.RangeDeletionsBytesEstimate += estimate
|
||||
|
||||
// If any files were completely contained with the range,
|
||||
// hintSeqNum is the smallest sequence number contained in any
|
||||
// such file.
|
||||
if hintSeqNum == math.MaxUint64 {
|
||||
continue
|
||||
}
|
||||
hint := deleteCompactionHint{
|
||||
hintType: hintType,
|
||||
start: make([]byte, len(start)),
|
||||
end: make([]byte, len(end)),
|
||||
tombstoneFile: meta,
|
||||
tombstoneLevel: level,
|
||||
tombstoneLargestSeqNum: s.LargestSeqNum(),
|
||||
tombstoneSmallestSeqNum: s.SmallestSeqNum(),
|
||||
fileSmallestSeqNum: hintSeqNum,
|
||||
}
|
||||
copy(hint.start, start)
|
||||
copy(hint.end, end)
|
||||
compactionHints = append(compactionHints, hint)
|
||||
}
|
||||
return compactionHints, err
|
||||
}
|
||||
|
||||
@@ -410,8 +428,8 @@ func (d *DB) averageEntrySizeBeneath(
|
||||
return avgKeySize, avgValueSize, err
|
||||
}
|
||||
|
||||
func (d *DB) estimateSizeBeneath(
|
||||
v *version, level int, meta *fileMetadata, start, end []byte,
|
||||
func (d *DB) estimateReclaimedSizeBeneath(
|
||||
v *version, level int, start, end []byte, hintType deleteCompactionHintType,
|
||||
) (estimate uint64, hintSeqNum uint64, err error) {
|
||||
// Find all files in lower levels that overlap with the deleted range
|
||||
// [start, end).
|
||||
@@ -430,13 +448,58 @@ func (d *DB) estimateSizeBeneath(
|
||||
startCmp := d.cmp(start, file.Smallest.UserKey)
|
||||
endCmp := d.cmp(file.Largest.UserKey, end)
|
||||
if startCmp <= 0 && (endCmp < 0 || endCmp == 0 && file.Largest.IsExclusiveSentinel()) {
|
||||
// The range fully contains the file, so skip looking it up in
|
||||
// table cache/looking at its indexes and add the full file size.
|
||||
estimate += file.Size
|
||||
if hintSeqNum > file.SmallestSeqNum {
|
||||
// The range fully contains the file, so skip looking it up in table
|
||||
// cache/looking at its indexes and add the full file size. Whether the
|
||||
// disk estimate and hint seqnums are updated depends on a) the type of
|
||||
// hint that requested the estimate and b) the keys contained in this
|
||||
// current file.
|
||||
var updateEstimates, updateHints bool
|
||||
switch hintType {
|
||||
case deleteCompactionHintTypePointKeyOnly:
|
||||
// The range deletion byte estimates should only be updated if this
|
||||
// table contains point keys. This ends up being an overestimate in
|
||||
// the case that table also has range keys, but such keys are expected
|
||||
// to contribute a negligible amount of the table's overall size,
|
||||
// relative to point keys.
|
||||
if file.HasPointKeys {
|
||||
updateEstimates = true
|
||||
}
|
||||
// As the initiating span contained only range dels, hints can only be
|
||||
// updated if this table does _not_ contain range keys.
|
||||
if !file.HasRangeKeys {
|
||||
updateHints = true
|
||||
}
|
||||
case deleteCompactionHintTypeRangeKeyOnly:
|
||||
// The initiating span contained only range key dels. The estimates
|
||||
// apply only to point keys, and are therefore not updated.
|
||||
updateEstimates = false
|
||||
// As the initiating span contained only range key dels, hints can
|
||||
// only be updated if this table does _not_ contain point keys.
|
||||
if !file.HasPointKeys {
|
||||
updateHints = true
|
||||
}
|
||||
case deleteCompactionHintTypePointAndRangeKey:
|
||||
// Always update the estimates and hints, as this hint type can drop a
|
||||
// file, irrespective of the mixture of keys. Similar to above, the
|
||||
// range del bytes estimates is an overestimate.
|
||||
updateEstimates, updateHints = true, true
|
||||
default:
|
||||
panic(fmt.Sprintf("pebble: unknown hint type %s", hintType))
|
||||
}
|
||||
if updateEstimates {
|
||||
estimate += file.Size
|
||||
}
|
||||
if updateHints && hintSeqNum > file.SmallestSeqNum {
|
||||
hintSeqNum = file.SmallestSeqNum
|
||||
}
|
||||
} else if d.cmp(file.Smallest.UserKey, end) <= 0 && d.cmp(start, file.Largest.UserKey) <= 0 {
|
||||
// Partial overlap.
|
||||
if hintType == deleteCompactionHintTypeRangeKeyOnly {
|
||||
// If the hint that generated this overlap contains only range keys,
|
||||
// there is no need to calculate disk usage, as the reclaimable space
|
||||
// is expected to be minimal relative to point keys.
|
||||
continue
|
||||
}
|
||||
var size uint64
|
||||
err := d.tableCache.withReader(file, func(r *sstable.Reader) (err error) {
|
||||
size, err = r.EstimateDiskUsage(start, end)
|
||||
@@ -452,60 +515,19 @@ func (d *DB) estimateSizeBeneath(
|
||||
return estimate, hintSeqNum, nil
|
||||
}
|
||||
|
||||
func foreachDefragmentedTombstone(
|
||||
rangeDelIter keyspan.FragmentIterator,
|
||||
cmp base.Compare,
|
||||
fn func([]byte, []byte, uint64, uint64) error,
|
||||
) error {
|
||||
// Use an equals func that will always merge abutting spans.
|
||||
equal := func(_ base.Compare, _, _ keyspan.Span) bool { return true }
|
||||
// Reduce keys by maintaining a slice of length two, corresponding to the
|
||||
// largest and smallest keys in the defragmented span. This maintains the
|
||||
// contract that the emitted slice is sorted by (SeqNum, Kind) descending.
|
||||
reducer := func(current, incoming []keyspan.Key) []keyspan.Key {
|
||||
if len(current) == 0 && len(incoming) == 0 {
|
||||
// While this should never occur in practice, a defensive return is used
|
||||
// here to preserve correctness.
|
||||
return current
|
||||
}
|
||||
// Determine the smallest / largest keys across the two slices. As `current`
|
||||
// and `incoming` are both sorted by (seqnum, kind) descending, only the
|
||||
// first and last element of each slice need to be consulted.
|
||||
var largest, smallest keyspan.Key
|
||||
var largestSet, smallestSet bool
|
||||
for _, keys := range [2][]keyspan.Key{current, incoming} {
|
||||
if len(keys) == 0 {
|
||||
continue
|
||||
}
|
||||
first, last := keys[0], keys[len(keys)-1]
|
||||
// Update largest.
|
||||
if !largestSet || first.Trailer > largest.Trailer {
|
||||
largest, largestSet = first, true
|
||||
}
|
||||
// Update smallest.
|
||||
if !smallestSet || last.Trailer < smallest.Trailer {
|
||||
smallest, smallestSet = last, true
|
||||
}
|
||||
}
|
||||
return append(current[:0], largest, smallest)
|
||||
}
|
||||
iter := keyspan.DefragmentingIter{}
|
||||
iter.Init(cmp, rangeDelIter, equal, reducer)
|
||||
for s := iter.First(); s.Valid(); s = iter.Next() {
|
||||
if err := fn(s.Start, s.End, s.SmallestSeqNum(), s.LargestSeqNum()); err != nil {
|
||||
_ = iter.Close()
|
||||
return err
|
||||
}
|
||||
}
|
||||
return iter.Close()
|
||||
}
|
||||
|
||||
func maybeSetStatsFromProperties(meta *fileMetadata, props *sstable.Properties) bool {
|
||||
// If a table has range deletions, we can't calculate the
|
||||
// RangeDeletionsBytesEstimate statistic and can't populate table stats
|
||||
// from just the properties. The table stats collector goroutine will
|
||||
// populate the stats.
|
||||
if props.NumRangeDeletions != 0 {
|
||||
// If a table contains range deletions or range key deletions, we defer the
|
||||
// stats collection. There are two main reasons for this:
|
||||
//
|
||||
// 1. Estimating the potential for reclaimed space due to a range deletion
|
||||
// tombstone requires scanning the LSM - a potentially expensive operation
|
||||
// that should be deferred.
|
||||
// 2. Range deletions and / or range key deletions present an opportunity to
|
||||
// compute "deletion hints", which also requires a scan of the LSM to
|
||||
// compute tables that would be eligible for deletion.
|
||||
//
|
||||
// These two tasks are deferred to the table stats collector goroutine.
|
||||
if props.NumRangeDeletions != 0 || props.NumRangeKeyDels != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -528,14 +550,12 @@ func maybeSetStatsFromProperties(meta *fileMetadata, props *sstable.Properties)
|
||||
pointEstimate = pointDeletionsBytesEstimate(props, avgKeySize, avgValSize)
|
||||
}
|
||||
|
||||
meta.Stats = manifest.TableStats{
|
||||
Valid: true,
|
||||
NumEntries: props.NumEntries,
|
||||
NumDeletions: props.NumDeletions,
|
||||
NumRangeKeys: props.NumRangeKeys(),
|
||||
PointDeletionsBytesEstimate: pointEstimate,
|
||||
RangeDeletionsBytesEstimate: 0,
|
||||
}
|
||||
meta.Stats.NumEntries = props.NumEntries
|
||||
meta.Stats.NumDeletions = props.NumDeletions
|
||||
meta.Stats.NumRangeKeySets = props.NumRangeKeySets
|
||||
meta.Stats.PointDeletionsBytesEstimate = pointEstimate
|
||||
meta.Stats.RangeDeletionsBytesEstimate = 0
|
||||
meta.StatsMarkValid()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -581,3 +601,208 @@ func estimateEntrySizes(
|
||||
avgValSize = props.RawValueSize * fileSizePerEntry / uncompressedSum
|
||||
return avgKeySize, avgValSize
|
||||
}
|
||||
|
||||
// newCombinedDeletionKeyspanIter returns a keyspan.FragmentIterator that
|
||||
// returns "ranged deletion" spans for a single table, providing a combined view
|
||||
// of both range deletion and range key deletion spans. The
|
||||
// tableRangedDeletionIter is intended for use in the specific case of computing
|
||||
// the statistics and deleteCompactionHints for a single table.
|
||||
//
|
||||
// As an example, consider the following set of spans from the range deletion
|
||||
// and range key blocks of a table:
|
||||
//
|
||||
// |---------| |---------| |-------| RANGEKEYDELs
|
||||
// |-----------|-------------| |-----| RANGEDELs
|
||||
// __________________________________________________________
|
||||
// a b c d e f g h i j k l m n o p q r s t u v w x y z
|
||||
//
|
||||
// The tableRangedDeletionIter produces the following set of output spans, where
|
||||
// '1' indicates a span containing only range deletions, '2' is a span
|
||||
// containing only range key deletions, and '3' is a span containing a mixture
|
||||
// of both range deletions and range key deletions.
|
||||
//
|
||||
// 1 3 1 3 2 1 3 2
|
||||
// |-----|---------|-----|---|-----| |---|-|-----|
|
||||
// __________________________________________________________
|
||||
// a b c d e f g h i j k l m n o p q r s t u v w x y z
|
||||
//
|
||||
// Algorithm.
|
||||
//
|
||||
// The iterator first defragments the range deletion and range key blocks
|
||||
// separately. During this defragmentation, the range key block is also filtered
|
||||
// so that keys other than range key deletes are ignored. The range delete and
|
||||
// range key delete keyspaces are then merged.
|
||||
//
|
||||
// Note that the only fragmentation introduced by merging is from where a range
|
||||
// del span overlaps with a range key del span. Within the bounds of any overlap
|
||||
// there is guaranteed to be no further fragmentation, as the constituent spans
|
||||
// have already been defragmented. To the left and right of any overlap, the
|
||||
// same reasoning applies. For example,
|
||||
//
|
||||
// |--------| |-------| RANGEKEYDEL
|
||||
// |---------------------------| RANGEDEL
|
||||
// |----1---|----3---|----1----|---2---| Merged, fragmented spans.
|
||||
// __________________________________________________________
|
||||
// a b c d e f g h i j k l m n o p q r s t u v w x y z
|
||||
//
|
||||
// Any fragmented abutting spans produced by the merging iter will be of
|
||||
// differing types (i.e. a transition from a span with homogenous key kinds to a
|
||||
// heterogeneous span, or a transition from a span with exclusively range dels
|
||||
// to a span with exclusively range key dels). Therefore, further
|
||||
// defragmentation is not required.
|
||||
//
|
||||
// Each span returned by the tableRangeDeletionIter will have at most four keys,
|
||||
// corresponding to the largest and smallest sequence numbers encountered across
|
||||
// the range deletes and range keys deletes that comprised the merged spans.
|
||||
func newCombinedDeletionKeyspanIter(
|
||||
comparer *base.Comparer, r *sstable.Reader, m *fileMetadata,
|
||||
) (keyspan.FragmentIterator, error) {
|
||||
// The range del iter and range key iter are each wrapped in their own
|
||||
// defragmenting iter. For each iter, abutting spans can always be merged.
|
||||
var equal = keyspan.DefragmentMethodFunc(func(_ base.Equal, a, b *keyspan.Span) bool { return true })
|
||||
// Reduce keys by maintaining a slice of at most length two, corresponding to
|
||||
// the largest and smallest keys in the defragmented span. This maintains the
|
||||
// contract that the emitted slice is sorted by (SeqNum, Kind) descending.
|
||||
reducer := func(current, incoming []keyspan.Key) []keyspan.Key {
|
||||
if len(current) == 0 && len(incoming) == 0 {
|
||||
// While this should never occur in practice, a defensive return is used
|
||||
// here to preserve correctness.
|
||||
return current
|
||||
}
|
||||
var largest, smallest keyspan.Key
|
||||
var set bool
|
||||
for _, keys := range [2][]keyspan.Key{current, incoming} {
|
||||
if len(keys) == 0 {
|
||||
continue
|
||||
}
|
||||
first, last := keys[0], keys[len(keys)-1]
|
||||
if !set {
|
||||
largest, smallest = first, last
|
||||
set = true
|
||||
continue
|
||||
}
|
||||
if first.Trailer > largest.Trailer {
|
||||
largest = first
|
||||
}
|
||||
if last.Trailer < smallest.Trailer {
|
||||
smallest = last
|
||||
}
|
||||
}
|
||||
if largest.Equal(comparer.Equal, smallest) {
|
||||
current = append(current[:0], largest)
|
||||
} else {
|
||||
current = append(current[:0], largest, smallest)
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
// The separate iters for the range dels and range keys are wrapped in a
|
||||
// merging iter to join the keyspaces into a single keyspace. The separate
|
||||
// iters are only added if the particular key kind is present.
|
||||
mIter := &keyspan.MergingIter{}
|
||||
var transform = keyspan.TransformerFunc(func(cmp base.Compare, in keyspan.Span, out *keyspan.Span) error {
|
||||
if in.KeysOrder != keyspan.ByTrailerDesc {
|
||||
panic("pebble: combined deletion iter encountered keys in non-trailer descending order")
|
||||
}
|
||||
out.Start, out.End = in.Start, in.End
|
||||
out.Keys = append(out.Keys[:0], in.Keys...)
|
||||
out.KeysOrder = keyspan.ByTrailerDesc
|
||||
// NB: The order of by-trailer descending may have been violated,
|
||||
// because we've layered rangekey and rangedel iterators from the same
|
||||
// sstable into the same keyspan.MergingIter. The MergingIter will
|
||||
// return the keys in the order that the child iterators were provided.
|
||||
// Sort the keys to ensure they're sorted by trailer descending.
|
||||
keyspan.SortKeysByTrailer(&out.Keys)
|
||||
return nil
|
||||
})
|
||||
mIter.Init(comparer.Compare, transform)
|
||||
|
||||
iter, err := r.NewRawRangeDelIter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iter != nil {
|
||||
dIter := &keyspan.DefragmentingIter{}
|
||||
dIter.Init(comparer, iter, equal, reducer)
|
||||
iter = dIter
|
||||
// Truncate tombstones to the containing file's bounds if necessary.
|
||||
// See docs/range_deletions.md for why this is necessary.
|
||||
iter = keyspan.Truncate(
|
||||
comparer.Compare, iter, m.Smallest.UserKey, m.Largest.UserKey, nil, nil,
|
||||
)
|
||||
mIter.AddLevel(iter)
|
||||
}
|
||||
|
||||
iter, err = r.NewRawRangeKeyIter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if iter != nil {
|
||||
// Wrap the range key iterator in a filter that elides keys other than range
|
||||
// key deletions.
|
||||
iter = keyspan.Filter(iter, func(in *keyspan.Span, out *keyspan.Span) (keep bool) {
|
||||
out.Start, out.End = in.Start, in.End
|
||||
out.Keys = out.Keys[:0]
|
||||
for _, k := range in.Keys {
|
||||
if k.Kind() != base.InternalKeyKindRangeKeyDelete {
|
||||
continue
|
||||
}
|
||||
out.Keys = append(out.Keys, k)
|
||||
}
|
||||
return len(out.Keys) > 0
|
||||
})
|
||||
dIter := &keyspan.DefragmentingIter{}
|
||||
dIter.Init(comparer, iter, equal, reducer)
|
||||
iter = dIter
|
||||
mIter.AddLevel(iter)
|
||||
}
|
||||
|
||||
return mIter, nil
|
||||
}
|
||||
|
||||
// rangeKeySetsAnnotator implements manifest.Annotator, annotating B-Tree nodes
|
||||
// with the sum of the files' counts of range key fragments. Its annotation type
|
||||
// is a *uint64. The count of range key sets may change once a table's stats are
|
||||
// loaded asynchronously, so its values are marked as cacheable only if a file's
|
||||
// stats have been loaded.
|
||||
type rangeKeySetsAnnotator struct{}
|
||||
|
||||
var _ manifest.Annotator = rangeKeySetsAnnotator{}
|
||||
|
||||
func (a rangeKeySetsAnnotator) Zero(dst interface{}) interface{} {
|
||||
if dst == nil {
|
||||
return new(uint64)
|
||||
}
|
||||
v := dst.(*uint64)
|
||||
*v = 0
|
||||
return v
|
||||
}
|
||||
|
||||
func (a rangeKeySetsAnnotator) Accumulate(
|
||||
f *fileMetadata, dst interface{},
|
||||
) (v interface{}, cacheOK bool) {
|
||||
vptr := dst.(*uint64)
|
||||
*vptr = *vptr + f.Stats.NumRangeKeySets
|
||||
return vptr, f.StatsValidLocked()
|
||||
}
|
||||
|
||||
func (a rangeKeySetsAnnotator) Merge(src interface{}, dst interface{}) interface{} {
|
||||
srcV := src.(*uint64)
|
||||
dstV := dst.(*uint64)
|
||||
*dstV = *dstV + *srcV
|
||||
return dstV
|
||||
}
|
||||
|
||||
// countRangeKeySetFragments counts the number of RANGEKEYSET keys across all
|
||||
// files of the LSM. It only counts keys in files for which table stats have
|
||||
// been loaded. It uses a b-tree annotator to cache intermediate values between
|
||||
// calculations when possible.
|
||||
func countRangeKeySetFragments(v *version) (count uint64) {
|
||||
for l := 0; l < numLevels; l++ {
|
||||
if v.RangeKeyLevels[l].Empty() {
|
||||
continue
|
||||
}
|
||||
count += *v.RangeKeyLevels[l].Annotation(rangeKeySetsAnnotator{}).(*uint64)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user