Initial commit
This commit is contained in:
1238
vendor/github.com/cockroachdb/pebble/sstable/block.go
generated
vendored
Normal file
1238
vendor/github.com/cockroachdb/pebble/sstable/block.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
574
vendor/github.com/cockroachdb/pebble/sstable/block_property.go
generated
vendored
Normal file
574
vendor/github.com/cockroachdb/pebble/sstable/block_property.go
generated
vendored
Normal file
@@ -0,0 +1,574 @@
|
||||
// Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/rangekey"
|
||||
)
|
||||
|
||||
// Block properties are an optional user-facing feature that can be used to
|
||||
// filter data blocks (and whole sstables) from an Iterator before they are
|
||||
// loaded. They do not apply to range delete blocks. These are expected to
|
||||
// very concisely represent a set of some attribute value contained within the
|
||||
// key or value, such that the set includes all the attribute values in the
|
||||
// block. This has some similarities with OLAP pruning approaches that
|
||||
// maintain min-max attribute values for some column (which concisely
|
||||
// represent a set), that is then used to prune at query time. In Pebble's
|
||||
// case, data blocks are small, typically 25-50KB, so these properties should
|
||||
// reduce their precision in order to be concise -- a good rule of thumb is to
|
||||
// not consume more than 50-100 bytes across all properties maintained for a
|
||||
// block, i.e., a 500x reduction compared to loading the data block.
|
||||
//
|
||||
// A block property must be assigned a unique name, which is encoded and
|
||||
// stored in the sstable. This name must be unique among all user-properties
|
||||
// encoded in an sstable.
|
||||
//
|
||||
// A property is represented as a []byte. A nil value or empty byte slice are
|
||||
// considered semantically identical. The caller is free to choose the
|
||||
// semantics of an empty byte slice e.g. they could use it to represent the
|
||||
// empty set or the universal set, whichever they think is more common and
|
||||
// therefore better to encode more concisely. The serialization of the
|
||||
// property for the various Finish*() calls in a BlockPropertyCollector
|
||||
// implementation should be identical, since the corresponding
|
||||
// BlockPropertyFilter implementation is not told the context in which it is
|
||||
// deserializing the property.
|
||||
//
|
||||
// Block properties are more general than table properties and should be
|
||||
// preferred over using table properties. A BlockPropertyCollector can achieve
|
||||
// identical behavior to table properties by returning the nil slice from
|
||||
// FinishDataBlock and FinishIndexBlock, and interpret them as the universal
|
||||
// set in BlockPropertyFilter, and return a non-universal set in FinishTable.
|
||||
|
||||
// BlockPropertyCollector is used when writing a sstable.
|
||||
// - All calls to Add are included in the next FinishDataBlock, after which
|
||||
// the next data block is expected to start.
|
||||
//
|
||||
// - The index entry generated for the data block, which contains the return
|
||||
// value from FinishDataBlock, is not immediately included in the current
|
||||
// index block. It is included when AddPrevDataBlockToIndexBlock is called.
|
||||
// An alternative would be to return an opaque handle from FinishDataBlock
|
||||
// and pass it to a new AddToIndexBlock method, which requires more
|
||||
// plumbing, and passing of an interface{} results in a undesirable heap
|
||||
// allocation. AddPrevDataBlockToIndexBlock must be called before keys are
|
||||
// added to the new data block.
|
||||
type BlockPropertyCollector interface {
|
||||
// Name returns the name of the block property collector.
|
||||
Name() string
|
||||
// Add is called with each new entry added to a data block in the sstable.
|
||||
// The callee can assume that these are in sorted order.
|
||||
Add(key InternalKey, value []byte) error
|
||||
// FinishDataBlock is called when all the entries have been added to a
|
||||
// data block. Subsequent Add calls will be for the next data block. It
|
||||
// returns the property value for the finished block.
|
||||
FinishDataBlock(buf []byte) ([]byte, error)
|
||||
// AddPrevDataBlockToIndexBlock adds the entry corresponding to the
|
||||
// previous FinishDataBlock to the current index block.
|
||||
AddPrevDataBlockToIndexBlock()
|
||||
// FinishIndexBlock is called when an index block, containing all the
|
||||
// key-value pairs since the last FinishIndexBlock, will no longer see new
|
||||
// entries. It returns the property value for the index block.
|
||||
FinishIndexBlock(buf []byte) ([]byte, error)
|
||||
// FinishTable is called when the sstable is finished, and returns the
|
||||
// property value for the sstable.
|
||||
FinishTable(buf []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// SuffixReplaceableBlockCollector is an extension to the BlockPropertyCollector
|
||||
// interface that allows a block property collector to indicate the it supports
|
||||
// being *updated* during suffix replacement, i.e. when an existing SST in which
|
||||
// all keys have the same key suffix is updated to have a new suffix.
|
||||
//
|
||||
// A collector which supports being updated in such cases must be able to derive
|
||||
// its updated value from its old value and the change being made to the suffix,
|
||||
// without needing to be passed each updated K/V.
|
||||
//
|
||||
// For example, a collector that only inspects values would can simply copy its
|
||||
// previously computed property as-is, since key-suffix replacement does not
|
||||
// change values, while a collector that depends only on key suffixes, like one
|
||||
// which collected mvcc-timestamp bounds from timestamp-suffixed keys, can just
|
||||
// set its new bounds from the new suffix, as it is common to all keys, without
|
||||
// needing to recompute it from every key.
|
||||
//
|
||||
// An implementation of DataBlockIntervalCollector can also implement this
|
||||
// interface, in which case the BlockPropertyCollector returned by passing it to
|
||||
// NewBlockIntervalCollector will also implement this interface automatically.
|
||||
type SuffixReplaceableBlockCollector interface {
|
||||
// UpdateKeySuffixes is called when a block is updated to change the suffix of
|
||||
// all keys in the block, and is passed the old value for that prop, if any,
|
||||
// for that block as well as the old and new suffix.
|
||||
UpdateKeySuffixes(oldProp []byte, oldSuffix, newSuffix []byte) error
|
||||
}
|
||||
|
||||
// BlockPropertyFilter is used in an Iterator to filter sstables and blocks
|
||||
// within the sstable. It should not maintain any per-sstable state, and must
|
||||
// be thread-safe.
|
||||
type BlockPropertyFilter = base.BlockPropertyFilter
|
||||
|
||||
// BlockIntervalCollector is a helper implementation of BlockPropertyCollector
|
||||
// for users who want to represent a set of the form [lower,upper) where both
|
||||
// lower and upper are uint64, and lower <= upper.
|
||||
//
|
||||
// The set is encoded as:
|
||||
// - Two varint integers, (lower,upper-lower), when upper-lower > 0
|
||||
// - Nil, when upper-lower=0
|
||||
//
|
||||
// Users must not expect this to preserve differences between empty sets --
|
||||
// they will all get turned into the semantically equivalent [0,0).
|
||||
//
|
||||
// A BlockIntervalCollector that collects over point and range keys needs to
|
||||
// have both the point and range DataBlockIntervalCollector specified, since
|
||||
// point and range keys are fed to the BlockIntervalCollector in an interleaved
|
||||
// fashion, independently of one another. This also implies that the
|
||||
// DataBlockIntervalCollectors for point and range keys should be references to
|
||||
// independent instances, rather than references to the same collector, as point
|
||||
// and range keys are tracked independently.
|
||||
type BlockIntervalCollector struct {
|
||||
name string
|
||||
points DataBlockIntervalCollector
|
||||
ranges DataBlockIntervalCollector
|
||||
|
||||
blockInterval interval
|
||||
indexInterval interval
|
||||
tableInterval interval
|
||||
}
|
||||
|
||||
var _ BlockPropertyCollector = &BlockIntervalCollector{}
|
||||
|
||||
// DataBlockIntervalCollector is the interface used by BlockIntervalCollector
|
||||
// that contains the actual logic pertaining to the property. It only
|
||||
// maintains state for the current data block, and resets that state in
|
||||
// FinishDataBlock. This interface can be used to reduce parsing costs.
|
||||
type DataBlockIntervalCollector interface {
|
||||
// Add is called with each new entry added to a data block in the sstable.
|
||||
// The callee can assume that these are in sorted order.
|
||||
Add(key InternalKey, value []byte) error
|
||||
// FinishDataBlock is called when all the entries have been added to a
|
||||
// data block. Subsequent Add calls will be for the next data block. It
|
||||
// returns the [lower, upper) for the finished block.
|
||||
FinishDataBlock() (lower uint64, upper uint64, err error)
|
||||
}
|
||||
|
||||
// NewBlockIntervalCollector constructs a BlockIntervalCollector with the given
|
||||
// name. The BlockIntervalCollector makes use of the given point and range key
|
||||
// DataBlockIntervalCollectors when encountering point and range keys,
|
||||
// respectively.
|
||||
//
|
||||
// The caller may pass a nil DataBlockIntervalCollector for one of the point or
|
||||
// range key collectors, in which case keys of those types will be ignored. This
|
||||
// allows for flexible construction of BlockIntervalCollectors that operate on
|
||||
// just point keys, just range keys, or both point and range keys.
|
||||
//
|
||||
// If both point and range keys are to be tracked, two independent collectors
|
||||
// should be provided, rather than the same collector passed in twice (see the
|
||||
// comment on BlockIntervalCollector for more detail)
|
||||
func NewBlockIntervalCollector(
|
||||
name string, pointCollector, rangeCollector DataBlockIntervalCollector,
|
||||
) BlockPropertyCollector {
|
||||
if pointCollector == nil && rangeCollector == nil {
|
||||
panic("sstable: at least one interval collector must be provided")
|
||||
}
|
||||
bic := BlockIntervalCollector{
|
||||
name: name,
|
||||
points: pointCollector,
|
||||
ranges: rangeCollector,
|
||||
}
|
||||
if _, ok := pointCollector.(SuffixReplaceableBlockCollector); ok {
|
||||
return &suffixReplacementBlockCollectorWrapper{bic}
|
||||
}
|
||||
return &bic
|
||||
}
|
||||
|
||||
// Name implements the BlockPropertyCollector interface.
|
||||
func (b *BlockIntervalCollector) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
// Add implements the BlockPropertyCollector interface.
|
||||
func (b *BlockIntervalCollector) Add(key InternalKey, value []byte) error {
|
||||
if rangekey.IsRangeKey(key.Kind()) {
|
||||
if b.ranges != nil {
|
||||
return b.ranges.Add(key, value)
|
||||
}
|
||||
} else if b.points != nil {
|
||||
return b.points.Add(key, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FinishDataBlock implements the BlockPropertyCollector interface.
|
||||
func (b *BlockIntervalCollector) FinishDataBlock(buf []byte) ([]byte, error) {
|
||||
if b.points == nil {
|
||||
return buf, nil
|
||||
}
|
||||
var err error
|
||||
b.blockInterval.lower, b.blockInterval.upper, err = b.points.FinishDataBlock()
|
||||
if err != nil {
|
||||
return buf, err
|
||||
}
|
||||
buf = b.blockInterval.encode(buf)
|
||||
b.tableInterval.union(b.blockInterval)
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// AddPrevDataBlockToIndexBlock implements the BlockPropertyCollector
|
||||
// interface.
|
||||
func (b *BlockIntervalCollector) AddPrevDataBlockToIndexBlock() {
|
||||
b.indexInterval.union(b.blockInterval)
|
||||
b.blockInterval = interval{}
|
||||
}
|
||||
|
||||
// FinishIndexBlock implements the BlockPropertyCollector interface.
|
||||
func (b *BlockIntervalCollector) FinishIndexBlock(buf []byte) ([]byte, error) {
|
||||
buf = b.indexInterval.encode(buf)
|
||||
b.indexInterval = interval{}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
// FinishTable implements the BlockPropertyCollector interface.
|
||||
func (b *BlockIntervalCollector) FinishTable(buf []byte) ([]byte, error) {
|
||||
// If the collector is tracking range keys, the range key interval is union-ed
|
||||
// with the point key interval for the table.
|
||||
if b.ranges != nil {
|
||||
var rangeInterval interval
|
||||
var err error
|
||||
rangeInterval.lower, rangeInterval.upper, err = b.ranges.FinishDataBlock()
|
||||
if err != nil {
|
||||
return buf, err
|
||||
}
|
||||
b.tableInterval.union(rangeInterval)
|
||||
}
|
||||
return b.tableInterval.encode(buf), nil
|
||||
}
|
||||
|
||||
type interval struct {
|
||||
lower uint64
|
||||
upper uint64
|
||||
}
|
||||
|
||||
func (i interval) encode(buf []byte) []byte {
|
||||
if i.lower < i.upper {
|
||||
var encoded [binary.MaxVarintLen64 * 2]byte
|
||||
n := binary.PutUvarint(encoded[:], i.lower)
|
||||
n += binary.PutUvarint(encoded[n:], i.upper-i.lower)
|
||||
buf = append(buf, encoded[:n]...)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func (i *interval) decode(buf []byte) error {
|
||||
if len(buf) == 0 {
|
||||
*i = interval{}
|
||||
return nil
|
||||
}
|
||||
var n int
|
||||
i.lower, n = binary.Uvarint(buf)
|
||||
if n <= 0 || n >= len(buf) {
|
||||
return base.CorruptionErrorf("cannot decode interval from buf %x", buf)
|
||||
}
|
||||
pos := n
|
||||
i.upper, n = binary.Uvarint(buf[pos:])
|
||||
pos += n
|
||||
if pos != len(buf) || n <= 0 {
|
||||
return base.CorruptionErrorf("cannot decode interval from buf %x", buf)
|
||||
}
|
||||
// Delta decode.
|
||||
i.upper += i.lower
|
||||
if i.upper < i.lower {
|
||||
return base.CorruptionErrorf("unexpected overflow, upper %d < lower %d", i.upper, i.lower)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *interval) union(x interval) {
|
||||
if x.lower >= x.upper {
|
||||
// x is the empty set.
|
||||
return
|
||||
}
|
||||
if i.lower >= i.upper {
|
||||
// i is the empty set.
|
||||
*i = x
|
||||
return
|
||||
}
|
||||
// Both sets are non-empty.
|
||||
if x.lower < i.lower {
|
||||
i.lower = x.lower
|
||||
}
|
||||
if x.upper > i.upper {
|
||||
i.upper = x.upper
|
||||
}
|
||||
}
|
||||
|
||||
func (i interval) intersects(x interval) bool {
|
||||
if i.lower >= i.upper || x.lower >= x.upper {
|
||||
// At least one of the sets is empty.
|
||||
return false
|
||||
}
|
||||
// Neither set is empty.
|
||||
return i.upper > x.lower && i.lower < x.upper
|
||||
}
|
||||
|
||||
type suffixReplacementBlockCollectorWrapper struct {
|
||||
BlockIntervalCollector
|
||||
}
|
||||
|
||||
// UpdateKeySuffixes implements the SuffixReplaceableBlockCollector interface.
|
||||
func (w *suffixReplacementBlockCollectorWrapper) UpdateKeySuffixes(
|
||||
oldProp []byte, from, to []byte,
|
||||
) error {
|
||||
return w.BlockIntervalCollector.points.(SuffixReplaceableBlockCollector).UpdateKeySuffixes(oldProp, from, to)
|
||||
}
|
||||
|
||||
// blockIntervalFilter is an implementation of BlockPropertyFilter when the
|
||||
// corresponding collector is a BlockIntervalCollector. That is, the set is of
|
||||
// the form [lower, upper).
|
||||
type blockIntervalFilter struct {
|
||||
name string
|
||||
filterInterval interval
|
||||
}
|
||||
|
||||
// NewBlockIntervalFilter constructs a BlockPropertyFilter that filters blocks
|
||||
// based on an interval property collected by BlockIntervalCollector and the
|
||||
// given [lower, upper) bounds. The given name specifies the
|
||||
// BlockIntervalCollector's properties to read.
|
||||
func NewBlockIntervalFilter(name string, lower uint64, upper uint64) BlockPropertyFilter {
|
||||
return &blockIntervalFilter{
|
||||
name: name,
|
||||
filterInterval: interval{lower: lower, upper: upper},
|
||||
}
|
||||
}
|
||||
|
||||
// Name implements the BlockPropertyFilter interface.
|
||||
func (b *blockIntervalFilter) Name() string {
|
||||
return b.name
|
||||
}
|
||||
|
||||
// Intersects implements the BlockPropertyFilter interface.
|
||||
func (b *blockIntervalFilter) Intersects(prop []byte) (bool, error) {
|
||||
var i interval
|
||||
if err := i.decode(prop); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return i.intersects(b.filterInterval), nil
|
||||
}
|
||||
|
||||
// When encoding block properties for each block, we cannot afford to encode
|
||||
// the name. Instead, the name is mapped to a shortID, in the scope of that
|
||||
// sstable, and the shortID is encoded. Since we use a uint8, there is a limit
|
||||
// of 256 block property collectors per sstable.
|
||||
type shortID uint8
|
||||
|
||||
type blockPropertiesEncoder struct {
|
||||
propsBuf []byte
|
||||
scratch []byte
|
||||
}
|
||||
|
||||
func (e *blockPropertiesEncoder) getScratchForProp() []byte {
|
||||
return e.scratch[:0]
|
||||
}
|
||||
|
||||
func (e *blockPropertiesEncoder) resetProps() {
|
||||
e.propsBuf = e.propsBuf[:0]
|
||||
}
|
||||
|
||||
func (e *blockPropertiesEncoder) addProp(id shortID, scratch []byte) {
|
||||
const lenID = 1
|
||||
lenProp := uvarintLen(uint32(len(scratch)))
|
||||
n := lenID + lenProp + len(scratch)
|
||||
if cap(e.propsBuf)-len(e.propsBuf) < n {
|
||||
size := len(e.propsBuf) + 2*n
|
||||
if size < 2*cap(e.propsBuf) {
|
||||
size = 2 * cap(e.propsBuf)
|
||||
}
|
||||
buf := make([]byte, len(e.propsBuf), size)
|
||||
copy(buf, e.propsBuf)
|
||||
e.propsBuf = buf
|
||||
}
|
||||
pos := len(e.propsBuf)
|
||||
b := e.propsBuf[pos : pos+lenID]
|
||||
b[0] = byte(id)
|
||||
pos += lenID
|
||||
b = e.propsBuf[pos : pos+lenProp]
|
||||
n = binary.PutUvarint(b, uint64(len(scratch)))
|
||||
pos += n
|
||||
b = e.propsBuf[pos : pos+len(scratch)]
|
||||
pos += len(scratch)
|
||||
copy(b, scratch)
|
||||
e.propsBuf = e.propsBuf[0:pos]
|
||||
e.scratch = scratch
|
||||
}
|
||||
|
||||
func (e *blockPropertiesEncoder) unsafeProps() []byte {
|
||||
return e.propsBuf
|
||||
}
|
||||
|
||||
func (e *blockPropertiesEncoder) props() []byte {
|
||||
buf := make([]byte, len(e.propsBuf))
|
||||
copy(buf, e.propsBuf)
|
||||
return buf
|
||||
}
|
||||
|
||||
type blockPropertiesDecoder struct {
|
||||
props []byte
|
||||
}
|
||||
|
||||
func (d *blockPropertiesDecoder) done() bool {
|
||||
return len(d.props) == 0
|
||||
}
|
||||
|
||||
// REQUIRES: !done()
|
||||
func (d *blockPropertiesDecoder) next() (id shortID, prop []byte, err error) {
|
||||
const lenID = 1
|
||||
id = shortID(d.props[0])
|
||||
propLen, m := binary.Uvarint(d.props[lenID:])
|
||||
n := lenID + m
|
||||
if m <= 0 || propLen == 0 || (n+int(propLen)) > len(d.props) {
|
||||
return 0, nil, base.CorruptionErrorf("corrupt block property length")
|
||||
}
|
||||
prop = d.props[n : n+int(propLen)]
|
||||
d.props = d.props[n+int(propLen):]
|
||||
return id, prop, nil
|
||||
}
|
||||
|
||||
// BlockPropertiesFilterer provides filtering support when reading an sstable
|
||||
// in the context of an iterator that has a slice of BlockPropertyFilters.
|
||||
// After the call to NewBlockPropertiesFilterer, the caller must call
|
||||
// IntersectsUserPropsAndFinishInit to check if the sstable intersects with
|
||||
// the filters. If it does intersect, this function also finishes initializing
|
||||
// the BlockPropertiesFilterer using the shortIDs for the relevant filters.
|
||||
// Subsequent checks for relevance of a block should use the intersects
|
||||
// method.
|
||||
type BlockPropertiesFilterer struct {
|
||||
filters []BlockPropertyFilter
|
||||
// Maps shortID => index in filters. This can be sparse, and shortIDs for
|
||||
// which there is no filter are represented with an index of -1. The
|
||||
// length of this can be shorter than the shortIDs allocated in the
|
||||
// sstable. e.g. if the sstable used shortIDs 0, 1, 2, 3, and the iterator
|
||||
// has two filters, corresponding to shortIDs 2, 0, this would be:
|
||||
// len(shortIDToFiltersIndex)==3, 0=>1, 1=>-1, 2=>0.
|
||||
shortIDToFiltersIndex []int
|
||||
}
|
||||
|
||||
var blockPropertiesFiltererPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &BlockPropertiesFilterer{}
|
||||
},
|
||||
}
|
||||
|
||||
// NewBlockPropertiesFilterer returns a partially initialized filterer. To complete
|
||||
// initialization, call IntersectsUserPropsAndFinishInit.
|
||||
func NewBlockPropertiesFilterer(filters []BlockPropertyFilter) *BlockPropertiesFilterer {
|
||||
filterer := blockPropertiesFiltererPool.Get().(*BlockPropertiesFilterer)
|
||||
*filterer = BlockPropertiesFilterer{filters: filters}
|
||||
return filterer
|
||||
}
|
||||
|
||||
func releaseBlockPropertiesFilterer(filterer *BlockPropertiesFilterer) {
|
||||
*filterer = BlockPropertiesFilterer{
|
||||
shortIDToFiltersIndex: filterer.shortIDToFiltersIndex[:0],
|
||||
}
|
||||
blockPropertiesFiltererPool.Put(filterer)
|
||||
}
|
||||
|
||||
// IntersectsUserPropsAndFinishInit is called with the user properties map for
|
||||
// the sstable and returns whether the sstable intersects the filters. It
|
||||
// additionally initializes the shortIDToFiltersIndex for the filters that are
|
||||
// relevant to this sstable.
|
||||
func (f *BlockPropertiesFilterer) IntersectsUserPropsAndFinishInit(
|
||||
userProperties map[string]string,
|
||||
) (bool, error) {
|
||||
for i := range f.filters {
|
||||
props, ok := userProperties[f.filters[i].Name()]
|
||||
if !ok {
|
||||
// Collector was not used when writing this file, so it is
|
||||
// considered intersecting.
|
||||
continue
|
||||
}
|
||||
byteProps := []byte(props)
|
||||
if len(byteProps) < 1 {
|
||||
return false, base.CorruptionErrorf(
|
||||
"block properties for %s is corrupted", f.filters[i].Name())
|
||||
}
|
||||
shortID := shortID(byteProps[0])
|
||||
intersects, err := f.filters[i].Intersects(byteProps[1:])
|
||||
if err != nil || !intersects {
|
||||
return false, err
|
||||
}
|
||||
// Intersects the sstable, so need to use this filter when
|
||||
// deciding whether to read blocks.
|
||||
n := len(f.shortIDToFiltersIndex)
|
||||
if n <= int(shortID) {
|
||||
if cap(f.shortIDToFiltersIndex) <= int(shortID) {
|
||||
index := make([]int, shortID+1, 2*(shortID+1))
|
||||
copy(index, f.shortIDToFiltersIndex)
|
||||
f.shortIDToFiltersIndex = index
|
||||
} else {
|
||||
f.shortIDToFiltersIndex = f.shortIDToFiltersIndex[:shortID+1]
|
||||
}
|
||||
for j := n; j < int(shortID); j++ {
|
||||
f.shortIDToFiltersIndex[j] = -1
|
||||
}
|
||||
}
|
||||
f.shortIDToFiltersIndex[shortID] = i
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *BlockPropertiesFilterer) intersects(props []byte) (bool, error) {
|
||||
i := 0
|
||||
decoder := blockPropertiesDecoder{props: props}
|
||||
for i < len(f.shortIDToFiltersIndex) {
|
||||
var id int
|
||||
var prop []byte
|
||||
if !decoder.done() {
|
||||
var shortID shortID
|
||||
var err error
|
||||
shortID, prop, err = decoder.next()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
id = int(shortID)
|
||||
} else {
|
||||
id = math.MaxUint8 + 1
|
||||
}
|
||||
for i < len(f.shortIDToFiltersIndex) && id > i {
|
||||
if f.shortIDToFiltersIndex[i] >= 0 {
|
||||
// There is a filter for this id, but the property for this id
|
||||
// is not encoded for this block.
|
||||
intersects, err := f.filters[f.shortIDToFiltersIndex[i]].Intersects(nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !intersects {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i >= len(f.shortIDToFiltersIndex) {
|
||||
return true, nil
|
||||
}
|
||||
// INVARIANT: id <= i. And since i is always incremented by 1, id==i.
|
||||
if id != i {
|
||||
panic(fmt.Sprintf("%d != %d", id, i))
|
||||
}
|
||||
if f.shortIDToFiltersIndex[i] >= 0 {
|
||||
intersects, err := f.filters[f.shortIDToFiltersIndex[i]].Intersects(prop)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !intersects {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
34
vendor/github.com/cockroachdb/pebble/sstable/comparer.go
generated
vendored
Normal file
34
vendor/github.com/cockroachdb/pebble/sstable/comparer.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2011 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import "github.com/cockroachdb/pebble/internal/base"
|
||||
|
||||
// Compare exports the base.Compare type.
|
||||
type Compare = base.Compare
|
||||
|
||||
// Equal exports the base.Equal type.
|
||||
type Equal = base.Equal
|
||||
|
||||
// AbbreviatedKey exports the base.AbbreviatedKey type.
|
||||
type AbbreviatedKey = base.AbbreviatedKey
|
||||
|
||||
// Separator exports the base.Separator type.
|
||||
type Separator = base.Separator
|
||||
|
||||
// Successor exports the base.Successor type.
|
||||
type Successor = base.Successor
|
||||
|
||||
// Split exports the base.Split type.
|
||||
type Split = base.Split
|
||||
|
||||
// Comparer exports the base.Comparer type.
|
||||
type Comparer = base.Comparer
|
||||
|
||||
// DefaultComparer exports the base.DefaultComparer variable.
|
||||
var DefaultComparer = base.DefaultComparer
|
||||
|
||||
// Merger exports the base.Merger type.
|
||||
type Merger = base.Merger
|
||||
98
vendor/github.com/cockroachdb/pebble/sstable/compression.go
generated
vendored
Normal file
98
vendor/github.com/cockroachdb/pebble/sstable/compression.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/cache"
|
||||
"github.com/golang/snappy"
|
||||
)
|
||||
|
||||
func decompressedLen(blockType blockType, b []byte) (int, int, error) {
|
||||
switch blockType {
|
||||
case noCompressionBlockType:
|
||||
return 0, 0, nil
|
||||
case snappyCompressionBlockType:
|
||||
l, err := snappy.DecodedLen(b)
|
||||
return l, 0, err
|
||||
case zstdCompressionBlockType:
|
||||
// This will also be used by zlib, bzip2 and lz4 to retrieve the decodedLen
|
||||
// if we implement these algorithms in the future.
|
||||
decodedLenU64, varIntLen := binary.Uvarint(b)
|
||||
if varIntLen <= 0 {
|
||||
return 0, 0, base.CorruptionErrorf("pebble/table: compression block has invalid length")
|
||||
}
|
||||
return int(decodedLenU64), varIntLen, nil
|
||||
default:
|
||||
return 0, 0, base.CorruptionErrorf("pebble/table: unknown block compression: %d", errors.Safe(blockType))
|
||||
}
|
||||
}
|
||||
|
||||
func decompressInto(blockType blockType, compressed []byte, buf []byte) ([]byte, error) {
|
||||
var result []byte
|
||||
var err error
|
||||
switch blockType {
|
||||
case snappyCompressionBlockType:
|
||||
result, err = snappy.Decode(buf, compressed)
|
||||
case zstdCompressionBlockType:
|
||||
result, err = decodeZstd(buf, compressed)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, base.MarkCorruptionError(err)
|
||||
}
|
||||
if len(result) != 0 && (len(result) != len(buf) || &result[0] != &buf[0]) {
|
||||
return nil, base.CorruptionErrorf("pebble/table: decompressed into unexpected buffer: %p != %p",
|
||||
errors.Safe(result), errors.Safe(buf))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// decompressBlock decompresses an SST block, with space allocated from a cache.
|
||||
func decompressBlock(cache *cache.Cache, blockType blockType, b []byte) (*cache.Value, error) {
|
||||
if blockType == noCompressionBlockType {
|
||||
return nil, nil
|
||||
}
|
||||
// first obtain the decoded length.
|
||||
decodedLen, prefixLen, err := decompressedLen(blockType, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if prefixLen != 0 {
|
||||
b = b[prefixLen:]
|
||||
}
|
||||
// Allocate sufficient space from the cache.
|
||||
decoded := cache.Alloc(decodedLen)
|
||||
decodedBuf := decoded.Buf()
|
||||
if _, err := decompressInto(blockType, b, decodedBuf); err != nil {
|
||||
cache.Free(decoded)
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
// compressBlock compresses an SST block, using compressBuf as the desired destination.
|
||||
func compressBlock(
|
||||
compression Compression, b []byte, compressedBuf []byte,
|
||||
) (blockType blockType, compressed []byte) {
|
||||
switch compression {
|
||||
case SnappyCompression:
|
||||
return snappyCompressionBlockType, snappy.Encode(compressedBuf, b)
|
||||
case NoCompression:
|
||||
return noCompressionBlockType, b
|
||||
}
|
||||
|
||||
if len(compressedBuf) < binary.MaxVarintLen64 {
|
||||
compressedBuf = append(compressedBuf, make([]byte, binary.MaxVarintLen64-len(compressedBuf))...)
|
||||
}
|
||||
varIntLen := binary.PutUvarint(compressedBuf, uint64(len(b)))
|
||||
switch compression {
|
||||
case ZstdCompression:
|
||||
return zstdCompressionBlockType, encodeZstd(compressedBuf, varIntLen, b)
|
||||
default:
|
||||
return noCompressionBlockType, b
|
||||
}
|
||||
}
|
||||
34
vendor/github.com/cockroachdb/pebble/sstable/compression_cgo.go
generated
vendored
Normal file
34
vendor/github.com/cockroachdb/pebble/sstable/compression_cgo.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
//go:build cgo
|
||||
// +build cgo
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/DataDog/zstd"
|
||||
)
|
||||
|
||||
// decodeZstd decompresses b with the Zstandard algorithm.
|
||||
// It reuses the preallocated capacity of decodedBuf if it is sufficient.
|
||||
// On success, it returns the decoded byte slice.
|
||||
func decodeZstd(decodedBuf, b []byte) ([]byte, error) {
|
||||
return zstd.Decompress(decodedBuf, b)
|
||||
}
|
||||
|
||||
// encodeZstd compresses b with the Zstandard algorithm at default compression
|
||||
// level (level 3). It reuses the preallocated capacity of compressedBuf if it
|
||||
// is sufficient. The subslice `compressedBuf[:varIntLen]` should already encode
|
||||
// the length of `b` before calling encodeZstd. It returns the encoded byte
|
||||
// slice, including the `compressedBuf[:varIntLen]` prefix.
|
||||
func encodeZstd(compressedBuf []byte, varIntLen int, b []byte) []byte {
|
||||
buf := bytes.NewBuffer(compressedBuf[:varIntLen])
|
||||
writer := zstd.NewWriterLevel(buf, 3)
|
||||
writer.Write(b)
|
||||
writer.Close()
|
||||
return buf.Bytes()
|
||||
}
|
||||
30
vendor/github.com/cockroachdb/pebble/sstable/compression_nocgo.go
generated
vendored
Normal file
30
vendor/github.com/cockroachdb/pebble/sstable/compression_nocgo.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
//go:build !cgo
|
||||
// +build !cgo
|
||||
|
||||
package sstable
|
||||
|
||||
import "github.com/klauspost/compress/zstd"
|
||||
|
||||
// decodeZstd decompresses b with the Zstandard algorithm.
|
||||
// It reuses the preallocated capacity of decodedBuf if it is sufficient.
|
||||
// On success, it returns the decoded byte slice.
|
||||
func decodeZstd(decodedBuf, b []byte) ([]byte, error) {
|
||||
decoder, _ := zstd.NewReader(nil)
|
||||
defer decoder.Close()
|
||||
return decoder.DecodeAll(b, decodedBuf[:0])
|
||||
}
|
||||
|
||||
// encodeZstd compresses b with the Zstandard algorithm at default compression
|
||||
// level (level 3). It reuses the preallocated capacity of compressedBuf if it
|
||||
// is sufficient. The subslice `compressedBuf[:varIntLen]` should already encode
|
||||
// the length of `b` before calling encodeZstd. It returns the encoded byte
|
||||
// slice, including the `compressedBuf[:varIntLen]` prefix.
|
||||
func encodeZstd(compressedBuf []byte, varIntLen int, b []byte) []byte {
|
||||
encoder, _ := zstd.NewWriter(nil)
|
||||
defer encoder.Close()
|
||||
return encoder.EncodeAll(b, compressedBuf[:varIntLen])
|
||||
}
|
||||
102
vendor/github.com/cockroachdb/pebble/sstable/filter.go
generated
vendored
Normal file
102
vendor/github.com/cockroachdb/pebble/sstable/filter.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2011 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// FilterMetrics holds metrics for the filter policy.
|
||||
type FilterMetrics struct {
|
||||
// The number of hits for the filter policy. This is the
|
||||
// number of times the filter policy was successfully used to avoid access
|
||||
// of a data block.
|
||||
Hits int64
|
||||
// The number of misses for the filter policy. This is the number of times
|
||||
// the filter policy was checked but was unable to filter an access of a data
|
||||
// block.
|
||||
Misses int64
|
||||
}
|
||||
|
||||
var dummyFilterMetrics FilterMetrics
|
||||
|
||||
func (m *FilterMetrics) readerApply(r *Reader) {
|
||||
if r.tableFilter != nil {
|
||||
r.tableFilter.metrics = m
|
||||
}
|
||||
}
|
||||
|
||||
// BlockHandle is the file offset and length of a block.
|
||||
type BlockHandle struct {
|
||||
Offset, Length uint64
|
||||
}
|
||||
|
||||
// BlockHandleWithProperties is used for data blocks and first/lower level
|
||||
// index blocks, since they can be annotated using BlockPropertyCollectors.
|
||||
type BlockHandleWithProperties struct {
|
||||
BlockHandle
|
||||
Props []byte
|
||||
}
|
||||
|
||||
type filterWriter interface {
|
||||
addKey(key []byte)
|
||||
finish() ([]byte, error)
|
||||
metaName() string
|
||||
policyName() string
|
||||
}
|
||||
|
||||
type tableFilterReader struct {
|
||||
policy FilterPolicy
|
||||
metrics *FilterMetrics
|
||||
}
|
||||
|
||||
func newTableFilterReader(policy FilterPolicy) *tableFilterReader {
|
||||
return &tableFilterReader{
|
||||
policy: policy,
|
||||
metrics: &dummyFilterMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *tableFilterReader) mayContain(data, key []byte) bool {
|
||||
mayContain := f.policy.MayContain(TableFilter, data, key)
|
||||
if mayContain {
|
||||
atomic.AddInt64(&f.metrics.Misses, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&f.metrics.Hits, 1)
|
||||
}
|
||||
return mayContain
|
||||
}
|
||||
|
||||
type tableFilterWriter struct {
|
||||
policy FilterPolicy
|
||||
writer FilterWriter
|
||||
// count is the count of the number of keys added to the filter.
|
||||
count int
|
||||
}
|
||||
|
||||
func newTableFilterWriter(policy FilterPolicy) *tableFilterWriter {
|
||||
return &tableFilterWriter{
|
||||
policy: policy,
|
||||
writer: policy.NewWriter(TableFilter),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *tableFilterWriter) addKey(key []byte) {
|
||||
f.count++
|
||||
f.writer.AddKey(key)
|
||||
}
|
||||
|
||||
func (f *tableFilterWriter) finish() ([]byte, error) {
|
||||
if f.count == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return f.writer.Finish(nil), nil
|
||||
}
|
||||
|
||||
func (f *tableFilterWriter) metaName() string {
|
||||
return "fullfilter." + f.policy.Name()
|
||||
}
|
||||
|
||||
func (f *tableFilterWriter) policyName() string {
|
||||
return f.policy.Name()
|
||||
}
|
||||
91
vendor/github.com/cockroachdb/pebble/sstable/format.go
generated
vendored
Normal file
91
vendor/github.com/cockroachdb/pebble/sstable/format.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2022 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
)
|
||||
|
||||
// TableFormat specifies the format version for sstables. The legacy LevelDB
|
||||
// format is format version 1.
|
||||
type TableFormat uint32
|
||||
|
||||
// The available table formats, representing the tuple (magic number, version
|
||||
// number). Note that these values are not (and should not) be serialized to
|
||||
// disk. The ordering should follow the order the versions were introduced to
|
||||
// Pebble (i.e. the history is linear).
|
||||
const (
|
||||
TableFormatUnspecified TableFormat = iota
|
||||
TableFormatLevelDB
|
||||
TableFormatRocksDBv2
|
||||
TableFormatPebblev1 // Block properties.
|
||||
TableFormatPebblev2 // Range keys.
|
||||
|
||||
TableFormatMax = TableFormatPebblev2
|
||||
)
|
||||
|
||||
// ParseTableFormat parses the given magic bytes and version into its
|
||||
// corresponding internal TableFormat.
|
||||
func ParseTableFormat(magic []byte, version uint32) (TableFormat, error) {
|
||||
switch string(magic) {
|
||||
case levelDBMagic:
|
||||
return TableFormatLevelDB, nil
|
||||
case rocksDBMagic:
|
||||
if version != rocksDBFormatVersion2 {
|
||||
return TableFormatUnspecified, base.CorruptionErrorf(
|
||||
"pebble/table: unsupported rocksdb format version %d", errors.Safe(version),
|
||||
)
|
||||
}
|
||||
return TableFormatRocksDBv2, nil
|
||||
case pebbleDBMagic:
|
||||
switch version {
|
||||
case 1:
|
||||
return TableFormatPebblev1, nil
|
||||
case 2:
|
||||
return TableFormatPebblev2, nil
|
||||
default:
|
||||
return TableFormatUnspecified, base.CorruptionErrorf(
|
||||
"pebble/table: unsupported pebble format version %d", errors.Safe(version),
|
||||
)
|
||||
}
|
||||
default:
|
||||
return TableFormatUnspecified, base.CorruptionErrorf(
|
||||
"pebble/table: invalid table (bad magic number)",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// AsTuple returns the TableFormat's (Magic String, Version) tuple.
|
||||
func (f TableFormat) AsTuple() (string, uint32) {
|
||||
switch f {
|
||||
case TableFormatLevelDB:
|
||||
return levelDBMagic, 0
|
||||
case TableFormatRocksDBv2:
|
||||
return rocksDBMagic, 2
|
||||
case TableFormatPebblev1:
|
||||
return pebbleDBMagic, 1
|
||||
case TableFormatPebblev2:
|
||||
return pebbleDBMagic, 2
|
||||
default:
|
||||
panic("sstable: unknown table format version tuple")
|
||||
}
|
||||
}
|
||||
|
||||
// String returns the TableFormat (Magic String,Version) tuple.
|
||||
func (f TableFormat) String() string {
|
||||
switch f {
|
||||
case TableFormatLevelDB:
|
||||
return "(LevelDB)"
|
||||
case TableFormatRocksDBv2:
|
||||
return "(RocksDB,v2)"
|
||||
case TableFormatPebblev1:
|
||||
return "(Pebble,v1)"
|
||||
case TableFormatPebblev2:
|
||||
return "(Pebble,v2)"
|
||||
default:
|
||||
panic("sstable: unknown table format version tuple")
|
||||
}
|
||||
}
|
||||
27
vendor/github.com/cockroachdb/pebble/sstable/internal.go
generated
vendored
Normal file
27
vendor/github.com/cockroachdb/pebble/sstable/internal.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import "github.com/cockroachdb/pebble/internal/base"
|
||||
|
||||
// InternalKeyKind exports the base.InternalKeyKind type.
|
||||
type InternalKeyKind = base.InternalKeyKind
|
||||
|
||||
// These constants are part of the file format, and should not be changed.
|
||||
const (
|
||||
InternalKeyKindDelete = base.InternalKeyKindDelete
|
||||
InternalKeyKindSet = base.InternalKeyKindSet
|
||||
InternalKeyKindMerge = base.InternalKeyKindMerge
|
||||
InternalKeyKindLogData = base.InternalKeyKindLogData
|
||||
InternalKeyKindRangeDelete = base.InternalKeyKindRangeDelete
|
||||
InternalKeyKindMax = base.InternalKeyKindMax
|
||||
InternalKeyKindInvalid = base.InternalKeyKindInvalid
|
||||
InternalKeySeqNumBatch = base.InternalKeySeqNumBatch
|
||||
InternalKeySeqNumMax = base.InternalKeySeqNumMax
|
||||
InternalKeyRangeDeleteSentinel = base.InternalKeyRangeDeleteSentinel
|
||||
)
|
||||
|
||||
// InternalKey exports the base.InternalKey type.
|
||||
type InternalKey = base.InternalKey
|
||||
257
vendor/github.com/cockroachdb/pebble/sstable/options.go
generated
vendored
Normal file
257
vendor/github.com/cockroachdb/pebble/sstable/options.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
// Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/cache"
|
||||
)
|
||||
|
||||
// Compression is the per-block compression algorithm to use.
|
||||
type Compression int
|
||||
|
||||
// The available compression types.
|
||||
const (
|
||||
DefaultCompression Compression = iota
|
||||
NoCompression
|
||||
SnappyCompression
|
||||
ZstdCompression
|
||||
NCompression
|
||||
)
|
||||
|
||||
func (c Compression) String() string {
|
||||
switch c {
|
||||
case DefaultCompression:
|
||||
return "Default"
|
||||
case NoCompression:
|
||||
return "NoCompression"
|
||||
case SnappyCompression:
|
||||
return "Snappy"
|
||||
case ZstdCompression:
|
||||
return "ZSTD"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// FilterType exports the base.FilterType type.
|
||||
type FilterType = base.FilterType
|
||||
|
||||
// Exported TableFilter constants.
|
||||
const (
|
||||
TableFilter = base.TableFilter
|
||||
)
|
||||
|
||||
// FilterWriter exports the base.FilterWriter type.
|
||||
type FilterWriter = base.FilterWriter
|
||||
|
||||
// FilterPolicy exports the base.FilterPolicy type.
|
||||
type FilterPolicy = base.FilterPolicy
|
||||
|
||||
// TablePropertyCollector provides a hook for collecting user-defined
|
||||
// properties based on the keys and values stored in an sstable. A new
|
||||
// TablePropertyCollector is created for an sstable when the sstable is being
|
||||
// written.
|
||||
type TablePropertyCollector interface {
|
||||
// Add is called with each new entry added to the sstable. While the sstable
|
||||
// is itself sorted by key, do not assume that the entries are added in any
|
||||
// order. In particular, the ordering of point entries and range tombstones
|
||||
// is unspecified.
|
||||
Add(key InternalKey, value []byte) error
|
||||
|
||||
// Finish is called when all entries have been added to the sstable. The
|
||||
// collected properties (if any) should be added to the specified map. Note
|
||||
// that in case of an error during sstable construction, Finish may not be
|
||||
// called.
|
||||
Finish(userProps map[string]string) error
|
||||
|
||||
// The name of the property collector.
|
||||
Name() string
|
||||
}
|
||||
|
||||
// SuffixReplaceableTableCollector is an extension to the TablePropertyCollector
|
||||
// interface that allows a table property collector to indicate that it supports
|
||||
// being *updated* during suffix replacement, i.e. when an existing SST in which
|
||||
// all keys have the same key suffix is updated to have a new suffix.
|
||||
//
|
||||
// A collector which supports being updated in such cases must be able to derive
|
||||
// its updated value from its old value and the change being made to the suffix,
|
||||
// without needing to be passed each updated K/V.
|
||||
//
|
||||
// For example, a collector that only inspects values can simply copy its
|
||||
// previously computed property as-is, since key-suffix replacement does not
|
||||
// change values, while a collector that depends only on key suffixes, like one
|
||||
// which collected mvcc-timestamp bounds from timestamp-suffixed keys, can just
|
||||
// set its new bounds from the new suffix, as it is common to all keys, without
|
||||
// needing to recompute it from every key.
|
||||
type SuffixReplaceableTableCollector interface {
|
||||
// UpdateKeySuffixes is called when a table is updated to change the suffix of
|
||||
// all keys in the table, and is passed the old value for that prop, if any,
|
||||
// for that table as well as the old and new suffix.
|
||||
UpdateKeySuffixes(oldProps map[string]string, oldSuffix, newSuffix []byte) error
|
||||
}
|
||||
|
||||
// ReaderOptions holds the parameters needed for reading an sstable.
|
||||
type ReaderOptions struct {
|
||||
// Cache is used to cache uncompressed blocks from sstables.
|
||||
//
|
||||
// The default cache size is a zero-size cache.
|
||||
Cache *cache.Cache
|
||||
|
||||
// Comparer defines a total ordering over the space of []byte keys: a 'less
|
||||
// than' relationship. The same comparison algorithm must be used for reads
|
||||
// and writes over the lifetime of the DB.
|
||||
//
|
||||
// The default value uses the same ordering as bytes.Compare.
|
||||
Comparer *Comparer
|
||||
|
||||
// Filters is a map from filter policy name to filter policy. It is used for
|
||||
// debugging tools which may be used on multiple databases configured with
|
||||
// different filter policies. It is not necessary to populate this filters
|
||||
// map during normal usage of a DB.
|
||||
Filters map[string]FilterPolicy
|
||||
|
||||
// Merger defines the associative merge operation to use for merging values
|
||||
// written with {Batch,DB}.Merge. The MergerName is checked for consistency
|
||||
// with the value stored in the sstable when it was written.
|
||||
MergerName string
|
||||
}
|
||||
|
||||
func (o ReaderOptions) ensureDefaults() ReaderOptions {
|
||||
if o.Comparer == nil {
|
||||
o.Comparer = base.DefaultComparer
|
||||
}
|
||||
if o.MergerName == "" {
|
||||
o.MergerName = base.DefaultMerger.Name
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// WriterOptions holds the parameters used to control building an sstable.
|
||||
type WriterOptions struct {
|
||||
// BlockRestartInterval is the number of keys between restart points
|
||||
// for delta encoding of keys.
|
||||
//
|
||||
// The default value is 16.
|
||||
BlockRestartInterval int
|
||||
|
||||
// BlockSize is the target uncompressed size in bytes of each table block.
|
||||
//
|
||||
// The default value is 4096.
|
||||
BlockSize int
|
||||
|
||||
// BlockSizeThreshold finishes a block if the block size is larger than the
|
||||
// specified percentage of the target block size and adding the next entry
|
||||
// would cause the block to be larger than the target block size.
|
||||
//
|
||||
// The default value is 90
|
||||
BlockSizeThreshold int
|
||||
|
||||
// Cache is used to cache uncompressed blocks from sstables.
|
||||
//
|
||||
// The default is a nil cache.
|
||||
Cache *cache.Cache
|
||||
|
||||
// Comparer defines a total ordering over the space of []byte keys: a 'less
|
||||
// than' relationship. The same comparison algorithm must be used for reads
|
||||
// and writes over the lifetime of the DB.
|
||||
//
|
||||
// The default value uses the same ordering as bytes.Compare.
|
||||
Comparer *Comparer
|
||||
|
||||
// Compression defines the per-block compression to use.
|
||||
//
|
||||
// The default value (DefaultCompression) uses snappy compression.
|
||||
Compression Compression
|
||||
|
||||
// FilterPolicy defines a filter algorithm (such as a Bloom filter) that can
|
||||
// reduce disk reads for Get calls.
|
||||
//
|
||||
// One such implementation is bloom.FilterPolicy(10) from the pebble/bloom
|
||||
// package.
|
||||
//
|
||||
// The default value means to use no filter.
|
||||
FilterPolicy FilterPolicy
|
||||
|
||||
// FilterType defines whether an existing filter policy is applied at a
|
||||
// block-level or table-level. Block-level filters use less memory to create,
|
||||
// but are slower to access as a check for the key in the index must first be
|
||||
// performed to locate the filter block. A table-level filter will require
|
||||
// memory proportional to the number of keys in an sstable to create, but
|
||||
// avoids the index lookup when determining if a key is present. Table-level
|
||||
// filters should be preferred except under constrained memory situations.
|
||||
FilterType FilterType
|
||||
|
||||
// IndexBlockSize is the target uncompressed size in bytes of each index
|
||||
// block. When the index block size is larger than this target, two-level
|
||||
// indexes are automatically enabled. Setting this option to a large value
|
||||
// (such as math.MaxInt32) disables the automatic creation of two-level
|
||||
// indexes.
|
||||
//
|
||||
// The default value is the value of BlockSize.
|
||||
IndexBlockSize int
|
||||
|
||||
// Merger defines the associative merge operation to use for merging values
|
||||
// written with {Batch,DB}.Merge. The MergerName is checked for consistency
|
||||
// with the value stored in the sstable when it was written.
|
||||
MergerName string
|
||||
|
||||
// TableFormat specifies the format version for writing sstables. The default
|
||||
// is TableFormatRocksDBv2 which creates RocksDB compatible sstables. Use
|
||||
// TableFormatLevelDB to create LevelDB compatible sstable which can be used
|
||||
// by a wider range of tools and libraries.
|
||||
TableFormat TableFormat
|
||||
|
||||
// TablePropertyCollectors is a list of TablePropertyCollector creation
|
||||
// functions. A new TablePropertyCollector is created for each sstable built
|
||||
// and lives for the lifetime of the table.
|
||||
TablePropertyCollectors []func() TablePropertyCollector
|
||||
|
||||
// BlockPropertyCollectors is a list of BlockPropertyCollector creation
|
||||
// functions. A new BlockPropertyCollector is created for each sstable
|
||||
// built and lives for the lifetime of writing that table.
|
||||
BlockPropertyCollectors []func() BlockPropertyCollector
|
||||
|
||||
// Checksum specifies which checksum to use.
|
||||
Checksum ChecksumType
|
||||
|
||||
// Parallelism is used to indicate that the sstable Writer is allowed to
|
||||
// compress data blocks and write datablocks to disk in parallel with the
|
||||
// Writer client goroutine.
|
||||
Parallelism bool
|
||||
}
|
||||
|
||||
func (o WriterOptions) ensureDefaults() WriterOptions {
|
||||
if o.BlockRestartInterval <= 0 {
|
||||
o.BlockRestartInterval = base.DefaultBlockRestartInterval
|
||||
}
|
||||
if o.BlockSize <= 0 {
|
||||
o.BlockSize = base.DefaultBlockSize
|
||||
}
|
||||
if o.BlockSizeThreshold <= 0 {
|
||||
o.BlockSizeThreshold = base.DefaultBlockSizeThreshold
|
||||
}
|
||||
if o.Comparer == nil {
|
||||
o.Comparer = base.DefaultComparer
|
||||
}
|
||||
if o.Compression <= DefaultCompression || o.Compression >= NCompression {
|
||||
o.Compression = SnappyCompression
|
||||
}
|
||||
if o.IndexBlockSize <= 0 {
|
||||
o.IndexBlockSize = o.BlockSize
|
||||
}
|
||||
if o.MergerName == "" {
|
||||
o.MergerName = base.DefaultMerger.Name
|
||||
}
|
||||
if o.Checksum == ChecksumTypeNone {
|
||||
o.Checksum = ChecksumTypeCRC32c
|
||||
}
|
||||
// By default, if the table format is not specified, fall back to using the
|
||||
// most compatible format.
|
||||
if o.TableFormat == TableFormatUnspecified {
|
||||
o.TableFormat = TableFormatRocksDBv2
|
||||
}
|
||||
return o
|
||||
}
|
||||
363
vendor/github.com/cockroachdb/pebble/sstable/properties.go
generated
vendored
Normal file
363
vendor/github.com/cockroachdb/pebble/sstable/properties.go
generated
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
// Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/intern"
|
||||
)
|
||||
|
||||
const propertiesBlockRestartInterval = math.MaxInt32
|
||||
const propGlobalSeqnumName = "rocksdb.external_sst_file.global_seqno"
|
||||
|
||||
var propTagMap = make(map[string]reflect.StructField)
|
||||
var propBoolTrue = []byte{'1'}
|
||||
var propBoolFalse = []byte{'0'}
|
||||
|
||||
var columnFamilyIDField = func() reflect.StructField {
|
||||
f, ok := reflect.TypeOf(Properties{}).FieldByName("ColumnFamilyID")
|
||||
if !ok {
|
||||
panic("Properties.ColumnFamilyID field not found")
|
||||
}
|
||||
return f
|
||||
}()
|
||||
|
||||
var propOffsetTagMap = make(map[uintptr]string)
|
||||
|
||||
func init() {
|
||||
t := reflect.TypeOf(Properties{})
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if tag := f.Tag.Get("prop"); tag != "" {
|
||||
switch f.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
case reflect.Uint32:
|
||||
case reflect.Uint64:
|
||||
case reflect.String:
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported property field type: %s %s", f.Name, f.Type))
|
||||
}
|
||||
propTagMap[tag] = f
|
||||
propOffsetTagMap[f.Offset] = tag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Properties holds the sstable property values. The properties are
|
||||
// automatically populated during sstable creation and load from the properties
|
||||
// meta block when an sstable is opened.
|
||||
type Properties struct {
|
||||
// ID of column family for this SST file, corresponding to the CF identified
|
||||
// by column_family_name.
|
||||
ColumnFamilyID uint64 `prop:"rocksdb.column.family.id"`
|
||||
// Name of the column family with which this SST file is associated. Empty if
|
||||
// the column family is unknown.
|
||||
ColumnFamilyName string `prop:"rocksdb.column.family.name"`
|
||||
// The name of the comparer used in this table.
|
||||
ComparerName string `prop:"rocksdb.comparator"`
|
||||
// The compression algorithm used to compress blocks.
|
||||
CompressionName string `prop:"rocksdb.compression"`
|
||||
// The compression options used to compress blocks.
|
||||
CompressionOptions string `prop:"rocksdb.compression_options"`
|
||||
// The time when the SST file was created. Since SST files are immutable,
|
||||
// this is equivalent to last modified time.
|
||||
CreationTime uint64 `prop:"rocksdb.creation.time"`
|
||||
// The total size of all data blocks.
|
||||
DataSize uint64 `prop:"rocksdb.data.size"`
|
||||
// The external sstable version format. Version 2 is the one RocksDB has been
|
||||
// using since 5.13. RocksDB only uses the global sequence number for an
|
||||
// sstable if this property has been set.
|
||||
ExternalFormatVersion uint32 `prop:"rocksdb.external_sst_file.version"`
|
||||
// Actual SST file creation time. 0 means unknown.
|
||||
FileCreationTime uint64 `prop:"rocksdb.file.creation.time"`
|
||||
// The name of the filter policy used in this table. Empty if no filter
|
||||
// policy is used.
|
||||
FilterPolicyName string `prop:"rocksdb.filter.policy"`
|
||||
// The size of filter block.
|
||||
FilterSize uint64 `prop:"rocksdb.filter.size"`
|
||||
// If 0, key is variable length. Otherwise number of bytes for each key.
|
||||
FixedKeyLen uint64 `prop:"rocksdb.fixed.key.length"`
|
||||
// Format version, reserved for backward compatibility.
|
||||
FormatVersion uint64 `prop:"rocksdb.format.version"`
|
||||
// The global sequence number to use for all entries in the table. Present if
|
||||
// the table was created externally and ingested whole.
|
||||
GlobalSeqNum uint64 `prop:"rocksdb.external_sst_file.global_seqno"`
|
||||
// Whether the index key is user key or an internal key.
|
||||
IndexKeyIsUserKey uint64 `prop:"rocksdb.index.key.is.user.key"`
|
||||
// Total number of index partitions if kTwoLevelIndexSearch is used.
|
||||
IndexPartitions uint64 `prop:"rocksdb.index.partitions"`
|
||||
// The size of index block.
|
||||
IndexSize uint64 `prop:"rocksdb.index.size"`
|
||||
// The index type. TODO(peter): add a more detailed description.
|
||||
IndexType uint32 `prop:"rocksdb.block.based.table.index.type"`
|
||||
// Whether delta encoding is used to encode the index values.
|
||||
IndexValueIsDeltaEncoded uint64 `prop:"rocksdb.index.value.is.delta.encoded"`
|
||||
// The name of the merger used in this table. Empty if no merger is used.
|
||||
MergerName string `prop:"rocksdb.merge.operator"`
|
||||
// The number of blocks in this table.
|
||||
NumDataBlocks uint64 `prop:"rocksdb.num.data.blocks"`
|
||||
// The number of deletion entries in this table, including both point and
|
||||
// range deletions.
|
||||
NumDeletions uint64 `prop:"rocksdb.deleted.keys"`
|
||||
// The number of entries in this table.
|
||||
NumEntries uint64 `prop:"rocksdb.num.entries"`
|
||||
// The number of merge operands in the table.
|
||||
NumMergeOperands uint64 `prop:"rocksdb.merge.operands"`
|
||||
// The number of range deletions in this table.
|
||||
NumRangeDeletions uint64 `prop:"rocksdb.num.range-deletions"`
|
||||
// The number of RANGEKEYDELs in this table.
|
||||
NumRangeKeyDels uint64 `prop:"pebble.num.range-key-dels"`
|
||||
// The number of RANGEKEYSETs in this table.
|
||||
NumRangeKeySets uint64 `prop:"pebble.num.range-key-sets"`
|
||||
// The number of RANGEKEYUNSETs in this table.
|
||||
NumRangeKeyUnsets uint64 `prop:"pebble.num.range-key-unsets"`
|
||||
// Timestamp of the earliest key. 0 if unknown.
|
||||
OldestKeyTime uint64 `prop:"rocksdb.oldest.key.time"`
|
||||
// The name of the prefix extractor used in this table. Empty if no prefix
|
||||
// extractor is used.
|
||||
PrefixExtractorName string `prop:"rocksdb.prefix.extractor.name"`
|
||||
// If filtering is enabled, was the filter created on the key prefix.
|
||||
PrefixFiltering bool `prop:"rocksdb.block.based.table.prefix.filtering"`
|
||||
// A comma separated list of names of the property collectors used in this
|
||||
// table.
|
||||
PropertyCollectorNames string `prop:"rocksdb.property.collectors"`
|
||||
// Total raw key size.
|
||||
RawKeySize uint64 `prop:"rocksdb.raw.key.size"`
|
||||
// Total raw rangekey key size.
|
||||
RawRangeKeyKeySize uint64 `prop:"pebble.raw.range-key.key.size"`
|
||||
// Total raw rangekey value size.
|
||||
RawRangeKeyValueSize uint64 `prop:"pebble.raw.range-key.value.size"`
|
||||
// Total raw value size.
|
||||
RawValueSize uint64 `prop:"rocksdb.raw.value.size"`
|
||||
// Size of the top-level index if kTwoLevelIndexSearch is used.
|
||||
TopLevelIndexSize uint64 `prop:"rocksdb.top-level.index.size"`
|
||||
// User collected properties.
|
||||
UserProperties map[string]string
|
||||
// If filtering is enabled, was the filter created on the whole key.
|
||||
WholeKeyFiltering bool `prop:"rocksdb.block.based.table.whole.key.filtering"`
|
||||
|
||||
// Loaded set indicating which fields have been loaded from disk. Indexed by
|
||||
// the field's byte offset within the struct
|
||||
// (reflect.StructField.Offset). Only set if the properties have been loaded
|
||||
// from a file. Only exported for testing purposes.
|
||||
Loaded map[uintptr]struct{}
|
||||
}
|
||||
|
||||
// NumPointDeletions returns the number of point deletions in this table.
|
||||
func (p *Properties) NumPointDeletions() uint64 {
|
||||
return p.NumDeletions - p.NumRangeDeletions
|
||||
}
|
||||
|
||||
// NumRangeKeys returns a count of the number of range keys in this table.
|
||||
func (p *Properties) NumRangeKeys() uint64 {
|
||||
return p.NumRangeKeyDels + p.NumRangeKeySets + p.NumRangeKeyUnsets
|
||||
}
|
||||
|
||||
func (p *Properties) String() string {
|
||||
var buf bytes.Buffer
|
||||
v := reflect.ValueOf(*p)
|
||||
vt := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
ft := vt.Field(i)
|
||||
tag := ft.Tag.Get("prop")
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
f := v.Field(i)
|
||||
// TODO(peter): Use f.IsZero() when we can rely on go1.13.
|
||||
if zero := reflect.Zero(f.Type()); zero.Interface() == f.Interface() {
|
||||
// Skip printing of zero values which were not loaded from disk.
|
||||
if _, ok := p.Loaded[ft.Offset]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(&buf, "%s: ", tag)
|
||||
switch ft.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
fmt.Fprintf(&buf, "%t\n", f.Bool())
|
||||
case reflect.Uint32:
|
||||
fmt.Fprintf(&buf, "%d\n", f.Uint())
|
||||
case reflect.Uint64:
|
||||
u := f.Uint()
|
||||
if ft.Offset == columnFamilyIDField.Offset && u == math.MaxInt32 {
|
||||
fmt.Fprintf(&buf, "-\n")
|
||||
} else {
|
||||
fmt.Fprintf(&buf, "%d\n", f.Uint())
|
||||
}
|
||||
case reflect.String:
|
||||
fmt.Fprintf(&buf, "%s\n", f.String())
|
||||
default:
|
||||
panic("not reached")
|
||||
}
|
||||
}
|
||||
keys := make([]string, 0, len(p.UserProperties))
|
||||
for key := range p.UserProperties {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
fmt.Fprintf(&buf, "%s: %s\n", key, p.UserProperties[key])
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (p *Properties) load(b block, blockOffset uint64) error {
|
||||
i, err := newRawBlockIter(bytes.Compare, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Loaded = make(map[uintptr]struct{})
|
||||
v := reflect.ValueOf(p).Elem()
|
||||
for valid := i.First(); valid; valid = i.Next() {
|
||||
tag := intern.Bytes(i.Key().UserKey)
|
||||
if f, ok := propTagMap[tag]; ok {
|
||||
p.Loaded[f.Offset] = struct{}{}
|
||||
field := v.FieldByIndex(f.Index)
|
||||
switch f.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
field.SetBool(bytes.Equal(i.Value(), propBoolTrue))
|
||||
case reflect.Uint32:
|
||||
field.SetUint(uint64(binary.LittleEndian.Uint32(i.Value())))
|
||||
case reflect.Uint64:
|
||||
var n uint64
|
||||
if tag == propGlobalSeqnumName {
|
||||
n = binary.LittleEndian.Uint64(i.Value())
|
||||
} else {
|
||||
n, _ = binary.Uvarint(i.Value())
|
||||
}
|
||||
field.SetUint(n)
|
||||
case reflect.String:
|
||||
field.SetString(intern.Bytes(i.Value()))
|
||||
default:
|
||||
panic("not reached")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if p.UserProperties == nil {
|
||||
p.UserProperties = make(map[string]string)
|
||||
}
|
||||
p.UserProperties[tag] = string(i.Value())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Properties) saveBool(m map[string][]byte, offset uintptr, value bool) {
|
||||
tag := propOffsetTagMap[offset]
|
||||
if value {
|
||||
m[tag] = propBoolTrue
|
||||
} else {
|
||||
m[tag] = propBoolFalse
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Properties) saveUint32(m map[string][]byte, offset uintptr, value uint32) {
|
||||
var buf [4]byte
|
||||
binary.LittleEndian.PutUint32(buf[:], value)
|
||||
m[propOffsetTagMap[offset]] = buf[:]
|
||||
}
|
||||
|
||||
func (p *Properties) saveUint64(m map[string][]byte, offset uintptr, value uint64) {
|
||||
var buf [8]byte
|
||||
binary.LittleEndian.PutUint64(buf[:], value)
|
||||
m[propOffsetTagMap[offset]] = buf[:]
|
||||
}
|
||||
|
||||
func (p *Properties) saveUvarint(m map[string][]byte, offset uintptr, value uint64) {
|
||||
var buf [10]byte
|
||||
n := binary.PutUvarint(buf[:], value)
|
||||
m[propOffsetTagMap[offset]] = buf[:n]
|
||||
}
|
||||
|
||||
func (p *Properties) saveString(m map[string][]byte, offset uintptr, value string) {
|
||||
m[propOffsetTagMap[offset]] = []byte(value)
|
||||
}
|
||||
|
||||
func (p *Properties) save(w *rawBlockWriter) {
|
||||
m := make(map[string][]byte)
|
||||
for k, v := range p.UserProperties {
|
||||
m[k] = []byte(v)
|
||||
}
|
||||
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.ColumnFamilyID), p.ColumnFamilyID)
|
||||
if p.ColumnFamilyName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.ColumnFamilyName), p.ColumnFamilyName)
|
||||
}
|
||||
if p.ComparerName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.ComparerName), p.ComparerName)
|
||||
}
|
||||
if p.CompressionName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.CompressionName), p.CompressionName)
|
||||
}
|
||||
if p.CompressionOptions != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.CompressionOptions), p.CompressionOptions)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.CreationTime), p.CreationTime)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.DataSize), p.DataSize)
|
||||
if p.ExternalFormatVersion != 0 {
|
||||
p.saveUint32(m, unsafe.Offsetof(p.ExternalFormatVersion), p.ExternalFormatVersion)
|
||||
p.saveUint64(m, unsafe.Offsetof(p.GlobalSeqNum), p.GlobalSeqNum)
|
||||
}
|
||||
if p.FileCreationTime > 0 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.FileCreationTime), p.FileCreationTime)
|
||||
}
|
||||
if p.FilterPolicyName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.FilterPolicyName), p.FilterPolicyName)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.FilterSize), p.FilterSize)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.FixedKeyLen), p.FixedKeyLen)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.FormatVersion), p.FormatVersion)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.IndexKeyIsUserKey), p.IndexKeyIsUserKey)
|
||||
if p.IndexPartitions != 0 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.IndexPartitions), p.IndexPartitions)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.TopLevelIndexSize), p.TopLevelIndexSize)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.IndexSize), p.IndexSize)
|
||||
p.saveUint32(m, unsafe.Offsetof(p.IndexType), p.IndexType)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.IndexValueIsDeltaEncoded), p.IndexValueIsDeltaEncoded)
|
||||
if p.MergerName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.MergerName), p.MergerName)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumDataBlocks), p.NumDataBlocks)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumEntries), p.NumEntries)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumDeletions), p.NumDeletions)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumMergeOperands), p.NumMergeOperands)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeDeletions), p.NumRangeDeletions)
|
||||
if p.NumRangeKeys() > 0 {
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeKeyDels), p.NumRangeKeyDels)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeKeySets), p.NumRangeKeySets)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.NumRangeKeyUnsets), p.NumRangeKeyUnsets)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.RawRangeKeyKeySize), p.RawRangeKeyKeySize)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.RawRangeKeyValueSize), p.RawRangeKeyValueSize)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.OldestKeyTime), p.OldestKeyTime)
|
||||
if p.PrefixExtractorName != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.PrefixExtractorName), p.PrefixExtractorName)
|
||||
}
|
||||
p.saveBool(m, unsafe.Offsetof(p.PrefixFiltering), p.PrefixFiltering)
|
||||
if p.PropertyCollectorNames != "" {
|
||||
p.saveString(m, unsafe.Offsetof(p.PropertyCollectorNames), p.PropertyCollectorNames)
|
||||
}
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.RawKeySize), p.RawKeySize)
|
||||
p.saveUvarint(m, unsafe.Offsetof(p.RawValueSize), p.RawValueSize)
|
||||
p.saveBool(m, unsafe.Offsetof(p.WholeKeyFiltering), p.WholeKeyFiltering)
|
||||
|
||||
keys := make([]string, 0, len(m))
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
w.add(InternalKey{UserKey: []byte(key)}, m[key])
|
||||
}
|
||||
}
|
||||
273
vendor/github.com/cockroachdb/pebble/sstable/raw_block.go
generated
vendored
Normal file
273
vendor/github.com/cockroachdb/pebble/sstable/raw_block.go
generated
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
// Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
)
|
||||
|
||||
type rawBlockWriter struct {
|
||||
blockWriter
|
||||
}
|
||||
|
||||
func (w *rawBlockWriter) add(key InternalKey, value []byte) {
|
||||
w.curKey, w.prevKey = w.prevKey, w.curKey
|
||||
|
||||
size := len(key.UserKey)
|
||||
if cap(w.curKey) < size {
|
||||
w.curKey = make([]byte, 0, size*2)
|
||||
}
|
||||
w.curKey = w.curKey[:size]
|
||||
copy(w.curKey, key.UserKey)
|
||||
|
||||
w.store(size, value)
|
||||
}
|
||||
|
||||
// rawBlockIter is an iterator over a single block of data. Unlike blockIter,
|
||||
// keys are stored in "raw" format (i.e. not as internal keys). Note that there
|
||||
// is significant similarity between this code and the code in blockIter. Yet
|
||||
// reducing duplication is difficult due to the blockIter being performance
|
||||
// critical.
|
||||
type rawBlockIter struct {
|
||||
cmp Compare
|
||||
offset int32
|
||||
nextOffset int32
|
||||
restarts int32
|
||||
numRestarts int32
|
||||
ptr unsafe.Pointer
|
||||
data []byte
|
||||
key, val []byte
|
||||
ikey InternalKey
|
||||
cached []blockEntry
|
||||
cachedBuf []byte
|
||||
}
|
||||
|
||||
func newRawBlockIter(cmp Compare, block block) (*rawBlockIter, error) {
|
||||
i := &rawBlockIter{}
|
||||
return i, i.init(cmp, block)
|
||||
}
|
||||
|
||||
func (i *rawBlockIter) init(cmp Compare, block block) error {
|
||||
numRestarts := int32(binary.LittleEndian.Uint32(block[len(block)-4:]))
|
||||
if numRestarts == 0 {
|
||||
return base.CorruptionErrorf("pebble/table: invalid table (block has no restart points)")
|
||||
}
|
||||
i.cmp = cmp
|
||||
i.restarts = int32(len(block)) - 4*(1+numRestarts)
|
||||
i.numRestarts = numRestarts
|
||||
i.ptr = unsafe.Pointer(&block[0])
|
||||
i.data = block
|
||||
if i.key == nil {
|
||||
i.key = make([]byte, 0, 256)
|
||||
} else {
|
||||
i.key = i.key[:0]
|
||||
}
|
||||
i.val = nil
|
||||
i.clearCache()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *rawBlockIter) readEntry() {
|
||||
ptr := unsafe.Pointer(uintptr(i.ptr) + uintptr(i.offset))
|
||||
shared, ptr := decodeVarint(ptr)
|
||||
unshared, ptr := decodeVarint(ptr)
|
||||
value, ptr := decodeVarint(ptr)
|
||||
i.key = append(i.key[:shared], getBytes(ptr, int(unshared))...)
|
||||
i.key = i.key[:len(i.key):len(i.key)]
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + uintptr(unshared))
|
||||
i.val = getBytes(ptr, int(value))
|
||||
i.nextOffset = int32(uintptr(ptr)-uintptr(i.ptr)) + int32(value)
|
||||
}
|
||||
|
||||
func (i *rawBlockIter) loadEntry() {
|
||||
i.readEntry()
|
||||
i.ikey.UserKey = i.key
|
||||
}
|
||||
|
||||
func (i *rawBlockIter) clearCache() {
|
||||
i.cached = i.cached[:0]
|
||||
i.cachedBuf = i.cachedBuf[:0]
|
||||
}
|
||||
|
||||
func (i *rawBlockIter) cacheEntry() {
|
||||
var valStart int32
|
||||
valSize := int32(len(i.val))
|
||||
if valSize > 0 {
|
||||
valStart = int32(uintptr(unsafe.Pointer(&i.val[0])) - uintptr(i.ptr))
|
||||
}
|
||||
|
||||
i.cached = append(i.cached, blockEntry{
|
||||
offset: i.offset,
|
||||
keyStart: int32(len(i.cachedBuf)),
|
||||
keyEnd: int32(len(i.cachedBuf) + len(i.key)),
|
||||
valStart: valStart,
|
||||
valSize: valSize,
|
||||
})
|
||||
i.cachedBuf = append(i.cachedBuf, i.key...)
|
||||
}
|
||||
|
||||
// SeekGE implements internalIterator.SeekGE, as documented in the pebble
|
||||
// package.
|
||||
func (i *rawBlockIter) SeekGE(key []byte) bool {
|
||||
// Find the index of the smallest restart point whose key is > the key
|
||||
// sought; index will be numRestarts if there is no such restart point.
|
||||
i.offset = 0
|
||||
index := sort.Search(int(i.numRestarts), func(j int) bool {
|
||||
offset := int32(binary.LittleEndian.Uint32(i.data[int(i.restarts)+4*j:]))
|
||||
// For a restart point, there are 0 bytes shared with the previous key.
|
||||
// The varint encoding of 0 occupies 1 byte.
|
||||
ptr := unsafe.Pointer(uintptr(i.ptr) + uintptr(offset+1))
|
||||
// Decode the key at that restart point, and compare it to the key sought.
|
||||
v1, ptr := decodeVarint(ptr)
|
||||
_, ptr = decodeVarint(ptr)
|
||||
s := getBytes(ptr, int(v1))
|
||||
return i.cmp(key, s) < 0
|
||||
})
|
||||
|
||||
// Since keys are strictly increasing, if index > 0 then the restart point at
|
||||
// index-1 will be the largest whose key is <= the key sought. If index ==
|
||||
// 0, then all keys in this block are larger than the key sought, and offset
|
||||
// remains at zero.
|
||||
if index > 0 {
|
||||
i.offset = int32(binary.LittleEndian.Uint32(i.data[int(i.restarts)+4*(index-1):]))
|
||||
}
|
||||
i.loadEntry()
|
||||
|
||||
// Iterate from that restart point to somewhere >= the key sought.
|
||||
for valid := i.Valid(); valid; valid = i.Next() {
|
||||
if i.cmp(key, i.key) <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return i.Valid()
|
||||
}
|
||||
|
||||
// SeekPrefixGE implements internalIterator.SeekPrefixGE, as documented in the
|
||||
// pebble package.
|
||||
func (i *rawBlockIter) SeekPrefixGE(key []byte) bool {
|
||||
// This should never be called as prefix iteration is never used with raw blocks.
|
||||
panic("pebble: SeekPrefixGE unimplemented")
|
||||
}
|
||||
|
||||
// SeekLT implements internalIterator.SeekLT, as documented in the pebble
|
||||
// package.
|
||||
func (i *rawBlockIter) SeekLT(key []byte) bool {
|
||||
panic("pebble/table: SeekLT unimplemented")
|
||||
}
|
||||
|
||||
// First implements internalIterator.First, as documented in the pebble
|
||||
// package.
|
||||
func (i *rawBlockIter) First() bool {
|
||||
i.offset = 0
|
||||
i.loadEntry()
|
||||
return i.Valid()
|
||||
}
|
||||
|
||||
// Last implements internalIterator.Last, as documented in the pebble package.
|
||||
func (i *rawBlockIter) Last() bool {
|
||||
// Seek forward from the last restart point.
|
||||
i.offset = int32(binary.LittleEndian.Uint32(i.data[i.restarts+4*(i.numRestarts-1):]))
|
||||
|
||||
i.readEntry()
|
||||
i.clearCache()
|
||||
i.cacheEntry()
|
||||
|
||||
for i.nextOffset < i.restarts {
|
||||
i.offset = i.nextOffset
|
||||
i.readEntry()
|
||||
i.cacheEntry()
|
||||
}
|
||||
|
||||
i.ikey.UserKey = i.key
|
||||
return i.Valid()
|
||||
}
|
||||
|
||||
// Next implements internalIterator.Next, as documented in the pebble
|
||||
// package.
|
||||
func (i *rawBlockIter) Next() bool {
|
||||
i.offset = i.nextOffset
|
||||
if !i.Valid() {
|
||||
return false
|
||||
}
|
||||
i.loadEntry()
|
||||
return true
|
||||
}
|
||||
|
||||
// Prev implements internalIterator.Prev, as documented in the pebble
|
||||
// package.
|
||||
func (i *rawBlockIter) Prev() bool {
|
||||
if n := len(i.cached) - 1; n > 0 && i.cached[n].offset == i.offset {
|
||||
i.nextOffset = i.offset
|
||||
e := &i.cached[n-1]
|
||||
i.offset = e.offset
|
||||
i.val = getBytes(unsafe.Pointer(uintptr(i.ptr)+uintptr(e.valStart)), int(e.valSize))
|
||||
i.ikey.UserKey = i.cachedBuf[e.keyStart:e.keyEnd]
|
||||
i.cached = i.cached[:n]
|
||||
return true
|
||||
}
|
||||
|
||||
if i.offset == 0 {
|
||||
i.offset = -1
|
||||
i.nextOffset = 0
|
||||
return false
|
||||
}
|
||||
|
||||
targetOffset := i.offset
|
||||
index := sort.Search(int(i.numRestarts), func(j int) bool {
|
||||
offset := int32(binary.LittleEndian.Uint32(i.data[int(i.restarts)+4*j:]))
|
||||
return offset >= targetOffset
|
||||
})
|
||||
i.offset = 0
|
||||
if index > 0 {
|
||||
i.offset = int32(binary.LittleEndian.Uint32(i.data[int(i.restarts)+4*(index-1):]))
|
||||
}
|
||||
|
||||
i.readEntry()
|
||||
i.clearCache()
|
||||
i.cacheEntry()
|
||||
|
||||
for i.nextOffset < targetOffset {
|
||||
i.offset = i.nextOffset
|
||||
i.readEntry()
|
||||
i.cacheEntry()
|
||||
}
|
||||
|
||||
i.ikey.UserKey = i.key
|
||||
return true
|
||||
}
|
||||
|
||||
// Key implements internalIterator.Key, as documented in the pebble package.
|
||||
func (i *rawBlockIter) Key() InternalKey {
|
||||
return i.ikey
|
||||
}
|
||||
|
||||
// Value implements internalIterator.Value, as documented in the pebble
|
||||
// package.
|
||||
func (i *rawBlockIter) Value() []byte {
|
||||
return i.val
|
||||
}
|
||||
|
||||
// Valid implements internalIterator.Valid, as documented in the pebble
|
||||
// package.
|
||||
func (i *rawBlockIter) Valid() bool {
|
||||
return i.offset >= 0 && i.offset < i.restarts
|
||||
}
|
||||
|
||||
// Error implements internalIterator.Error, as documented in the pebble
|
||||
// package.
|
||||
func (i *rawBlockIter) Error() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements internalIterator.Close, as documented in the pebble
|
||||
// package.
|
||||
func (i *rawBlockIter) Close() error {
|
||||
i.val = nil
|
||||
return nil
|
||||
}
|
||||
3089
vendor/github.com/cockroachdb/pebble/sstable/reader.go
generated
vendored
Normal file
3089
vendor/github.com/cockroachdb/pebble/sstable/reader.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
429
vendor/github.com/cockroachdb/pebble/sstable/suffix_rewriter.go
generated
vendored
Normal file
429
vendor/github.com/cockroachdb/pebble/sstable/suffix_rewriter.go
generated
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
// RewriteKeySuffixes copies the content of the passed SSTable bytes to a new
|
||||
// sstable, written to `out`, in which the suffix `from` has is replaced with
|
||||
// `to` in every key. The input sstable must consist of only Sets and every key
|
||||
// must have `from` as its suffix as determined by the Split function of the
|
||||
// Comparer in the passed WriterOptions.
|
||||
//
|
||||
// Data blocks are rewritten in parallel by `concurrency` workers and then
|
||||
// assembled into a final SST. Filters are copied from the original SST without
|
||||
// modification as they are not affected by the suffix, while block and table
|
||||
// properties are only minimally recomputed.
|
||||
//
|
||||
// Any block and table property collectors configured in the WriterOptions must
|
||||
// implement SuffixReplaceableTableCollector/SuffixReplaceableBlockCollector.
|
||||
func RewriteKeySuffixes(
|
||||
sst []byte,
|
||||
rOpts ReaderOptions,
|
||||
out writeCloseSyncer,
|
||||
o WriterOptions,
|
||||
from, to []byte,
|
||||
concurrency int,
|
||||
) (*WriterMetadata, error) {
|
||||
r, err := NewMemReader(sst, rOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
return rewriteKeySuffixesInBlocks(r, out, o, from, to, concurrency)
|
||||
}
|
||||
|
||||
func rewriteKeySuffixesInBlocks(
|
||||
r *Reader, out writeCloseSyncer, o WriterOptions, from, to []byte, concurrency int,
|
||||
) (*WriterMetadata, error) {
|
||||
if o.Comparer == nil || o.Comparer.Split == nil {
|
||||
return nil, errors.New("a valid splitter is required to define suffix to replace replace suffix")
|
||||
}
|
||||
if concurrency < 1 {
|
||||
return nil, errors.New("concurrency must be >= 1")
|
||||
}
|
||||
|
||||
w := NewWriter(out, o)
|
||||
defer w.Close()
|
||||
|
||||
for _, c := range w.propCollectors {
|
||||
if _, ok := c.(SuffixReplaceableTableCollector); !ok {
|
||||
return nil, errors.Errorf("property collector %s does not support suffix replacement", c.Name())
|
||||
}
|
||||
}
|
||||
for _, c := range w.blockPropCollectors {
|
||||
if _, ok := c.(SuffixReplaceableBlockCollector); !ok {
|
||||
return nil, errors.Errorf("block property collector %s does not support suffix replacement", c.Name())
|
||||
}
|
||||
}
|
||||
|
||||
l, err := r.Layout()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "reading layout")
|
||||
}
|
||||
|
||||
if err := rewriteDataBlocksToWriter(r, w, l.Data, from, to, w.split, concurrency); err != nil {
|
||||
return nil, errors.Wrap(err, "rewriting data blocks")
|
||||
}
|
||||
|
||||
// Copy over the filter block if it exists (rewriteDataBlocksToWriter will
|
||||
// already have ensured this is valid if it exists).
|
||||
if w.filter != nil {
|
||||
if l.Filter.Length == 0 {
|
||||
return nil, errors.New("input table has no filter")
|
||||
}
|
||||
filterBlock, _, err := readBlockBuf(r, l.Filter, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "reading filter")
|
||||
}
|
||||
w.filter = copyFilterWriter{
|
||||
origPolicyName: w.filter.policyName(), origMetaName: w.filter.metaName(), data: filterBlock,
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return w.Metadata()
|
||||
}
|
||||
|
||||
var errBadKind = errors.New("key does not have expected kind (set)")
|
||||
|
||||
type blockWithSpan struct {
|
||||
start, end InternalKey
|
||||
data []byte
|
||||
}
|
||||
|
||||
func rewriteBlocks(
|
||||
r *Reader,
|
||||
restartInterval int,
|
||||
checksumType ChecksumType,
|
||||
compression Compression,
|
||||
input []BlockHandleWithProperties,
|
||||
output []blockWithSpan,
|
||||
totalWorkers, worker int,
|
||||
from, to []byte,
|
||||
split Split,
|
||||
) error {
|
||||
bw := blockWriter{
|
||||
restartInterval: restartInterval,
|
||||
}
|
||||
buf := blockBuf{checksummer: checksummer{checksumType: checksumType}}
|
||||
if checksumType == ChecksumTypeXXHash {
|
||||
buf.checksummer.xxHasher = xxhash.New()
|
||||
}
|
||||
|
||||
var blockAlloc []byte
|
||||
var keyAlloc []byte
|
||||
var scratch InternalKey
|
||||
|
||||
var inputBlock, inputBlockBuf []byte
|
||||
|
||||
iter := &blockIter{}
|
||||
|
||||
// We'll assume all blocks are _roughly_ equal so round-robin static partition
|
||||
// of each worker doing every ith block is probably enough.
|
||||
for i := worker; i < len(input); i += totalWorkers {
|
||||
bh := input[i]
|
||||
|
||||
var err error
|
||||
inputBlock, inputBlockBuf, err = readBlockBuf(r, bh.BlockHandle, inputBlockBuf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := iter.init(r.Compare, inputBlock, r.Properties.GlobalSeqNum); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cap(bw.restarts) < int(iter.restarts) {
|
||||
bw.restarts = make([]uint32, 0, iter.restarts)
|
||||
}
|
||||
if cap(bw.buf) == 0 {
|
||||
bw.buf = make([]byte, 0, len(inputBlock))
|
||||
}
|
||||
if cap(bw.restarts) < int(iter.numRestarts) {
|
||||
bw.restarts = make([]uint32, 0, iter.numRestarts)
|
||||
}
|
||||
|
||||
for key, val := iter.First(); key != nil; key, val = iter.Next() {
|
||||
if key.Kind() != InternalKeyKindSet {
|
||||
return errBadKind
|
||||
}
|
||||
si := split(key.UserKey)
|
||||
oldSuffix := key.UserKey[si:]
|
||||
if !bytes.Equal(oldSuffix, from) {
|
||||
err := errors.Errorf("key has suffix %q, expected %q", oldSuffix, from)
|
||||
return err
|
||||
}
|
||||
newLen := si + len(to)
|
||||
if cap(scratch.UserKey) < newLen {
|
||||
scratch.UserKey = make([]byte, 0, len(key.UserKey)*2+len(to)-len(from))
|
||||
}
|
||||
|
||||
scratch.Trailer = key.Trailer
|
||||
scratch.UserKey = scratch.UserKey[:newLen]
|
||||
copy(scratch.UserKey, key.UserKey[:si])
|
||||
copy(scratch.UserKey[si:], to)
|
||||
|
||||
bw.add(scratch, val)
|
||||
if output[i].start.UserKey == nil {
|
||||
keyAlloc, output[i].start = cloneKeyWithBuf(scratch, keyAlloc)
|
||||
}
|
||||
}
|
||||
*iter = iter.resetForReuse()
|
||||
|
||||
keyAlloc, output[i].end = cloneKeyWithBuf(scratch, keyAlloc)
|
||||
|
||||
finished := compressAndChecksum(bw.finish(), compression, &buf)
|
||||
|
||||
// copy our finished block into the output buffer.
|
||||
sz := len(finished) + blockTrailerLen
|
||||
if cap(blockAlloc) < sz {
|
||||
blockAlloc = make([]byte, sz*128)
|
||||
}
|
||||
output[i].data = blockAlloc[:sz:sz]
|
||||
blockAlloc = blockAlloc[sz:]
|
||||
copy(output[i].data, finished)
|
||||
copy(output[i].data[len(finished):], buf.tmp[:blockTrailerLen])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func rewriteDataBlocksToWriter(
|
||||
r *Reader,
|
||||
w *Writer,
|
||||
data []BlockHandleWithProperties,
|
||||
from, to []byte,
|
||||
split Split,
|
||||
concurrency int,
|
||||
) error {
|
||||
blocks := make([]blockWithSpan, len(data))
|
||||
|
||||
if w.filter != nil {
|
||||
if r.Properties.FilterPolicyName != w.filter.policyName() {
|
||||
return errors.New("mismatched filters")
|
||||
}
|
||||
if was, is := r.Properties.ComparerName, w.props.ComparerName; was != is {
|
||||
return errors.Errorf("mismatched Comparer %s vs %s, replacement requires same splitter to copy filters", was, is)
|
||||
}
|
||||
}
|
||||
|
||||
g := &sync.WaitGroup{}
|
||||
g.Add(concurrency)
|
||||
errCh := make(chan error, concurrency)
|
||||
for i := 0; i < concurrency; i++ {
|
||||
worker := i
|
||||
go func() {
|
||||
defer g.Done()
|
||||
err := rewriteBlocks(
|
||||
r,
|
||||
w.dataBlockBuf.dataBlock.restartInterval,
|
||||
w.blockBuf.checksummer.checksumType,
|
||||
w.compression,
|
||||
data,
|
||||
blocks,
|
||||
concurrency,
|
||||
worker,
|
||||
from, to,
|
||||
split,
|
||||
)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
g.Wait()
|
||||
close(errCh)
|
||||
if err, ok := <-errCh; ok {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range w.propCollectors {
|
||||
if err := p.(SuffixReplaceableTableCollector).UpdateKeySuffixes(r.Properties.UserProperties, from, to); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var decoder blockPropertiesDecoder
|
||||
var oldShortIDs []shortID
|
||||
var oldProps [][]byte
|
||||
if len(w.blockPropCollectors) > 0 {
|
||||
oldProps = make([][]byte, len(w.blockPropCollectors))
|
||||
oldShortIDs = make([]shortID, math.MaxUint8)
|
||||
for i, p := range w.blockPropCollectors {
|
||||
if prop, ok := r.Properties.UserProperties[p.Name()]; ok {
|
||||
was, is := shortID(byte(prop[0])), shortID(i)
|
||||
oldShortIDs[was] = is
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range blocks {
|
||||
// Write the rewritten block to the file.
|
||||
n, err := w.writer.Write(blocks[i].data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bh := BlockHandle{Offset: w.meta.Size, Length: uint64(n) - blockTrailerLen}
|
||||
// Update the overall size.
|
||||
w.meta.Size += uint64(n)
|
||||
|
||||
// Load any previous values for our prop collectors into oldProps.
|
||||
for i := range oldProps {
|
||||
oldProps[i] = nil
|
||||
}
|
||||
decoder.props = data[i].Props
|
||||
for !decoder.done() {
|
||||
id, val, err := decoder.next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldProps[oldShortIDs[id]] = val
|
||||
}
|
||||
|
||||
for i, p := range w.blockPropCollectors {
|
||||
if err := p.(SuffixReplaceableBlockCollector).UpdateKeySuffixes(oldProps[i], from, to); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var bhp BlockHandleWithProperties
|
||||
if bhp, err = w.maybeAddBlockPropertiesToBlockHandle(bh); err != nil {
|
||||
return err
|
||||
}
|
||||
var nextKey InternalKey
|
||||
if i+1 < len(blocks) {
|
||||
nextKey = blocks[i+1].start
|
||||
}
|
||||
if err = w.addIndexEntrySync(blocks[i].end, nextKey, bhp, w.dataBlockBuf.tmp[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w.meta.updateSeqNum(blocks[0].start.SeqNum())
|
||||
w.props.NumEntries = r.Properties.NumEntries
|
||||
w.props.RawKeySize = r.Properties.RawKeySize
|
||||
w.props.RawValueSize = r.Properties.RawValueSize
|
||||
w.meta.SmallestPoint, w.meta.LargestPoint = blocks[0].start, blocks[len(blocks)-1].end
|
||||
w.meta.HasPointKeys = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type copyFilterWriter struct {
|
||||
origMetaName string
|
||||
origPolicyName string
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (copyFilterWriter) addKey(key []byte) { panic("unimplemented") }
|
||||
func (c copyFilterWriter) finish() ([]byte, error) { return c.data, nil }
|
||||
func (c copyFilterWriter) metaName() string { return c.origMetaName }
|
||||
func (c copyFilterWriter) policyName() string { return c.origPolicyName }
|
||||
|
||||
// RewriteKeySuffixesViaWriter is similar to RewriteKeySuffixes but uses just a
|
||||
// single loop over the Reader that writes each key to the Writer with the new
|
||||
// suffix. The is significantly slower than the parallelized rewriter, and does
|
||||
// more work to rederive filters, props, etc, however re-doing that work makes
|
||||
// it less restrictive -- props no longer need to
|
||||
func RewriteKeySuffixesViaWriter(
|
||||
r *Reader, out writeCloseSyncer, o WriterOptions, from, to []byte,
|
||||
) (*WriterMetadata, error) {
|
||||
if o.Comparer == nil || o.Comparer.Split == nil {
|
||||
return nil, errors.New("a valid splitter is required to define suffix to replace replace suffix")
|
||||
}
|
||||
|
||||
w := NewWriter(out, o)
|
||||
i, err := r.NewIter(nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer i.Close()
|
||||
|
||||
k, v := i.First()
|
||||
var scratch InternalKey
|
||||
for k != nil {
|
||||
if k.Kind() != InternalKeyKindSet {
|
||||
return nil, errors.New("invalid key type")
|
||||
}
|
||||
oldSuffix := k.UserKey[r.Split(k.UserKey):]
|
||||
if !bytes.Equal(oldSuffix, from) {
|
||||
return nil, errors.Errorf("key has suffix %q, expected %q", oldSuffix, from)
|
||||
}
|
||||
scratch.UserKey = append(scratch.UserKey[:0], k.UserKey[:len(k.UserKey)-len(from)]...)
|
||||
scratch.UserKey = append(scratch.UserKey, to...)
|
||||
scratch.Trailer = k.Trailer
|
||||
|
||||
if w.addPoint(scratch, v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k, v = i.Next()
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &w.meta, nil
|
||||
}
|
||||
|
||||
// NewMemReader opens a reader over the SST stored in the passed []byte.
|
||||
func NewMemReader(sst []byte, o ReaderOptions) (*Reader, error) {
|
||||
return NewReader(memReader{sst, bytes.NewReader(sst), sizeOnlyStat(int64(len(sst)))}, o)
|
||||
}
|
||||
|
||||
func readBlockBuf(r *Reader, bh BlockHandle, buf []byte) ([]byte, []byte, error) {
|
||||
raw := r.file.(memReader).b[bh.Offset : bh.Offset+bh.Length+blockTrailerLen]
|
||||
if err := checkChecksum(r.checksumType, raw, bh, 0); err != nil {
|
||||
return nil, buf, err
|
||||
}
|
||||
typ := blockType(raw[bh.Length])
|
||||
raw = raw[:bh.Length]
|
||||
if typ == noCompressionBlockType {
|
||||
return raw, buf, nil
|
||||
}
|
||||
decompressedLen, prefix, err := decompressedLen(typ, raw)
|
||||
if err != nil {
|
||||
return nil, buf, err
|
||||
}
|
||||
if cap(buf) < decompressedLen {
|
||||
buf = make([]byte, decompressedLen)
|
||||
}
|
||||
res, err := decompressInto(typ, raw[prefix:], buf[:decompressedLen])
|
||||
return res, buf, err
|
||||
}
|
||||
|
||||
// memReader is a thin wrapper around a []byte such that it can be passed to an
|
||||
// sstable.Reader. It supports concurrent use, and does so without locking in
|
||||
// contrast to the heavier read/write vfs.MemFile.
|
||||
type memReader struct {
|
||||
b []byte
|
||||
r *bytes.Reader
|
||||
s sizeOnlyStat
|
||||
}
|
||||
|
||||
var _ ReadableFile = memReader{}
|
||||
|
||||
// ReadAt implements io.ReaderAt.
|
||||
func (m memReader) ReadAt(p []byte, off int64) (n int, err error) { return m.r.ReadAt(p, off) }
|
||||
|
||||
// Close implements io.Closer.
|
||||
func (memReader) Close() error { return nil }
|
||||
|
||||
// Stat implements ReadableFile.
|
||||
func (m memReader) Stat() (os.FileInfo, error) { return m.s, nil }
|
||||
|
||||
type sizeOnlyStat int64
|
||||
|
||||
func (s sizeOnlyStat) Size() int64 { return int64(s) }
|
||||
func (sizeOnlyStat) IsDir() bool { panic(errors.AssertionFailedf("unimplemented")) }
|
||||
func (sizeOnlyStat) ModTime() time.Time { panic(errors.AssertionFailedf("unimplemented")) }
|
||||
func (sizeOnlyStat) Mode() os.FileMode { panic(errors.AssertionFailedf("unimplemented")) }
|
||||
func (sizeOnlyStat) Name() string { panic(errors.AssertionFailedf("unimplemented")) }
|
||||
func (sizeOnlyStat) Sys() interface{} { panic(errors.AssertionFailedf("unimplemented")) }
|
||||
428
vendor/github.com/cockroachdb/pebble/sstable/table.go
generated
vendored
Normal file
428
vendor/github.com/cockroachdb/pebble/sstable/table.go
generated
vendored
Normal file
@@ -0,0 +1,428 @@
|
||||
// Copyright 2011 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
// Package sstable implements readers and writers of pebble tables.
|
||||
//
|
||||
// Tables are either opened for reading or created for writing but not both.
|
||||
//
|
||||
// A reader can create iterators, which allow seeking and next/prev
|
||||
// iteration. There may be multiple key/value pairs that have the same key and
|
||||
// different sequence numbers.
|
||||
//
|
||||
// A reader can be used concurrently. Multiple goroutines can call NewIter
|
||||
// concurrently, and each iterator can run concurrently with other iterators.
|
||||
// However, any particular iterator should not be used concurrently, and iterators
|
||||
// should not be used once a reader is closed.
|
||||
//
|
||||
// A writer writes key/value pairs in increasing key order, and cannot be used
|
||||
// concurrently. A table cannot be read until the writer has finished.
|
||||
//
|
||||
// Readers and writers can be created with various options. Passing a nil
|
||||
// Options pointer is valid and means to use the default values.
|
||||
//
|
||||
// One such option is to define the 'less than' ordering for keys. The default
|
||||
// Comparer uses the natural ordering consistent with bytes.Compare. The same
|
||||
// ordering should be used for reading and writing a table.
|
||||
//
|
||||
// To return the value for a key:
|
||||
//
|
||||
// r := table.NewReader(file, options)
|
||||
// defer r.Close()
|
||||
// i := r.NewIter(nil, nil)
|
||||
// defer i.Close()
|
||||
// ikey, value := r.SeekGE(key)
|
||||
// if options.Comparer.Compare(ikey.UserKey, key) != 0 {
|
||||
// // not found
|
||||
// } else {
|
||||
// // value is the first record containing key
|
||||
// }
|
||||
//
|
||||
// To count the number of entries in a table:
|
||||
//
|
||||
// i, n := r.NewIter(nil, nil), 0
|
||||
// for key, value := i.First(); key != nil; key, value = i.Next() {
|
||||
// n++
|
||||
// }
|
||||
// if err := i.Close(); err != nil {
|
||||
// return 0, err
|
||||
// }
|
||||
// return n, nil
|
||||
//
|
||||
// To write a table with three entries:
|
||||
//
|
||||
// w := table.NewWriter(file, options)
|
||||
// if err := w.Set([]byte("apple"), []byte("red")); err != nil {
|
||||
// w.Close()
|
||||
// return err
|
||||
// }
|
||||
// if err := w.Set([]byte("banana"), []byte("yellow")); err != nil {
|
||||
// w.Close()
|
||||
// return err
|
||||
// }
|
||||
// if err := w.Set([]byte("cherry"), []byte("red")); err != nil {
|
||||
// w.Close()
|
||||
// return err
|
||||
// }
|
||||
// return w.Close()
|
||||
package sstable // import "github.com/cockroachdb/pebble/sstable"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
)
|
||||
|
||||
/*
|
||||
The table file format looks like:
|
||||
|
||||
<start_of_file>
|
||||
[data block 0]
|
||||
[data block 1]
|
||||
...
|
||||
[data block N-1]
|
||||
[meta filter block] (optional)
|
||||
[index block] (for single level index)
|
||||
[meta rangedel block] (optional)
|
||||
[meta range key block] (optional)
|
||||
[meta properties block]
|
||||
[metaindex block]
|
||||
[footer]
|
||||
<end_of_file>
|
||||
|
||||
A Reader eagerly loads the footer, metaindex block and meta properties block,
|
||||
because the data contained in those blocks is needed on every read, and even
|
||||
before reading. For example, the meta properties block is used to verify the
|
||||
comparer and merger are compatible, and the metaindex block contains the
|
||||
location of the meta properties (and other meta blocks). In situations where
|
||||
file system locality matters, or one wants to minimize number of read
|
||||
requests when eagerly loading these blocks, having these three as a suffix
|
||||
of the file is convenient.
|
||||
|
||||
The interleaving of the index block(s) between the meta blocks is done to
|
||||
match RocksDB/LevelDB behavior.
|
||||
|
||||
Each block consists of some data and a 5 byte trailer: a 1 byte block type and a
|
||||
4 byte checksum. The checksum is computed over the compressed data and the first
|
||||
byte of the trailer (i.e. the block type), and is serialized as little-endian.
|
||||
The block type gives the per-block compression used; each block is compressed
|
||||
independently. The checksum algorithm is described in the pebble/crc package.
|
||||
|
||||
Most blocks, other than the meta filter block, contain key/value pairs. The
|
||||
remainder of this comment refers to the decompressed block, which has its 5 byte
|
||||
trailer stripped. The decompressed block data consists of a sequence of such
|
||||
key/value entries followed by a block suffix. Each key is encoded as a shared
|
||||
prefix length and a remainder string. For example, if two adjacent keys are
|
||||
"tweedledee" and "tweedledum", then the second key would be encoded as {8,
|
||||
"um"}. The shared prefix length is varint encoded. The remainder string and the
|
||||
value are encoded as a varint-encoded length followed by the literal contents.
|
||||
To continue the example, suppose that the key "tweedledum" mapped to the value
|
||||
"socks". The encoded key/value entry would be: "\x08\x02\x05umsocks".
|
||||
|
||||
Every block has a restart interval I. Every I'th key/value entry in that block
|
||||
is called a restart point, and shares no key prefix with the previous entry.
|
||||
Continuing the example above, if the key after "tweedledum" was "two", but was
|
||||
part of a restart point, then that key would be encoded as {0, "two"} instead
|
||||
of {2, "o"}. If a block has P restart points, then the block suffix consists
|
||||
of (P+1)*4 bytes: (P+1) little-endian uint32 values. The first P of these
|
||||
uint32 values are the block offsets of each restart point. The final uint32
|
||||
value is P itself. Thus, when seeking for a particular key, one can use binary
|
||||
search to find the largest restart point whose key is <= the key sought.
|
||||
|
||||
An index block is a block with N key/value entries. The i'th value is the
|
||||
encoded block handle of the i'th data block. The i'th key is a separator for
|
||||
i < N-1, and a successor for i == N-1. The separator between blocks i and i+1
|
||||
is a key that is >= every key in block i and is < every key i block i+1. The
|
||||
successor for the final block is a key that is >= every key in block N-1. The
|
||||
index block restart interval is 1: every entry is a restart point.
|
||||
|
||||
A block handle is an offset, a length, and optional block properties (for data
|
||||
blocks and first/lower level index blocks); the length does not include the 5
|
||||
byte trailer. All numbers are varint-encoded, with no padding between the two
|
||||
values. The maximum size of an encoded block handle without properties is 20
|
||||
bytes. It is not advised to have properties that accumulate to be longer than
|
||||
100 bytes.
|
||||
|
||||
Instead of a single index block, the sstable can have a two-level index (this
|
||||
is used to prevent a single huge index block). A two-level index consists of a
|
||||
sequence of lower-level index blocks with block handles for data blocks
|
||||
followed by a single top-level index block with block handles for the
|
||||
lower-level index blocks.
|
||||
|
||||
The metaindex block also contains block handles as values, with keys being
|
||||
the names of the meta blocks.
|
||||
|
||||
*/
|
||||
|
||||
const (
|
||||
blockTrailerLen = 5
|
||||
blockHandleMaxLenWithoutProperties = 10 + 10
|
||||
// blockHandleLikelyMaxLen can be used for pre-allocating buffers to
|
||||
// reduce memory copies. It is not guaranteed that a block handle will not
|
||||
// exceed this length.
|
||||
blockHandleLikelyMaxLen = blockHandleMaxLenWithoutProperties + 100
|
||||
|
||||
levelDBFooterLen = 48
|
||||
levelDBMagic = "\x57\xfb\x80\x8b\x24\x75\x47\xdb"
|
||||
levelDBMagicOffset = levelDBFooterLen - len(levelDBMagic)
|
||||
|
||||
rocksDBFooterLen = 1 + 2*blockHandleMaxLenWithoutProperties + 4 + 8
|
||||
rocksDBMagic = "\xf7\xcf\xf4\x85\xb7\x41\xe2\x88"
|
||||
rocksDBMagicOffset = rocksDBFooterLen - len(rocksDBMagic)
|
||||
rocksDBVersionOffset = rocksDBMagicOffset - 4
|
||||
rocksDBExternalFormatVersion = 2
|
||||
|
||||
pebbleDBMagic = "\xf0\x9f\xaa\xb3\xf0\x9f\xaa\xb3" // 🪳🪳
|
||||
|
||||
minFooterLen = levelDBFooterLen
|
||||
maxFooterLen = rocksDBFooterLen
|
||||
|
||||
levelDBFormatVersion = 0
|
||||
rocksDBFormatVersion2 = 2
|
||||
|
||||
metaRangeKeyName = "pebble.range_key"
|
||||
metaPropertiesName = "rocksdb.properties"
|
||||
metaRangeDelName = "rocksdb.range_del"
|
||||
metaRangeDelV2Name = "rocksdb.range_del2"
|
||||
|
||||
// Index Types.
|
||||
// A space efficient index block that is optimized for binary-search-based
|
||||
// index.
|
||||
binarySearchIndex = 0
|
||||
// hashSearchIndex = 1
|
||||
// A two-level index implementation. Both levels are binary search indexes.
|
||||
twoLevelIndex = 2
|
||||
// binarySearchWithFirstKeyIndex = 3
|
||||
|
||||
// RocksDB always includes this in the properties block. Since Pebble
|
||||
// doesn't use zstd compression, the string will always be the same.
|
||||
// This should be removed if we ever decide to diverge from the RocksDB
|
||||
// properties block.
|
||||
rocksDBCompressionOptions = "window_bits=-14; level=32767; strategy=0; max_dict_bytes=0; zstd_max_train_bytes=0; enabled=0; "
|
||||
)
|
||||
|
||||
// ChecksumType specifies the checksum used for blocks.
|
||||
type ChecksumType byte
|
||||
|
||||
// The available checksum types.
|
||||
const (
|
||||
ChecksumTypeNone ChecksumType = 0
|
||||
ChecksumTypeCRC32c ChecksumType = 1
|
||||
ChecksumTypeXXHash ChecksumType = 2
|
||||
ChecksumTypeXXHash64 ChecksumType = 3
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (t ChecksumType) String() string {
|
||||
switch t {
|
||||
case ChecksumTypeCRC32c:
|
||||
return "crc32c"
|
||||
case ChecksumTypeNone:
|
||||
return "none"
|
||||
case ChecksumTypeXXHash:
|
||||
return "xxhash"
|
||||
case ChecksumTypeXXHash64:
|
||||
return "xxhash64"
|
||||
default:
|
||||
panic(errors.Newf("sstable: unknown checksum type: %d", t))
|
||||
}
|
||||
}
|
||||
|
||||
type blockType byte
|
||||
|
||||
const (
|
||||
// The block type gives the per-block compression format.
|
||||
// These constants are part of the file format and should not be changed.
|
||||
// They are different from the Compression constants because the latter
|
||||
// are designed so that the zero value of the Compression type means to
|
||||
// use the default compression (which is snappy).
|
||||
// Not all compression types listed here are supported.
|
||||
noCompressionBlockType blockType = 0
|
||||
snappyCompressionBlockType blockType = 1
|
||||
zlibCompressionBlockType blockType = 2
|
||||
bzip2CompressionBlockType blockType = 3
|
||||
lz4CompressionBlockType blockType = 4
|
||||
lz4hcCompressionBlockType blockType = 5
|
||||
xpressCompressionBlockType blockType = 6
|
||||
zstdCompressionBlockType blockType = 7
|
||||
)
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (t blockType) String() string {
|
||||
switch t {
|
||||
case 0:
|
||||
return "none"
|
||||
case 1:
|
||||
return "snappy"
|
||||
case 2:
|
||||
return "zlib"
|
||||
case 3:
|
||||
return "bzip2"
|
||||
case 4:
|
||||
return "lz4"
|
||||
case 5:
|
||||
return "lz4hc"
|
||||
case 6:
|
||||
return "xpress"
|
||||
case 7:
|
||||
return "zstd"
|
||||
default:
|
||||
panic(errors.Newf("sstable: unknown block type: %d", t))
|
||||
}
|
||||
}
|
||||
|
||||
// legacy (LevelDB) footer format:
|
||||
// metaindex handle (varint64 offset, varint64 size)
|
||||
// index handle (varint64 offset, varint64 size)
|
||||
// <padding> to make the total size 2 * BlockHandle::kMaxEncodedLength
|
||||
// table_magic_number (8 bytes)
|
||||
// new (RocksDB) footer format:
|
||||
// checksum type (char, 1 byte)
|
||||
// metaindex handle (varint64 offset, varint64 size)
|
||||
// index handle (varint64 offset, varint64 size)
|
||||
// <padding> to make the total size 2 * BlockHandle::kMaxEncodedLength + 1
|
||||
// footer version (4 bytes)
|
||||
// table_magic_number (8 bytes)
|
||||
type footer struct {
|
||||
format TableFormat
|
||||
checksum ChecksumType
|
||||
metaindexBH BlockHandle
|
||||
indexBH BlockHandle
|
||||
footerBH BlockHandle
|
||||
}
|
||||
|
||||
func readFooter(f ReadableFile) (footer, error) {
|
||||
var footer footer
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return footer, errors.Wrap(err, "pebble/table: invalid table (could not stat file)")
|
||||
}
|
||||
if stat.Size() < minFooterLen {
|
||||
return footer, base.CorruptionErrorf("pebble/table: invalid table (file size is too small)")
|
||||
}
|
||||
|
||||
buf := make([]byte, maxFooterLen)
|
||||
off := stat.Size() - maxFooterLen
|
||||
if off < 0 {
|
||||
off = 0
|
||||
}
|
||||
n, err := f.ReadAt(buf, off)
|
||||
if err != nil && err != io.EOF {
|
||||
return footer, errors.Wrap(err, "pebble/table: invalid table (could not read footer)")
|
||||
}
|
||||
buf = buf[:n]
|
||||
|
||||
switch magic := buf[len(buf)-len(rocksDBMagic):]; string(magic) {
|
||||
case levelDBMagic:
|
||||
if len(buf) < levelDBFooterLen {
|
||||
return footer, base.CorruptionErrorf(
|
||||
"pebble/table: invalid table (footer too short): %d", errors.Safe(len(buf)))
|
||||
}
|
||||
footer.footerBH.Offset = uint64(off+int64(len(buf))) - levelDBFooterLen
|
||||
buf = buf[len(buf)-levelDBFooterLen:]
|
||||
footer.footerBH.Length = uint64(len(buf))
|
||||
footer.format = TableFormatLevelDB
|
||||
footer.checksum = ChecksumTypeCRC32c
|
||||
|
||||
case rocksDBMagic, pebbleDBMagic:
|
||||
// NOTE: The Pebble magic string implies the same footer format as that used
|
||||
// by the RocksDBv2 table format.
|
||||
if len(buf) < rocksDBFooterLen {
|
||||
return footer, base.CorruptionErrorf("pebble/table: invalid table (footer too short): %d", errors.Safe(len(buf)))
|
||||
}
|
||||
footer.footerBH.Offset = uint64(off+int64(len(buf))) - rocksDBFooterLen
|
||||
buf = buf[len(buf)-rocksDBFooterLen:]
|
||||
footer.footerBH.Length = uint64(len(buf))
|
||||
version := binary.LittleEndian.Uint32(buf[rocksDBVersionOffset:rocksDBMagicOffset])
|
||||
|
||||
format, err := ParseTableFormat(magic, version)
|
||||
if err != nil {
|
||||
return footer, err
|
||||
}
|
||||
footer.format = format
|
||||
|
||||
switch ChecksumType(buf[0]) {
|
||||
case ChecksumTypeCRC32c:
|
||||
footer.checksum = ChecksumTypeCRC32c
|
||||
case ChecksumTypeXXHash64:
|
||||
footer.checksum = ChecksumTypeXXHash64
|
||||
default:
|
||||
return footer, base.CorruptionErrorf("pebble/table: unsupported checksum type %d", errors.Safe(footer.checksum))
|
||||
}
|
||||
buf = buf[1:]
|
||||
|
||||
default:
|
||||
return footer, base.CorruptionErrorf("pebble/table: invalid table (bad magic number)")
|
||||
}
|
||||
|
||||
{
|
||||
end := uint64(stat.Size())
|
||||
var n int
|
||||
footer.metaindexBH, n = decodeBlockHandle(buf)
|
||||
if n == 0 || footer.metaindexBH.Offset+footer.metaindexBH.Length > end {
|
||||
return footer, base.CorruptionErrorf("pebble/table: invalid table (bad metaindex block handle)")
|
||||
}
|
||||
buf = buf[n:]
|
||||
|
||||
footer.indexBH, n = decodeBlockHandle(buf)
|
||||
if n == 0 || footer.indexBH.Offset+footer.indexBH.Length > end {
|
||||
return footer, base.CorruptionErrorf("pebble/table: invalid table (bad index block handle)")
|
||||
}
|
||||
}
|
||||
|
||||
return footer, nil
|
||||
}
|
||||
|
||||
func (f footer) encode(buf []byte) []byte {
|
||||
switch magic, version := f.format.AsTuple(); magic {
|
||||
case levelDBMagic:
|
||||
buf = buf[:levelDBFooterLen]
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
n := encodeBlockHandle(buf[0:], f.metaindexBH)
|
||||
encodeBlockHandle(buf[n:], f.indexBH)
|
||||
copy(buf[len(buf)-len(levelDBMagic):], levelDBMagic)
|
||||
|
||||
case rocksDBMagic, pebbleDBMagic:
|
||||
buf = buf[:rocksDBFooterLen]
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
switch f.checksum {
|
||||
case ChecksumTypeNone:
|
||||
buf[0] = byte(ChecksumTypeNone)
|
||||
case ChecksumTypeCRC32c:
|
||||
buf[0] = byte(ChecksumTypeCRC32c)
|
||||
case ChecksumTypeXXHash:
|
||||
buf[0] = byte(ChecksumTypeXXHash)
|
||||
case ChecksumTypeXXHash64:
|
||||
buf[0] = byte(ChecksumTypeXXHash64)
|
||||
default:
|
||||
panic("unknown checksum type")
|
||||
}
|
||||
n := 1
|
||||
n += encodeBlockHandle(buf[n:], f.metaindexBH)
|
||||
encodeBlockHandle(buf[n:], f.indexBH)
|
||||
binary.LittleEndian.PutUint32(buf[rocksDBVersionOffset:], version)
|
||||
copy(buf[len(buf)-len(rocksDBMagic):], magic)
|
||||
|
||||
default:
|
||||
panic("sstable: unspecified table format version")
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func supportsTwoLevelIndex(format TableFormat) bool {
|
||||
switch format {
|
||||
case TableFormatLevelDB:
|
||||
return false
|
||||
case TableFormatRocksDBv2, TableFormatPebblev1, TableFormatPebblev2:
|
||||
return true
|
||||
default:
|
||||
panic("sstable: unspecified table format version")
|
||||
}
|
||||
}
|
||||
35
vendor/github.com/cockroachdb/pebble/sstable/unsafe.go
generated
vendored
Normal file
35
vendor/github.com/cockroachdb/pebble/sstable/unsafe.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/manual"
|
||||
)
|
||||
|
||||
func getBytes(ptr unsafe.Pointer, length int) []byte {
|
||||
return (*[manual.MaxArrayLen]byte)(ptr)[:length:length]
|
||||
}
|
||||
|
||||
func decodeVarint(ptr unsafe.Pointer) (uint32, unsafe.Pointer) {
|
||||
if a := *((*uint8)(ptr)); a < 128 {
|
||||
return uint32(a),
|
||||
unsafe.Pointer(uintptr(ptr) + 1)
|
||||
} else if a, b := a&0x7f, *((*uint8)(unsafe.Pointer(uintptr(ptr) + 1))); b < 128 {
|
||||
return uint32(b)<<7 | uint32(a),
|
||||
unsafe.Pointer(uintptr(ptr) + 2)
|
||||
} else if b, c := b&0x7f, *((*uint8)(unsafe.Pointer(uintptr(ptr) + 2))); c < 128 {
|
||||
return uint32(c)<<14 | uint32(b)<<7 | uint32(a),
|
||||
unsafe.Pointer(uintptr(ptr) + 3)
|
||||
} else if c, d := c&0x7f, *((*uint8)(unsafe.Pointer(uintptr(ptr) + 3))); d < 128 {
|
||||
return uint32(d)<<21 | uint32(c)<<14 | uint32(b)<<7 | uint32(a),
|
||||
unsafe.Pointer(uintptr(ptr) + 4)
|
||||
} else {
|
||||
d, e := d&0x7f, *((*uint8)(unsafe.Pointer(uintptr(ptr) + 4)))
|
||||
return uint32(e)<<28 | uint32(d)<<21 | uint32(c)<<14 | uint32(b)<<7 | uint32(a),
|
||||
unsafe.Pointer(uintptr(ptr) + 5)
|
||||
}
|
||||
}
|
||||
148
vendor/github.com/cockroachdb/pebble/sstable/write_queue.go
generated
vendored
Normal file
148
vendor/github.com/cockroachdb/pebble/sstable/write_queue.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
package sstable
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
)
|
||||
|
||||
type writeTask struct {
|
||||
// Since writeTasks are pooled, the compressionDone channel will be re-used.
|
||||
// It is necessary that any writes to the channel have already been read,
|
||||
// before adding the writeTask back to the pool.
|
||||
compressionDone chan bool
|
||||
buf *dataBlockBuf
|
||||
// If this is not nil, then this index block will be flushed.
|
||||
flushableIndexBlock *indexBlockBuf
|
||||
// currIndexBlock is the index block on which indexBlock.add must be called.
|
||||
currIndexBlock *indexBlockBuf
|
||||
indexEntrySep InternalKey
|
||||
// inflightSize is used to decrement Writer.coordination.sizeEstimate.inflightSize.
|
||||
inflightSize int
|
||||
// inflightIndexEntrySize is used to decrement Writer.indexBlock.sizeEstimate.inflightSize.
|
||||
indexInflightSize int
|
||||
// If the index block is finished, then we set the finishedIndexProps here.
|
||||
finishedIndexProps []byte
|
||||
}
|
||||
|
||||
// It is not the responsibility of the writeTask to clear the
|
||||
// task.flushableIndexBlock, and task.buf.
|
||||
func (task *writeTask) clear() {
|
||||
*task = writeTask{
|
||||
indexEntrySep: base.InvalidInternalKey,
|
||||
compressionDone: task.compressionDone,
|
||||
}
|
||||
}
|
||||
|
||||
// Note that only the Writer client goroutine will be adding tasks to the writeQueue.
|
||||
// Both the Writer client and the compression goroutines will be able to write to
|
||||
// writeTask.compressionDone to indicate that the compression job associated with
|
||||
// a writeTask has finished.
|
||||
type writeQueue struct {
|
||||
tasks chan *writeTask
|
||||
wg sync.WaitGroup
|
||||
writer *Writer
|
||||
|
||||
// err represents an error which is encountered when the write queue attempts
|
||||
// to write a block to disk. The error is stored here to skip unnecessary block
|
||||
// writes once the first error is encountered.
|
||||
err error
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newWriteQueue(size int, writer *Writer) *writeQueue {
|
||||
w := &writeQueue{}
|
||||
w.tasks = make(chan *writeTask, size)
|
||||
w.writer = writer
|
||||
|
||||
w.wg.Add(1)
|
||||
go w.runWorker()
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *writeQueue) performWrite(task *writeTask) error {
|
||||
var bh BlockHandle
|
||||
var bhp BlockHandleWithProperties
|
||||
|
||||
var err error
|
||||
if bh, err = w.writer.writeCompressedBlock(task.buf.compressed, task.buf.tmp[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the size estimates after writing the data block to disk.
|
||||
w.writer.coordination.sizeEstimate.dataBlockWritten(
|
||||
w.writer.meta.Size, task.inflightSize, int(bh.Length),
|
||||
)
|
||||
|
||||
bhp = BlockHandleWithProperties{BlockHandle: bh, Props: task.buf.dataBlockProps}
|
||||
if err = w.writer.addIndexEntry(
|
||||
task.indexEntrySep, bhp, task.buf.tmp[:], task.flushableIndexBlock, task.currIndexBlock,
|
||||
task.indexInflightSize, task.finishedIndexProps); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// It is necessary to ensure that none of the buffers in the writeTask,
|
||||
// dataBlockBuf, indexBlockBuf, are pointed to by another struct.
|
||||
func (w *writeQueue) releaseBuffers(task *writeTask) {
|
||||
task.buf.clear()
|
||||
dataBlockBufPool.Put(task.buf)
|
||||
|
||||
// This index block is no longer used by the Writer, so we can add it back
|
||||
// to the pool.
|
||||
if task.flushableIndexBlock != nil {
|
||||
task.flushableIndexBlock.clear()
|
||||
indexBlockBufPool.Put(task.flushableIndexBlock)
|
||||
}
|
||||
|
||||
task.clear()
|
||||
writeTaskPool.Put(task)
|
||||
}
|
||||
|
||||
func (w *writeQueue) runWorker() {
|
||||
for task := range w.tasks {
|
||||
<-task.compressionDone
|
||||
|
||||
if w.err == nil {
|
||||
w.err = w.performWrite(task)
|
||||
}
|
||||
|
||||
w.releaseBuffers(task)
|
||||
}
|
||||
w.wg.Done()
|
||||
}
|
||||
|
||||
//lint:ignore U1000 - Will be used in a future pr.
|
||||
func (w *writeQueue) add(task *writeTask) {
|
||||
w.tasks <- task
|
||||
}
|
||||
|
||||
// addSync will perform the writeTask synchronously with the caller goroutine. Calls to addSync
|
||||
// are no longer valid once writeQueue.add has been called at least once.
|
||||
func (w *writeQueue) addSync(task *writeTask) error {
|
||||
// This should instantly return without blocking.
|
||||
<-task.compressionDone
|
||||
|
||||
if w.err == nil {
|
||||
w.err = w.performWrite(task)
|
||||
}
|
||||
|
||||
w.releaseBuffers(task)
|
||||
|
||||
return w.err
|
||||
}
|
||||
|
||||
// finish should only be called once no more tasks will be added to the writeQueue.
|
||||
// finish will return any error which was encountered while tasks were processed.
|
||||
func (w *writeQueue) finish() error {
|
||||
if w.closed {
|
||||
return w.err
|
||||
}
|
||||
|
||||
close(w.tasks)
|
||||
w.wg.Wait()
|
||||
w.closed = true
|
||||
return w.err
|
||||
}
|
||||
2032
vendor/github.com/cockroachdb/pebble/sstable/writer.go
generated
vendored
Normal file
2032
vendor/github.com/cockroachdb/pebble/sstable/writer.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user