0

Initial commit

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

1230
vendor/github.com/cockroachdb/pebble/sstable/block.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,602 @@
// 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.
//
// Block property filtering is nondeterministic because the separation of keys
// into blocks is nondeterministic. Clients use block-property filters to
// implement efficient application of a filter F that applies to key-value pairs
// (abbreviated as kv-filter). Consider correctness defined as surfacing exactly
// the same key-value pairs that would be surfaced if one applied the filter F
// above normal iteration. With this correctness definition, block property
// filtering may introduce two kinds of errors:
//
// a) Block property filtering that uses a kv-filter may produce additional
// key-value pairs that don't satisfy the filter because of the separation
// of keys into blocks. Clients may remove these extra key-value pairs by
// re-applying the kv filter while reading results back from Pebble.
//
// b) Block property filtering may surface deleted key-value pairs if the
// the kv filter is not a strict function of the key's user key. A block
// containing k.DEL may be filtered, while a block containing the deleted
// key k.SET may not be filtered, if the kv filter applies to one but not
// the other.
//
// This error may be avoided trivially by using a kv filter that is a pure
// function of the the user key. A filter that examines values or key kinds
// requires care to ensure F(k.SET, <value>) = F(k.DEL) = F(k.SINGLEDEL).
//
// The combination of range deletions and filtering by table-level properties
// add another opportunity for deleted point keys to be surfaced. The pebble
// Iterator stack takes care to correctly apply filtered tables' range deletions
// to lower tables, preventing this form of nondeterministic error.
// 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
}

View 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

View 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
}
}

View 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()
}

View 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
View 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
View 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")
}
}

View File

@@ -0,0 +1,33 @@
// 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
// SeekGEFlags exports base.SeekGEFlags.
type SeekGEFlags = base.SeekGEFlags
// SeekLTFlags exports base.SeekLTFlags.
type SeekLTFlags = base.SeekLTFlags
// 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
View 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
}

View 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])
}
}

View 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
}

3207
vendor/github.com/cockroachdb/pebble/sstable/reader.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,480 @@
package sstable
import (
"bytes"
"math"
"os"
"sync"
"time"
"github.com/cespare/xxhash/v2"
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/rangekey"
)
// 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 or RangeKeySets
// and every key must have `from` as its suffix as determined by the Split
// function of the Comparer in the passed WriterOptions. Range deletes must not
// exist in this sstable, as they will be ignored.
//
// 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 range key block and replace suffixes in it if it exists.
if err := rewriteRangeKeyBlockToWriter(r, w, from, to); err != nil {
return nil, errors.Wrap(err, "rewriting range key blocks")
}
// Copy over the filter block if it exists (rewriteDataBlocksToWriter will
// already have ensured this is valid if it exists).
if w.filter != nil && l.Filter.Length > 0 {
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 {
if r.Properties.NumEntries == 0 {
// No point keys.
return nil
}
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.SetSmallestPointKey(blocks[0].start)
w.meta.SetLargestPointKey(blocks[len(blocks)-1].end)
return nil
}
func rewriteRangeKeyBlockToWriter(r *Reader, w *Writer, from, to []byte) error {
iter, err := r.NewRawRangeKeyIter()
if err != nil {
return err
}
if iter == nil {
// No range keys.
return nil
}
defer iter.Close()
for s := iter.First(); s != nil; s = iter.Next() {
if !s.Valid() {
break
}
for i := range s.Keys {
if s.Keys[i].Kind() != base.InternalKeyKindRangeKeySet {
return errBadKind
}
if !bytes.Equal(s.Keys[i].Suffix, from) {
return errors.Errorf("key has suffix %q, expected %q", s.Keys[i].Suffix, from)
}
s.Keys[i].Suffix = to
}
err := rangekey.Encode(s, func(k base.InternalKey, v []byte) error {
// Calling AddRangeKey instead of addRangeKeySpan bypasses the fragmenter.
// This is okay because the raw fragments off of `iter` are already
// fragmented, and suffix replacement should not affect fragmentation.
return w.AddRangeKey(k, v)
})
if err != nil {
return err
}
}
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 := rewriteRangeKeyBlockToWriter(r, w, from, to); err != nil {
return nil, err
}
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
View 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
View 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)
}
}

View File

@@ -0,0 +1,147 @@
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()
}
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
}

2034
vendor/github.com/cockroachdb/pebble/sstable/writer.go generated vendored Normal file

File diff suppressed because it is too large Load Diff