Update deps
This commit is contained in:
8
vendor/github.com/cockroachdb/pebble/Makefile
generated
vendored
8
vendor/github.com/cockroachdb/pebble/Makefile
generated
vendored
@@ -4,7 +4,7 @@ GOFLAGS :=
|
||||
STRESSFLAGS :=
|
||||
TAGS := invariants
|
||||
TESTS := .
|
||||
LATEST_RELEASE := $(shell git fetch origin && git branch -r --list '*/crl-release-*' | grep -o 'crl-release-.*$$' | sort | tail -1)
|
||||
PREV_RELEASE := crl-release-23.1
|
||||
COVER_PROFILE := coverprofile.out
|
||||
|
||||
.PHONY: all
|
||||
@@ -63,11 +63,11 @@ stressmeta: stress
|
||||
|
||||
.PHONY: crossversion-meta
|
||||
crossversion-meta:
|
||||
git checkout ${LATEST_RELEASE}; \
|
||||
${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/${LATEST_RELEASE}.test'; \
|
||||
git checkout ${PREV_RELEASE}; \
|
||||
${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/${PREV_RELEASE}.test'; \
|
||||
git checkout -; \
|
||||
${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/head.test'; \
|
||||
${GO} test -tags '$(TAGS)' ${testflags} -v -run 'TestMetaCrossVersion' ./internal/metamorphic/crossversion --version '${LATEST_RELEASE},${LATEST_RELEASE},${LATEST_RELEASE}.test' --version 'HEAD,HEAD,./head.test'
|
||||
${GO} test -tags '$(TAGS)' ${testflags} -v -run 'TestMetaCrossVersion' ./internal/metamorphic/crossversion --version '${PREV_RELEASE},${PREV_RELEASE},${PREV_RELEASE}.test' --version 'HEAD,HEAD,./head.test'
|
||||
|
||||
.PHONY: stress-crossversion
|
||||
stress-crossversion:
|
||||
|
||||
84
vendor/github.com/cockroachdb/pebble/README.md
generated
vendored
84
vendor/github.com/cockroachdb/pebble/README.md
generated
vendored
@@ -1,4 +1,4 @@
|
||||
# Pebble [](https://github.com/cockroachdb/pebble/actions/workflows/ci.yaml) [](https://godoc.org/github.com/cockroachdb/pebble)
|
||||
# Pebble [](https://github.com/cockroachdb/pebble/actions/workflows/ci.yaml) [](https://godoc.org/github.com/cockroachdb/pebble) <sup><sub><sub>[Coverage](https://storage.googleapis.com/crl-codecover-public/pebble/index.html)</sub></sub></sup>
|
||||
|
||||
#### [Nightly benchmarks](https://cockroachdb.github.io/pebble/)
|
||||
|
||||
@@ -8,9 +8,8 @@ RocksDB file formats and a few extensions such as range deletion
|
||||
tombstones, table-level bloom filters, and updates to the MANIFEST
|
||||
format.
|
||||
|
||||
Pebble intentionally does not aspire to include every feature in
|
||||
RocksDB and is specifically targetting the use case and feature set
|
||||
needed by CockroachDB:
|
||||
Pebble intentionally does not aspire to include every feature in RocksDB and
|
||||
specifically targets the use case and feature set needed by CockroachDB:
|
||||
|
||||
* Block-based tables
|
||||
* Checkpoints
|
||||
@@ -68,6 +67,17 @@ Pebble offers several improvements over RocksDB:
|
||||
* Faster commit pipeline that achieves better concurrency.
|
||||
* Seamless merged iteration of indexed batches. The mutations in the
|
||||
batch conceptually occupy another memtable level.
|
||||
* L0 sublevels and flush splitting for concurrent compactions out of L0 and
|
||||
reduced read-amplification during heavy write load.
|
||||
* Faster LSM edits in LSMs with large numbers of sstables through use of a
|
||||
copy-on-write B-tree to hold file metadata.
|
||||
* Delete-only compactions that drop whole sstables that fall within the bounds
|
||||
of a range deletion.
|
||||
* Block-property collectors and filters that enable iterators to skip tables,
|
||||
index blocks and data blocks that are irrelevant, according to user-defined
|
||||
properties over key-value pairs.
|
||||
* Range keys API, allowing KV pairs defined over a range of keyspace with
|
||||
user-defined semantics and interleaved during iteration.
|
||||
* Smaller, more approachable code base.
|
||||
|
||||
See the [Pebble vs RocksDB: Implementation
|
||||
@@ -100,10 +110,68 @@ known incompatibilities.
|
||||
format.
|
||||
* Plain table format. Pebble does not support the plain table sstable
|
||||
format.
|
||||
* SSTable format version 3 and 4. Pebble does not currently support
|
||||
version 3 and version 4 format sstables. The sstable format version
|
||||
is controlled by the `BlockBasedTableOptions::format_version`
|
||||
option. See [#97](https://github.com/cockroachdb/pebble/issues/97).
|
||||
* SSTable format version 3 and 4. Pebble does not support version 3
|
||||
and version 4 format sstables. The sstable format version is
|
||||
controlled by the `BlockBasedTableOptions::format_version` option.
|
||||
See [#97](https://github.com/cockroachdb/pebble/issues/97).
|
||||
|
||||
## Format major versions
|
||||
|
||||
Over time Pebble has introduced new physical file formats. Backwards
|
||||
incompatible changes are made through the introduction of 'format major
|
||||
versions'. By default, when Pebble opens a database, it defaults to
|
||||
`FormatMostCompatible`. This version is bi-directionally compatible with RocksDB
|
||||
6.2.1 (with the caveats described above).
|
||||
|
||||
To opt into new formats, a user may set `FormatMajorVersion` on the
|
||||
[`Options`](https://pkg.go.dev/github.com/cockroachdb/pebble#Options)
|
||||
supplied to
|
||||
[`Open`](https://pkg.go.dev/github.com/cockroachdb/pebble#Open), or
|
||||
upgrade the format major version at runtime using
|
||||
[`DB.RatchetFormatMajorVersion`](https://pkg.go.dev/github.com/cockroachdb/pebble#DB.RatchetFormatMajorVersion).
|
||||
Format major version upgrades are permanent; There is no option to
|
||||
return to an earlier format.
|
||||
|
||||
The table below outlines the history of format major versions:
|
||||
|
||||
| Name | Value | Migration |
|
||||
|------------------------------------|-------|------------|
|
||||
| FormatMostCompatible | 1 | No |
|
||||
| FormatVersioned | 3 | No |
|
||||
| FormatSetWithDelete | 4 | No |
|
||||
| FormatBlockPropertyCollector | 5 | No |
|
||||
| FormatSplitUserKeysMarked | 6 | Background |
|
||||
| FormatSplitUserKeysMarkedCompacted | 7 | Blocking |
|
||||
| FormatRangeKeys | 8 | No |
|
||||
| FormatMinTableFormatPebblev1 | 9 | No |
|
||||
| FormatPrePebblev1Marked | 10 | Background |
|
||||
| FormatSSTableValueBlocks | 12 | No |
|
||||
| FormatFlushableIngest | 13 | No |
|
||||
| FormatPrePebblev1MarkedCompacted | 14 | Blocking |
|
||||
| FormatDeleteSizedAndObsolete | 15 | No |
|
||||
| FormatVirtualSSTables | 16 | No |
|
||||
|
||||
Upgrading to a format major version with 'Background' in the migration
|
||||
column may trigger background activity to rewrite physical file
|
||||
formats, typically through compactions. Upgrading to a format major
|
||||
version with 'Blocking' in the migration column will block until a
|
||||
migration is complete. The database may continue to serve reads and
|
||||
writes if upgrading a live database through
|
||||
`RatchetFormatMajorVersion`, but the method call will not return until
|
||||
the migration is complete.
|
||||
|
||||
For reference, the table below lists the range of supported Pebble format major
|
||||
versions for CockroachDB releases.
|
||||
|
||||
| CockroachDB release | Earliest supported | Latest supported |
|
||||
|---------------------|------------------------------------|---------------------------|
|
||||
| 20.1 through 21.1 | FormatMostCompatible | FormatMostCompatible |
|
||||
| 21.2 | FormatMostCompatible | FormatSetWithDelete |
|
||||
| 21.2 | FormatMostCompatible | FormatSetWithDelete |
|
||||
| 22.1 | FormatMostCompatible | FormatSplitUserKeysMarked |
|
||||
| 22.2 | FormatMostCompatible | FormatPrePebblev1Marked |
|
||||
| 23.1 | FormatSplitUserKeysMarkedCompacted | FormatFlushableIngest |
|
||||
| 23.2 | FormatSplitUserKeysMarkedCompacted | FormatVirtualSSTables |
|
||||
|
||||
## Pedigree
|
||||
|
||||
|
||||
146
vendor/github.com/cockroachdb/pebble/batch.go
generated
vendored
146
vendor/github.com/cockroachdb/pebble/batch.go
generated
vendored
@@ -41,10 +41,10 @@ const (
|
||||
var ErrNotIndexed = errors.New("pebble: batch not indexed")
|
||||
|
||||
// ErrInvalidBatch indicates that a batch is invalid or otherwise corrupted.
|
||||
var ErrInvalidBatch = errors.New("pebble: invalid batch")
|
||||
var ErrInvalidBatch = base.MarkCorruptionError(errors.New("pebble: invalid batch"))
|
||||
|
||||
// ErrBatchTooLarge indicates that a batch is invalid or otherwise corrupted.
|
||||
var ErrBatchTooLarge = errors.Newf("pebble: batch too large: >= %s", humanize.Bytes.Uint64(maxBatchSize))
|
||||
var ErrBatchTooLarge = base.MarkCorruptionError(errors.Newf("pebble: batch too large: >= %s", humanize.Bytes.Uint64(maxBatchSize)))
|
||||
|
||||
// DeferredBatchOp represents a batch operation (eg. set, merge, delete) that is
|
||||
// being inserted into the batch. Indexing is not performed on the specified key
|
||||
@@ -183,6 +183,13 @@ func (d DeferredBatchOp) Finish() error {
|
||||
// WAL, and thus stable. New record kinds may be added, but the existing ones
|
||||
// will not be modified.
|
||||
type Batch struct {
|
||||
batchInternal
|
||||
applied atomic.Bool
|
||||
}
|
||||
|
||||
// batchInternal contains the set of fields within Batch that are non-atomic and
|
||||
// capable of being reset using a *b = batchInternal{} struct copy.
|
||||
type batchInternal struct {
|
||||
// Data is the wire format of a batch's log entry:
|
||||
// - 8 bytes for a sequence number of the first batch element,
|
||||
// or zeroes if the batch has not yet been applied,
|
||||
@@ -260,11 +267,6 @@ type Batch struct {
|
||||
// memtable.
|
||||
flushable *flushableBatch
|
||||
|
||||
// ingestedSSTBatch indicates that the batch contains one or more key kinds
|
||||
// of InternalKeyKindIngestSST. If the batch contains key kinds of IngestSST
|
||||
// then it will only contain key kinds of IngestSST.
|
||||
ingestedSSTBatch bool
|
||||
|
||||
// minimumFormatMajorVersion indicates the format major version required in
|
||||
// order to commit this batch. If an operation requires a particular format
|
||||
// major version, it ratchets the batch's minimumFormatMajorVersion. When
|
||||
@@ -292,7 +294,22 @@ type Batch struct {
|
||||
commitStats BatchCommitStats
|
||||
|
||||
commitErr error
|
||||
applied atomic.Bool
|
||||
|
||||
// Position bools together to reduce the sizeof the struct.
|
||||
|
||||
// ingestedSSTBatch indicates that the batch contains one or more key kinds
|
||||
// of InternalKeyKindIngestSST. If the batch contains key kinds of IngestSST
|
||||
// then it will only contain key kinds of IngestSST.
|
||||
ingestedSSTBatch bool
|
||||
|
||||
// committing is set to true when a batch begins to commit. It's used to
|
||||
// ensure the batch is not mutated concurrently. It is not an atomic
|
||||
// deliberately, so as to avoid the overhead on batch mutations. This is
|
||||
// okay, because under correct usage this field will never be accessed
|
||||
// concurrently. It's only under incorrect usage the memory accesses of this
|
||||
// variable may violate memory safety. Since we don't use atomics here,
|
||||
// false negatives are possible.
|
||||
committing bool
|
||||
}
|
||||
|
||||
// BatchCommitStats exposes stats related to committing a batch.
|
||||
@@ -432,18 +449,21 @@ func (b *Batch) release() {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Batch) refreshMemTableSize() {
|
||||
func (b *Batch) refreshMemTableSize() error {
|
||||
b.memTableSize = 0
|
||||
if len(b.data) < batchHeaderLen {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
b.countRangeDels = 0
|
||||
b.countRangeKeys = 0
|
||||
b.minimumFormatMajorVersion = 0
|
||||
for r := b.Reader(); ; {
|
||||
kind, key, value, ok := r.Next()
|
||||
kind, key, value, ok, err := r.Next()
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
switch kind {
|
||||
@@ -452,8 +472,8 @@ func (b *Batch) refreshMemTableSize() {
|
||||
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
b.countRangeKeys++
|
||||
case InternalKeyKindDeleteSized:
|
||||
if b.minimumFormatMajorVersion < ExperimentalFormatDeleteSizedAndObsolete {
|
||||
b.minimumFormatMajorVersion = ExperimentalFormatDeleteSizedAndObsolete
|
||||
if b.minimumFormatMajorVersion < FormatDeleteSizedAndObsolete {
|
||||
b.minimumFormatMajorVersion = FormatDeleteSizedAndObsolete
|
||||
}
|
||||
case InternalKeyKindIngestSST:
|
||||
if b.minimumFormatMajorVersion < FormatFlushableIngest {
|
||||
@@ -467,6 +487,7 @@ func (b *Batch) refreshMemTableSize() {
|
||||
if b.countRangeKeys > 0 && b.minimumFormatMajorVersion < FormatRangeKeys {
|
||||
b.minimumFormatMajorVersion = FormatRangeKeys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply the operations contained in the batch to the receiver batch.
|
||||
@@ -480,7 +501,7 @@ func (b *Batch) Apply(batch *Batch, _ *WriteOptions) error {
|
||||
return nil
|
||||
}
|
||||
if len(batch.data) < batchHeaderLen {
|
||||
return base.CorruptionErrorf("pebble: invalid batch")
|
||||
return ErrInvalidBatch
|
||||
}
|
||||
|
||||
offset := len(b.data)
|
||||
@@ -497,8 +518,11 @@ func (b *Batch) Apply(batch *Batch, _ *WriteOptions) error {
|
||||
// order to update the index.
|
||||
for iter := BatchReader(b.data[offset:]); len(iter) > 0; {
|
||||
offset := uintptr(unsafe.Pointer(&iter[0])) - uintptr(unsafe.Pointer(&b.data[0]))
|
||||
kind, key, value, ok := iter.Next()
|
||||
kind, key, value, ok, err := iter.Next()
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
switch kind {
|
||||
@@ -554,6 +578,9 @@ func (b *Batch) Get(key []byte) ([]byte, io.Closer, error) {
|
||||
}
|
||||
|
||||
func (b *Batch) prepareDeferredKeyValueRecord(keyLen, valueLen int, kind InternalKeyKind) {
|
||||
if b.committing {
|
||||
panic("pebble: batch already committing")
|
||||
}
|
||||
if len(b.data) == 0 {
|
||||
b.init(keyLen + valueLen + 2*binary.MaxVarintLen64 + batchHeaderLen)
|
||||
}
|
||||
@@ -603,6 +630,9 @@ func (b *Batch) prepareDeferredKeyValueRecord(keyLen, valueLen int, kind Interna
|
||||
}
|
||||
|
||||
func (b *Batch) prepareDeferredKeyRecord(keyLen int, kind InternalKeyKind) {
|
||||
if b.committing {
|
||||
panic("pebble: batch already committing")
|
||||
}
|
||||
if len(b.data) == 0 {
|
||||
b.init(keyLen + binary.MaxVarintLen64 + batchHeaderLen)
|
||||
}
|
||||
@@ -784,8 +814,8 @@ func (b *Batch) DeleteSized(key []byte, deletedValueSize uint32, _ *WriteOptions
|
||||
// complete key slice, letting the caller encode into the DeferredBatchOp.Key
|
||||
// slice and then call Finish() on the returned object.
|
||||
func (b *Batch) DeleteSizedDeferred(keyLen int, deletedValueSize uint32) *DeferredBatchOp {
|
||||
if b.minimumFormatMajorVersion < ExperimentalFormatDeleteSizedAndObsolete {
|
||||
b.minimumFormatMajorVersion = ExperimentalFormatDeleteSizedAndObsolete
|
||||
if b.minimumFormatMajorVersion < FormatDeleteSizedAndObsolete {
|
||||
b.minimumFormatMajorVersion = FormatDeleteSizedAndObsolete
|
||||
}
|
||||
|
||||
// Encode the sum of the key length and the value in the value.
|
||||
@@ -1073,11 +1103,12 @@ func (b *Batch) SetRepr(data []byte) error {
|
||||
}
|
||||
b.data = data
|
||||
b.count = uint64(binary.LittleEndian.Uint32(b.countData()))
|
||||
var err error
|
||||
if b.db != nil {
|
||||
// Only track memTableSize for batches that will be committed to the DB.
|
||||
b.refreshMemTableSize()
|
||||
err = b.refreshMemTableSize()
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// NewIter returns an iterator that is unpositioned (Iterator.Valid() will
|
||||
@@ -1320,33 +1351,30 @@ func (b *Batch) init(size int) {
|
||||
if cap(b.data) < n {
|
||||
b.data = rawalloc.New(batchHeaderLen, n)
|
||||
}
|
||||
b.setCount(0)
|
||||
b.setSeqNum(0)
|
||||
b.data = b.data[:batchHeaderLen]
|
||||
// Zero the sequence number in the header.
|
||||
for i := 0; i < len(b.data); i++ {
|
||||
b.data[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the batch for reuse. The underlying byte slice (that is
|
||||
// returned by Repr()) is not modified. It is only necessary to call this
|
||||
// returned by Repr()) may not be modified. It is only necessary to call this
|
||||
// method if a batch is explicitly being reused. Close automatically takes are
|
||||
// of releasing resources when appropriate for batches that are internally
|
||||
// being reused.
|
||||
func (b *Batch) Reset() {
|
||||
b.count = 0
|
||||
b.countRangeDels = 0
|
||||
b.countRangeKeys = 0
|
||||
b.memTableSize = 0
|
||||
b.deferredOp = DeferredBatchOp{}
|
||||
b.tombstones = nil
|
||||
b.tombstonesSeqNum = 0
|
||||
b.rangeKeys = nil
|
||||
b.rangeKeysSeqNum = 0
|
||||
b.flushable = nil
|
||||
b.commit = sync.WaitGroup{}
|
||||
b.fsyncWait = sync.WaitGroup{}
|
||||
b.commitStats = BatchCommitStats{}
|
||||
b.commitErr = nil
|
||||
// Zero out the struct, retaining only the fields necessary for manual
|
||||
// reuse.
|
||||
b.batchInternal = batchInternal{
|
||||
data: b.data,
|
||||
cmp: b.cmp,
|
||||
formatKey: b.formatKey,
|
||||
abbreviatedKey: b.abbreviatedKey,
|
||||
index: b.index,
|
||||
db: b.db,
|
||||
}
|
||||
b.applied.Store(false)
|
||||
b.minimumFormatMajorVersion = 0
|
||||
if b.data != nil {
|
||||
if cap(b.data) > batchMaxRetainedSize {
|
||||
// If the capacity of the buffer is larger than our maximum
|
||||
@@ -1357,13 +1385,14 @@ func (b *Batch) Reset() {
|
||||
} else {
|
||||
// Otherwise, reset the buffer for re-use.
|
||||
b.data = b.data[:batchHeaderLen]
|
||||
b.setSeqNum(0)
|
||||
// Zero the sequence number in the header.
|
||||
for i := 0; i < len(b.data); i++ {
|
||||
b.data[i] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if b.index != nil {
|
||||
b.index.Init(&b.data, b.cmp, b.abbreviatedKey)
|
||||
b.rangeDelIndex = nil
|
||||
b.rangeKeyIndex = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1435,6 +1464,11 @@ func (b *Batch) Reader() BatchReader {
|
||||
}
|
||||
|
||||
func batchDecodeStr(data []byte) (odata []byte, s []byte, ok bool) {
|
||||
// TODO(jackson): This will index out of bounds if there's no varint or an
|
||||
// invalid varint (eg, a single 0xff byte). Correcting will add a bit of
|
||||
// overhead. We could avoid that overhead whenever len(data) >=
|
||||
// binary.MaxVarint32?
|
||||
|
||||
var v uint32
|
||||
var n int
|
||||
ptr := unsafe.Pointer(&data[0])
|
||||
@@ -1497,19 +1531,21 @@ func ReadBatch(repr []byte) (r BatchReader, count uint32) {
|
||||
return repr[batchHeaderLen:], count
|
||||
}
|
||||
|
||||
// Next returns the next entry in this batch. The final return value is false
|
||||
// if the batch is corrupt. The end of batch is reached when len(r)==0.
|
||||
func (r *BatchReader) Next() (kind InternalKeyKind, ukey []byte, value []byte, ok bool) {
|
||||
// Next returns the next entry in this batch, if there is one. If the reader has
|
||||
// reached the end of the batch, Next returns ok=false and a nil error. If the
|
||||
// batch is corrupt and the next entry is illegible, Next returns ok=false and a
|
||||
// non-nil error.
|
||||
func (r *BatchReader) Next() (kind InternalKeyKind, ukey []byte, value []byte, ok bool, err error) {
|
||||
if len(*r) == 0 {
|
||||
return 0, nil, nil, false
|
||||
return 0, nil, nil, false, nil
|
||||
}
|
||||
kind = InternalKeyKind((*r)[0])
|
||||
if kind > InternalKeyKindMax {
|
||||
return 0, nil, nil, false
|
||||
return 0, nil, nil, false, errors.Wrapf(ErrInvalidBatch, "invalid key kind 0x%x", (*r)[0])
|
||||
}
|
||||
*r, ukey, ok = batchDecodeStr((*r)[1:])
|
||||
if !ok {
|
||||
return 0, nil, nil, false
|
||||
return 0, nil, nil, false, errors.Wrapf(ErrInvalidBatch, "decoding user key")
|
||||
}
|
||||
switch kind {
|
||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
||||
@@ -1517,10 +1553,10 @@ func (r *BatchReader) Next() (kind InternalKeyKind, ukey []byte, value []byte, o
|
||||
InternalKeyKindDeleteSized:
|
||||
*r, value, ok = batchDecodeStr(*r)
|
||||
if !ok {
|
||||
return 0, nil, nil, false
|
||||
return 0, nil, nil, false, errors.Wrapf(ErrInvalidBatch, "decoding %s value", kind)
|
||||
}
|
||||
}
|
||||
return kind, ukey, value, true
|
||||
return kind, ukey, value, true, nil
|
||||
}
|
||||
|
||||
// Note: batchIter mirrors the implementation of flushableBatchIter. Keep the
|
||||
@@ -1722,7 +1758,7 @@ var _ flushable = (*flushableBatch)(nil)
|
||||
// interface. This allows the batch to act like a memtable and be placed in the
|
||||
// queue of flushable memtables. Note that the flushable batch takes ownership
|
||||
// of the batch data.
|
||||
func newFlushableBatch(batch *Batch, comparer *Comparer) *flushableBatch {
|
||||
func newFlushableBatch(batch *Batch, comparer *Comparer) (*flushableBatch, error) {
|
||||
b := &flushableBatch{
|
||||
data: batch.data,
|
||||
cmp: comparer.Compare,
|
||||
@@ -1743,8 +1779,11 @@ func newFlushableBatch(batch *Batch, comparer *Comparer) *flushableBatch {
|
||||
var index uint32
|
||||
for iter := BatchReader(b.data[batchHeaderLen:]); len(iter) > 0; index++ {
|
||||
offset := uintptr(unsafe.Pointer(&iter[0])) - uintptr(unsafe.Pointer(&b.data[0]))
|
||||
kind, key, _, ok := iter.Next()
|
||||
kind, key, _, ok, err := iter.Next()
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
entry := flushableBatchEntry{
|
||||
@@ -1816,7 +1855,7 @@ func newFlushableBatch(batch *Batch, comparer *Comparer) *flushableBatch {
|
||||
}
|
||||
fragmentRangeKeys(frag, it, len(rangeKeyOffsets))
|
||||
}
|
||||
return b
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b *flushableBatch) setSeqNum(seqNum uint64) {
|
||||
@@ -2242,7 +2281,10 @@ func batchSort(
|
||||
rangeKeyIter := b.newRangeKeyIter(nil, math.MaxUint64)
|
||||
return pointIter, rangeDelIter, rangeKeyIter
|
||||
}
|
||||
f := newFlushableBatch(b, b.db.opts.Comparer)
|
||||
f, err := newFlushableBatch(b, b.db.opts.Comparer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return f.newIter(nil), f.newRangeDelIter(nil), f.newRangeKeyIter(nil)
|
||||
}
|
||||
|
||||
|
||||
5
vendor/github.com/cockroachdb/pebble/checkpoint.go
generated
vendored
5
vendor/github.com/cockroachdb/pebble/checkpoint.go
generated
vendored
@@ -135,9 +135,6 @@ func mkdirAllAndSyncParents(fs vfs.FS, destDir string) (vfs.File, error) {
|
||||
// space overhead for a checkpoint if hard links are disabled. Also beware that
|
||||
// even if hard links are used, the space overhead for the checkpoint will
|
||||
// increase over time as the DB performs compactions.
|
||||
//
|
||||
// TODO(bananabrick): Test checkpointing of virtual sstables once virtual
|
||||
// sstables is running e2e.
|
||||
func (d *DB) Checkpoint(
|
||||
destDir string, opts ...CheckpointOption,
|
||||
) (
|
||||
@@ -192,7 +189,7 @@ func (d *DB) Checkpoint(
|
||||
manifestSize := d.mu.versions.manifest.Size()
|
||||
optionsFileNum := d.optionsFileNum
|
||||
virtualBackingFiles := make(map[base.DiskFileNum]struct{})
|
||||
for diskFileNum := range d.mu.versions.fileBackingMap {
|
||||
for diskFileNum := range d.mu.versions.backingState.fileBackingMap {
|
||||
virtualBackingFiles[diskFileNum] = struct{}{}
|
||||
}
|
||||
// Release the manifest and DB.mu so we don't block other operations on
|
||||
|
||||
873
vendor/github.com/cockroachdb/pebble/compaction.go
generated
vendored
873
vendor/github.com/cockroachdb/pebble/compaction.go
generated
vendored
File diff suppressed because it is too large
Load Diff
534
vendor/github.com/cockroachdb/pebble/compaction_iter.go
generated
vendored
534
vendor/github.com/cockroachdb/pebble/compaction_iter.go
generated
vendored
@@ -15,9 +15,9 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/bytealloc"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||
"github.com/cockroachdb/pebble/internal/rangekey"
|
||||
"github.com/cockroachdb/redact"
|
||||
)
|
||||
|
||||
// compactionIter provides a forward-only iterator that encapsulates the logic
|
||||
@@ -256,10 +256,12 @@ type compactionIter struct {
|
||||
// The fragmented range keys.
|
||||
rangeKeys []keyspan.Span
|
||||
// Byte allocator for the tombstone keys.
|
||||
alloc bytealloc.A
|
||||
allowZeroSeqNum bool
|
||||
elideTombstone func(key []byte) bool
|
||||
elideRangeTombstone func(start, end []byte) bool
|
||||
alloc bytealloc.A
|
||||
allowZeroSeqNum bool
|
||||
elideTombstone func(key []byte) bool
|
||||
elideRangeTombstone func(start, end []byte) bool
|
||||
ineffectualSingleDeleteCallback func(userKey []byte)
|
||||
singleDeleteInvariantViolationCallback func(userKey []byte)
|
||||
// The on-disk format major version. This informs the types of keys that
|
||||
// may be written to disk during a compaction.
|
||||
formatVersion FormatMajorVersion
|
||||
@@ -281,20 +283,24 @@ func newCompactionIter(
|
||||
allowZeroSeqNum bool,
|
||||
elideTombstone func(key []byte) bool,
|
||||
elideRangeTombstone func(start, end []byte) bool,
|
||||
ineffectualSingleDeleteCallback func(userKey []byte),
|
||||
singleDeleteInvariantViolationCallback func(userKey []byte),
|
||||
formatVersion FormatMajorVersion,
|
||||
) *compactionIter {
|
||||
i := &compactionIter{
|
||||
equal: equal,
|
||||
merge: merge,
|
||||
iter: iter,
|
||||
snapshots: snapshots,
|
||||
frontiers: frontiers{cmp: cmp},
|
||||
rangeDelFrag: rangeDelFrag,
|
||||
rangeKeyFrag: rangeKeyFrag,
|
||||
allowZeroSeqNum: allowZeroSeqNum,
|
||||
elideTombstone: elideTombstone,
|
||||
elideRangeTombstone: elideRangeTombstone,
|
||||
formatVersion: formatVersion,
|
||||
equal: equal,
|
||||
merge: merge,
|
||||
iter: iter,
|
||||
snapshots: snapshots,
|
||||
frontiers: frontiers{cmp: cmp},
|
||||
rangeDelFrag: rangeDelFrag,
|
||||
rangeKeyFrag: rangeKeyFrag,
|
||||
allowZeroSeqNum: allowZeroSeqNum,
|
||||
elideTombstone: elideTombstone,
|
||||
elideRangeTombstone: elideRangeTombstone,
|
||||
ineffectualSingleDeleteCallback: ineffectualSingleDeleteCallback,
|
||||
singleDeleteInvariantViolationCallback: singleDeleteInvariantViolationCallback,
|
||||
formatVersion: formatVersion,
|
||||
}
|
||||
i.rangeDelFrag.Cmp = cmp
|
||||
i.rangeDelFrag.Format = formatKey
|
||||
@@ -333,7 +339,7 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Prior to this call to `Next()` we are in one of three situations with
|
||||
// Prior to this call to `Next()` we are in one of four situations with
|
||||
// respect to `iterKey` and related state:
|
||||
//
|
||||
// - `!skip && pos == iterPosNext`: `iterKey` is already at the next key.
|
||||
@@ -342,12 +348,34 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
// snapshot stripe.
|
||||
// - `skip && pos == iterPosCurForward`: We are at the key that has been returned.
|
||||
// To move forward we skip skippable entries in the stripe.
|
||||
// - `skip && pos == iterPosNext && i.iterStripeChange == sameStripeNonSkippable`:
|
||||
// This case may occur when skipping within a snapshot stripe and we
|
||||
// encounter either:
|
||||
// a) an invalid key kind; The previous call will have returned
|
||||
// whatever key it was processing and deferred handling of the
|
||||
// invalid key to this invocation of Next(). We're responsible for
|
||||
// ignoring skip=true and falling into the invalid key kind case
|
||||
// down below.
|
||||
// b) an interleaved range delete; This is a wart of the current code
|
||||
// structure. While skipping within a snapshot stripe, a range
|
||||
// delete interleaved at its start key and sequence number
|
||||
// interrupts the sequence of point keys. After we return the range
|
||||
// delete to the caller, we need to pick up skipping at where we
|
||||
// left off, so we preserve skip=true.
|
||||
// TODO(jackson): This last case is confusing and can be removed if we
|
||||
// interleave range deletions at the maximal sequence number using the
|
||||
// keyspan interleaving iterator. This is the treatment given to range
|
||||
// keys today.
|
||||
if i.pos == iterPosCurForward {
|
||||
if i.skip {
|
||||
i.skipInStripe()
|
||||
} else {
|
||||
i.nextInStripe()
|
||||
}
|
||||
} else if i.skip {
|
||||
if i.iterStripeChange != sameStripeNonSkippable {
|
||||
panic(errors.AssertionFailedf("compaction iterator has skip=true, but iterator is at iterPosNext"))
|
||||
}
|
||||
}
|
||||
|
||||
i.pos = iterPosCurForward
|
||||
@@ -401,6 +429,10 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
return &i.key, i.value
|
||||
}
|
||||
|
||||
// TODO(sumeer): we could avoid calling Covers if i.iterStripeChange ==
|
||||
// sameStripeSameKey since that check has already been done in
|
||||
// nextInStripeHelper. However, we also need to handle the case of
|
||||
// CoversInvisibly below.
|
||||
if cover := i.rangeDelFrag.Covers(*i.iterKey, i.curSnapshotSeqNum); cover == keyspan.CoversVisibly {
|
||||
// A pending range deletion deletes this key. Skip it.
|
||||
i.saveKey()
|
||||
@@ -429,7 +461,17 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
// If we're at the last snapshot stripe and the tombstone
|
||||
// can be elided skip skippable keys in the same stripe.
|
||||
i.saveKey()
|
||||
i.skipInStripe()
|
||||
if i.key.Kind() == InternalKeyKindSingleDelete {
|
||||
i.skipDueToSingleDeleteElision()
|
||||
} else {
|
||||
i.skipInStripe()
|
||||
if !i.skip && i.iterStripeChange != newStripeNewKey {
|
||||
panic(errors.AssertionFailedf("pebble: skipInStripe in last stripe disabled skip without advancing to new key"))
|
||||
}
|
||||
}
|
||||
if i.iterStripeChange == newStripeSameKey {
|
||||
panic(errors.AssertionFailedf("pebble: skipInStripe in last stripe found a new stripe within the same key"))
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
// We're not at the last snapshot stripe, so the tombstone
|
||||
@@ -456,8 +498,14 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
case InternalKeyKindSingleDelete:
|
||||
if i.singleDeleteNext() {
|
||||
return &i.key, i.value
|
||||
} else if i.err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
continue
|
||||
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected kind %s", redact.SafeString(i.iterKey.Kind().String())))
|
||||
}
|
||||
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||
@@ -467,6 +515,9 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
// preserving the original value, and potentially mutating the key
|
||||
// kind.
|
||||
i.setNext()
|
||||
if i.err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &i.key, i.value
|
||||
|
||||
case InternalKeyKindMerge:
|
||||
@@ -483,7 +534,15 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
if i.err == nil {
|
||||
// includesBase is true whenever we've transformed the MERGE record
|
||||
// into a SET.
|
||||
includesBase := i.key.Kind() == InternalKeyKindSet
|
||||
var includesBase bool
|
||||
switch i.key.Kind() {
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||
includesBase = true
|
||||
case InternalKeyKindMerge:
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected kind %s", redact.SafeString(i.key.Kind().String())))
|
||||
}
|
||||
i.value, needDelete, i.valueCloser, i.err = finishValueMerger(valueMerger, includesBase)
|
||||
}
|
||||
if i.err == nil {
|
||||
@@ -512,6 +571,8 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
}
|
||||
if i.err != nil {
|
||||
i.valid = false
|
||||
// TODO(sumeer): why is MarkCorruptionError only being called for
|
||||
// MERGE?
|
||||
i.err = base.MarkCorruptionError(i.err)
|
||||
}
|
||||
return nil, nil
|
||||
@@ -551,10 +612,16 @@ func snapshotIndex(seq uint64, snapshots []uint64) (int, uint64) {
|
||||
return index, snapshots[index]
|
||||
}
|
||||
|
||||
// skipInStripe skips over skippable keys in the same stripe and user key.
|
||||
// skipInStripe skips over skippable keys in the same stripe and user key. It
|
||||
// may set i.err, in which case i.iterKey will be nil.
|
||||
func (i *compactionIter) skipInStripe() {
|
||||
i.skip = true
|
||||
// TODO(sumeer): we can avoid the overhead of calling i.rangeDelFrag.Covers,
|
||||
// in this case of nextInStripe, since we are skipping all of them anyway.
|
||||
for i.nextInStripe() == sameStripeSkippable {
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
}
|
||||
// Reset skip if we landed outside the original stripe. Otherwise, we landed
|
||||
// in the same stripe on a non-skippable key. In that case we should preserve
|
||||
@@ -592,11 +659,17 @@ const (
|
||||
// nextInStripe advances the iterator and returns one of the above const ints
|
||||
// indicating how its state changed.
|
||||
//
|
||||
// All sameStripeSkippable keys that are covered by a RANGEDEL will be skipped
|
||||
// and not returned.
|
||||
//
|
||||
// Calls to nextInStripe must be preceded by a call to saveKey to retain a
|
||||
// temporary reference to the original key, so that forward iteration can
|
||||
// proceed with a reference to the original key. Care should be taken to avoid
|
||||
// overwriting or mutating the saved key or value before they have been returned
|
||||
// to the caller of the exported function (i.e. the caller of Next, First, etc.)
|
||||
//
|
||||
// nextInStripe may set i.err, in which case the return value will be
|
||||
// newStripeNewKey, and i.iterKey will be nil.
|
||||
func (i *compactionIter) nextInStripe() stripeChangeType {
|
||||
i.iterStripeChange = i.nextInStripeHelper()
|
||||
return i.iterStripeChange
|
||||
@@ -605,58 +678,72 @@ func (i *compactionIter) nextInStripe() stripeChangeType {
|
||||
// nextInStripeHelper is an internal helper for nextInStripe; callers should use
|
||||
// nextInStripe and not call nextInStripeHelper.
|
||||
func (i *compactionIter) nextInStripeHelper() stripeChangeType {
|
||||
if !i.iterNext() {
|
||||
return newStripeNewKey
|
||||
}
|
||||
key := i.iterKey
|
||||
origSnapshotIdx := i.curSnapshotIdx
|
||||
for {
|
||||
if !i.iterNext() {
|
||||
return newStripeNewKey
|
||||
}
|
||||
key := i.iterKey
|
||||
|
||||
// NB: The below conditional is an optimization to avoid a user key
|
||||
// comparison in many cases. Internal keys with the same user key are
|
||||
// ordered in (strictly) descending order by trailer. If the new key has a
|
||||
// greater or equal trailer, or the previous key had a zero sequence number,
|
||||
// the new key must have a new user key.
|
||||
//
|
||||
// A couple things make these cases common:
|
||||
// - Sequence-number zeroing ensures ~all of the keys in L6 have a zero
|
||||
// sequence number.
|
||||
// - Ingested sstables' keys all adopt the same sequence number.
|
||||
if i.keyTrailer <= base.InternalKeyZeroSeqnumMaxTrailer || key.Trailer >= i.keyTrailer {
|
||||
if invariants.Enabled && i.equal(i.key.UserKey, key.UserKey) {
|
||||
if !i.equal(i.key.UserKey, key.UserKey) {
|
||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||
return newStripeNewKey
|
||||
}
|
||||
|
||||
// If i.key and key have the same user key, then
|
||||
// 1. i.key must not have had a zero sequence number (or it would've be the last
|
||||
// key with its user key).
|
||||
// 2. i.key must have a strictly larger sequence number
|
||||
// There's an exception in that either key may be a range delete. Range
|
||||
// deletes may share a sequence number with a point key if the keys were
|
||||
// ingested together. Range keys may also share the sequence number if they
|
||||
// were ingested, but range keys are interleaved into the compaction
|
||||
// iterator's input iterator at the maximal sequence number so their
|
||||
// original sequence number will not be observed here.
|
||||
if prevSeqNum := base.SeqNumFromTrailer(i.keyTrailer); (prevSeqNum == 0 || prevSeqNum <= key.SeqNum()) &&
|
||||
i.key.Kind() != InternalKeyKindRangeDelete && key.Kind() != InternalKeyKindRangeDelete {
|
||||
prevKey := i.key
|
||||
prevKey.Trailer = i.keyTrailer
|
||||
panic(fmt.Sprintf("pebble: invariant violation: %s and %s out of order", key, prevKey))
|
||||
panic(errors.AssertionFailedf("pebble: invariant violation: %s and %s out of order", prevKey, key))
|
||||
}
|
||||
|
||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||
return newStripeNewKey
|
||||
} else if !i.equal(i.key.UserKey, key.UserKey) {
|
||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||
return newStripeNewKey
|
||||
}
|
||||
origSnapshotIdx := i.curSnapshotIdx
|
||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindRangeDelete:
|
||||
// Range tombstones need to be exposed by the compactionIter to the upper level
|
||||
// `compaction` object, so return them regardless of whether they are in the same
|
||||
// snapshot stripe.
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeNonSkippable
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindRangeDelete:
|
||||
// Range tombstones need to be exposed by the compactionIter to the upper level
|
||||
// `compaction` object, so return them regardless of whether they are in the same
|
||||
// snapshot stripe.
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeNonSkippable
|
||||
}
|
||||
return newStripeSameKey
|
||||
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
// Range keys are interleaved at the max sequence number for a given user
|
||||
// key, so we should not see any more range keys in this stripe.
|
||||
panic("unreachable")
|
||||
case InternalKeyKindInvalid:
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeNonSkippable
|
||||
}
|
||||
return newStripeSameKey
|
||||
case InternalKeyKindDelete, InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindSingleDelete,
|
||||
InternalKeyKindSetWithDelete, InternalKeyKindDeleteSized:
|
||||
// Fall through
|
||||
default:
|
||||
i.iterKey = nil
|
||||
i.err = base.CorruptionErrorf("invalid internal key kind: %d", errors.Safe(i.iterKey.Kind()))
|
||||
i.valid = false
|
||||
return newStripeNewKey
|
||||
}
|
||||
return newStripeSameKey
|
||||
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
// Range keys are interleaved at the max sequence number for a given user
|
||||
// key, so we should not see any more range keys in this stripe.
|
||||
panic("unreachable")
|
||||
case InternalKeyKindInvalid:
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeNonSkippable
|
||||
// Same snapshot.
|
||||
if i.rangeDelFrag.Covers(*i.iterKey, i.curSnapshotSeqNum) == keyspan.CoversVisibly {
|
||||
continue
|
||||
}
|
||||
return sameStripeSkippable
|
||||
}
|
||||
return newStripeSameKey
|
||||
}
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeSkippable
|
||||
}
|
||||
return newStripeSameKey
|
||||
}
|
||||
|
||||
func (i *compactionIter) setNext() {
|
||||
@@ -682,6 +769,10 @@ func (i *compactionIter) setNext() {
|
||||
|
||||
// Else, we continue to loop through entries in the stripe looking for a
|
||||
// DEL. Note that we may stop *before* encountering a DEL, if one exists.
|
||||
//
|
||||
// NB: nextInStripe will skip sameStripeSkippable keys that are visibly
|
||||
// covered by a RANGEDEL. This can include DELs -- this is fine since such
|
||||
// DELs don't need to be combined with SET to make SETWITHDEL.
|
||||
for {
|
||||
switch i.nextInStripe() {
|
||||
case newStripeNewKey, newStripeSameKey:
|
||||
@@ -732,12 +823,16 @@ func (i *compactionIter) setNext() {
|
||||
// We're still in the same stripe. If this is a
|
||||
// DEL/SINGLEDEL/DELSIZED, we stop looking and emit a SETWITHDEL.
|
||||
// Subsequent keys are eligible for skipping.
|
||||
if i.iterKey.Kind() == InternalKeyKindDelete ||
|
||||
i.iterKey.Kind() == InternalKeyKindSingleDelete ||
|
||||
i.iterKey.Kind() == InternalKeyKindDeleteSized {
|
||||
switch i.iterKey.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
i.key.SetKind(InternalKeyKindSetWithDelete)
|
||||
i.skip = true
|
||||
return
|
||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindSetWithDelete:
|
||||
// Do nothing
|
||||
default:
|
||||
i.err = base.CorruptionErrorf("invalid internal key kind: %d", errors.Safe(i.iterKey.Kind()))
|
||||
i.valid = false
|
||||
}
|
||||
default:
|
||||
panic("pebble: unexpected stripeChangeType: " + strconv.Itoa(int(i.iterStripeChange)))
|
||||
@@ -757,13 +852,24 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
||||
i.pos = iterPosNext
|
||||
return i.iterStripeChange
|
||||
}
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
// NB: MERGE#10+RANGEDEL#9 stays a MERGE, since nextInStripe skips
|
||||
// sameStripeSkippable keys that are visibly covered by a RANGEDEL. There
|
||||
// may be MERGE#7 that is invisibly covered and will be preserved, but
|
||||
// there is no risk that MERGE#10 and MERGE#7 will get merged in the
|
||||
// future as the RANGEDEL still exists and will be used in user-facing
|
||||
// reads that see MERGE#10, and will also eventually cause MERGE#7 to be
|
||||
// deleted in a compaction.
|
||||
key := i.iterKey
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
// We've hit a deletion tombstone. Return everything up to this point and
|
||||
// then skip entries until the next snapshot stripe. We change the kind
|
||||
// of the result key to a Set so that it shadows keys in lower
|
||||
// levels. That is, MERGE+DEL -> SET.
|
||||
// levels. That is, MERGE+DEL -> SETWITHDEL.
|
||||
//
|
||||
// We do the same for SingleDelete since SingleDelete is only
|
||||
// permitted (with deterministic behavior) for keys that have been
|
||||
// set once since the last SingleDelete/Delete, so everything
|
||||
@@ -776,21 +882,11 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
||||
// single Set, and then merge in any following Sets, but that is
|
||||
// complicated wrt code and unnecessary given the narrow permitted
|
||||
// use of SingleDelete.
|
||||
i.key.SetKind(InternalKeyKindSet)
|
||||
i.key.SetKind(InternalKeyKindSetWithDelete)
|
||||
i.skip = true
|
||||
return sameStripeSkippable
|
||||
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||
if i.rangeDelFrag.Covers(*key, i.curSnapshotSeqNum) == keyspan.CoversVisibly {
|
||||
// We change the kind of the result key to a Set so that it shadows
|
||||
// keys in lower levels. That is, MERGE+RANGEDEL -> SET. This isn't
|
||||
// strictly necessary, but provides consistency with the behavior of
|
||||
// MERGE+DEL.
|
||||
i.key.SetKind(InternalKeyKindSet)
|
||||
i.skip = true
|
||||
return sameStripeSkippable
|
||||
}
|
||||
|
||||
// We've hit a Set or SetWithDel value. Merge with the existing
|
||||
// value and return. We change the kind of the resulting key to a
|
||||
// Set so that it shadows keys in lower levels. That is:
|
||||
@@ -805,16 +901,6 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
||||
return sameStripeSkippable
|
||||
|
||||
case InternalKeyKindMerge:
|
||||
if i.rangeDelFrag.Covers(*key, i.curSnapshotSeqNum) == keyspan.CoversVisibly {
|
||||
// We change the kind of the result key to a Set so that it shadows
|
||||
// keys in lower levels. That is, MERGE+RANGEDEL -> SET. This isn't
|
||||
// strictly necessary, but provides consistency with the behavior of
|
||||
// MERGE+DEL.
|
||||
i.key.SetKind(InternalKeyKindSet)
|
||||
i.skip = true
|
||||
return sameStripeSkippable
|
||||
}
|
||||
|
||||
// We've hit another Merge value. Merge with the existing value and
|
||||
// continue looping.
|
||||
i.err = valueMerger.MergeOlder(i.iterValue)
|
||||
@@ -831,6 +917,16 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
||||
}
|
||||
}
|
||||
|
||||
// singleDeleteNext processes a SingleDelete point tombstone. A SingleDelete, or
|
||||
// SINGLEDEL, is unique in that it deletes exactly 1 internal key. It's a
|
||||
// performance optimization when the client knows a user key has not been
|
||||
// overwritten, allowing the elision of the tombstone earlier, avoiding write
|
||||
// amplification.
|
||||
//
|
||||
// singleDeleteNext returns a boolean indicating whether or not the caller
|
||||
// should yield the SingleDelete key to the consumer of the compactionIter. If
|
||||
// singleDeleteNext returns false, the caller may consume/elide the
|
||||
// SingleDelete.
|
||||
func (i *compactionIter) singleDeleteNext() bool {
|
||||
// Save the current key.
|
||||
i.saveKey()
|
||||
@@ -839,26 +935,85 @@ func (i *compactionIter) singleDeleteNext() bool {
|
||||
|
||||
// Loop until finds a key to be passed to the next level.
|
||||
for {
|
||||
// If we find a key that can't be skipped, return true so that the
|
||||
// caller yields the SingleDelete to the caller.
|
||||
if i.nextInStripe() != sameStripeSkippable {
|
||||
// This defers additional error checking regarding single delete
|
||||
// invariants to the compaction where the keys with the same user key as
|
||||
// the single delete are in the same stripe.
|
||||
i.pos = iterPosNext
|
||||
return true
|
||||
return i.err == nil
|
||||
}
|
||||
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
// INVARIANT: sameStripeSkippable.
|
||||
key := i.iterKey
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindMerge, InternalKeyKindSetWithDelete, InternalKeyKindDeleteSized:
|
||||
// We've hit a Delete, DeleteSized, Merge, SetWithDelete, transform
|
||||
kind := key.Kind()
|
||||
switch kind {
|
||||
case InternalKeyKindDelete, InternalKeyKindSetWithDelete, InternalKeyKindDeleteSized:
|
||||
if (kind == InternalKeyKindDelete || kind == InternalKeyKindDeleteSized) &&
|
||||
i.ineffectualSingleDeleteCallback != nil {
|
||||
i.ineffectualSingleDeleteCallback(i.key.UserKey)
|
||||
}
|
||||
// We've hit a Delete, DeleteSized, SetWithDelete, transform
|
||||
// the SingleDelete into a full Delete.
|
||||
i.key.SetKind(InternalKeyKindDelete)
|
||||
i.skip = true
|
||||
return true
|
||||
|
||||
case InternalKeyKindSet:
|
||||
i.nextInStripe()
|
||||
case InternalKeyKindSet, InternalKeyKindMerge:
|
||||
// This SingleDelete deletes the Set/Merge, and we can now elide the
|
||||
// SingleDel as well. We advance past the Set and return false to
|
||||
// indicate to the main compaction loop that we should NOT yield the
|
||||
// current SingleDel key to the compaction loop.
|
||||
//
|
||||
// NB: singleDeleteNext was called with i.pos == iterPosCurForward, and
|
||||
// after the call to nextInStripe, we are still at iterPosCurForward,
|
||||
// since we are at the key after the Set/Merge that was single deleted.
|
||||
change := i.nextInStripe()
|
||||
switch change {
|
||||
case sameStripeSkippable, newStripeSameKey:
|
||||
// On the same user key.
|
||||
nextKind := i.iterKey.Kind()
|
||||
switch nextKind {
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete, InternalKeyKindMerge:
|
||||
if i.singleDeleteInvariantViolationCallback != nil {
|
||||
// sameStripeSkippable keys returned by nextInStripe() are already
|
||||
// known to not be covered by a RANGEDEL, so it is an invariant
|
||||
// violation. The rare case is newStripeSameKey, where it is a
|
||||
// violation if not covered by a RANGEDEL.
|
||||
if change == sameStripeSkippable ||
|
||||
i.rangeDelFrag.Covers(*i.iterKey, i.curSnapshotSeqNum) == keyspan.NoCover {
|
||||
i.singleDeleteInvariantViolationCallback(i.key.UserKey)
|
||||
}
|
||||
}
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized, InternalKeyKindSingleDelete,
|
||||
InternalKeyKindRangeDelete:
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected internal key kind: %d", errors.Safe(i.iterKey.Kind())))
|
||||
}
|
||||
case sameStripeNonSkippable:
|
||||
// No ability to check whether there is another Set/Merge below with
|
||||
// the same user key.
|
||||
//
|
||||
// TODO(sumeer): once range deletions are interleaved at the maximal
|
||||
// sequence number, this case will go away.
|
||||
case newStripeNewKey:
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
i.valid = false
|
||||
return false
|
||||
|
||||
case InternalKeyKindSingleDelete:
|
||||
// Two single deletes met in a compaction. The first single delete is
|
||||
// ineffectual.
|
||||
if i.ineffectualSingleDeleteCallback != nil {
|
||||
i.ineffectualSingleDeleteCallback(i.key.UserKey)
|
||||
}
|
||||
// Continue to apply the second single delete.
|
||||
continue
|
||||
|
||||
default:
|
||||
@@ -869,6 +1024,135 @@ func (i *compactionIter) singleDeleteNext() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// skipDueToSingleDeleteElision is called when the SingleDelete is being
|
||||
// elided because it is in the final snapshot stripe and there are no keys
|
||||
// with the same user key in lower levels in the LSM (below the files in this
|
||||
// compaction).
|
||||
//
|
||||
// TODO(sumeer): the only difference between singleDeleteNext and
|
||||
// skipDueToSingleDeleteElision is the fact that the caller knows it will be
|
||||
// eliding the single delete in the latter case. There are some similar things
|
||||
// happening in both implementations. My first attempt at combining them into
|
||||
// a single method was hard to comprehend. Try again.
|
||||
func (i *compactionIter) skipDueToSingleDeleteElision() {
|
||||
for {
|
||||
stripeChange := i.nextInStripe()
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
switch stripeChange {
|
||||
case newStripeNewKey:
|
||||
// The single delete is only now being elided, meaning it did not elide
|
||||
// any keys earlier in its descent down the LSM. We stepped onto a new
|
||||
// user key, meaning that even now at its moment of elision, it still
|
||||
// hasn't elided any other keys. The single delete was ineffectual (a
|
||||
// no-op).
|
||||
if i.ineffectualSingleDeleteCallback != nil {
|
||||
i.ineffectualSingleDeleteCallback(i.key.UserKey)
|
||||
}
|
||||
i.skip = false
|
||||
return
|
||||
case newStripeSameKey:
|
||||
// This should be impossible. If we're eliding a single delete, we
|
||||
// determined that the tombstone is in the final snapshot stripe, but we
|
||||
// stepped into a new stripe of the same key.
|
||||
panic(errors.AssertionFailedf("eliding single delete followed by same key in new stripe"))
|
||||
case sameStripeNonSkippable:
|
||||
// There's a key that we cannot skip. There are two possible cases:
|
||||
// a. The key is invalid. This is an error.
|
||||
// b. The key is a range deletion.
|
||||
// The second case may also be an ineffectual single delete. However, it
|
||||
// is possible that there is a SET that is at the same seqnum as the
|
||||
// RANGEDEL, and so is not deleted by that RANGEDEL, and will be deleted
|
||||
// by this single delete. So we cannot be certain that this is an
|
||||
// ineffectual single delete.
|
||||
//
|
||||
// TODO(sumeer): the existing todo to interleave range deletions at the
|
||||
// maximal sequence number will allow us to address this ambiguity.
|
||||
//
|
||||
// TODO(sumeer): by setting skip to true, the compactionIter is making a
|
||||
// single delete stronger (like a del), which will hide bugs in the use of
|
||||
// single delete.
|
||||
i.skip = true
|
||||
return
|
||||
case sameStripeSkippable:
|
||||
kind := i.iterKey.Kind()
|
||||
switch kind {
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized, InternalKeyKindSingleDelete:
|
||||
if i.ineffectualSingleDeleteCallback != nil {
|
||||
i.ineffectualSingleDeleteCallback(i.key.UserKey)
|
||||
}
|
||||
switch kind {
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized:
|
||||
i.skipInStripe()
|
||||
return
|
||||
case InternalKeyKindSingleDelete:
|
||||
// Repeat the same with this SingleDelete. We don't want to simply
|
||||
// call skipInStripe(), since it increases the strength of the
|
||||
// SingleDel, which hides bugs in the use of single delete.
|
||||
continue
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected internal key kind: %d", errors.Safe(i.iterKey.Kind())))
|
||||
}
|
||||
case InternalKeyKindSetWithDelete:
|
||||
// The SingleDelete should behave like a Delete.
|
||||
i.skipInStripe()
|
||||
return
|
||||
case InternalKeyKindSet, InternalKeyKindMerge:
|
||||
// This SingleDelete deletes the Set/Merge, and we are eliding the
|
||||
// SingleDel as well. Step to the next key (this is not deleted by the
|
||||
// SingleDelete).
|
||||
//
|
||||
// NB: skipDueToSingleDeleteElision was called with i.pos ==
|
||||
// iterPosCurForward, and after the call to nextInStripe, we are still
|
||||
// at iterPosCurForward, since we are at the key after the Set/Merge
|
||||
// that was single deleted.
|
||||
change := i.nextInStripe()
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
switch change {
|
||||
case newStripeSameKey:
|
||||
panic(errors.AssertionFailedf("eliding single delete followed by same key in new stripe"))
|
||||
case newStripeNewKey:
|
||||
case sameStripeSkippable:
|
||||
// On the same key.
|
||||
nextKind := i.iterKey.Kind()
|
||||
switch nextKind {
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete, InternalKeyKindMerge:
|
||||
if i.singleDeleteInvariantViolationCallback != nil {
|
||||
i.singleDeleteInvariantViolationCallback(i.key.UserKey)
|
||||
}
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized, InternalKeyKindSingleDelete,
|
||||
InternalKeyKindRangeDelete:
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected internal key kind: %d", errors.Safe(i.iterKey.Kind())))
|
||||
}
|
||||
case sameStripeNonSkippable:
|
||||
// No ability to check whether there is another Set/Merge below with
|
||||
// the same user key.
|
||||
//
|
||||
// TODO(sumeer): once range deletions are interleaved at the maximal
|
||||
// sequence number, this case will go away.
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
// Whether in same stripe or new stripe, this key is not consumed by
|
||||
// the SingleDelete.
|
||||
i.skip = false
|
||||
return
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected internal key kind: %d", errors.Safe(i.iterKey.Kind())))
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteSizedNext processes a DELSIZED point tombstone. Unlike ordinary DELs,
|
||||
// these tombstones carry a value that's a varint indicating the size of the
|
||||
// entry (len(key)+len(value)) that the tombstone is expected to delete.
|
||||
@@ -894,8 +1178,11 @@ func (i *compactionIter) deleteSizedNext() (*base.InternalKey, []byte) {
|
||||
// Loop through all the keys within this stripe that are skippable.
|
||||
i.pos = iterPosNext
|
||||
for i.nextInStripe() == sameStripeSkippable {
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
switch i.iterKey.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized:
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized, InternalKeyKindSingleDelete:
|
||||
// We encountered a tombstone (DEL, or DELSIZED) that's deleted by
|
||||
// the original DELSIZED tombstone. This can happen in two cases:
|
||||
//
|
||||
@@ -917,8 +1204,8 @@ func (i *compactionIter) deleteSizedNext() (*base.InternalKey, []byte) {
|
||||
// We can differentiate these two cases by examining the length of
|
||||
// the DELSIZED's value. A DELSIZED's value holds the size of both
|
||||
// the user key and value that it intends to delete. For any user
|
||||
// key with a length > 1, a DELSIZED that has not deleted a key must
|
||||
// have a value with a length > 1.
|
||||
// key with a length > 0, a DELSIZED that has not deleted a key must
|
||||
// have a value with a length > 0.
|
||||
//
|
||||
// We treat both cases the same functionally, adopting the identity
|
||||
// of the lower-sequence numbered tombstone. However in the second
|
||||
@@ -930,20 +1217,28 @@ func (i *compactionIter) deleteSizedNext() (*base.InternalKey, []byte) {
|
||||
}
|
||||
i.valueBuf = append(i.valueBuf[:0], i.iterValue...)
|
||||
i.value = i.valueBuf
|
||||
if i.iterKey.Kind() == InternalKeyKindDelete {
|
||||
// Convert the DELSIZED to a DEL—The DEL we're eliding may not
|
||||
// have deleted the key(s) it was intended to yet. The ordinary
|
||||
// DEL compaction heuristics are better suited at that, plus we
|
||||
// don't want to count it as a missized DEL. We early exit in
|
||||
// this case, after skipping the remainder of the snapshot
|
||||
// stripe.
|
||||
i.key.SetKind(i.iterKey.Kind())
|
||||
if i.iterKey.Kind() != InternalKeyKindDeleteSized {
|
||||
// Convert the DELSIZED to a DEL—The DEL/SINGLEDEL we're eliding
|
||||
// may not have deleted the key(s) it was intended to yet. The
|
||||
// ordinary DEL compaction heuristics are better suited at that,
|
||||
// plus we don't want to count it as a missized DEL. We early
|
||||
// exit in this case, after skipping the remainder of the
|
||||
// snapshot stripe.
|
||||
i.key.SetKind(InternalKeyKindDelete)
|
||||
// NB: We skipInStripe now, rather than returning leaving
|
||||
// i.skip=true and returning early, because Next() requires
|
||||
// that i.skip=true only if i.iterPos = iterPosCurForward.
|
||||
//
|
||||
// Ignore any error caused by skipInStripe since it does not affect
|
||||
// the key/value being returned here, and the next call to Next() will
|
||||
// expose it.
|
||||
i.skipInStripe()
|
||||
return &i.key, i.value
|
||||
}
|
||||
// Continue, in case we uncover another DELSIZED or a key this
|
||||
// DELSIZED deletes.
|
||||
default:
|
||||
|
||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindSetWithDelete:
|
||||
// If the DELSIZED is value-less, it already deleted the key that it
|
||||
// was intended to delete. This is possible with a sequence like:
|
||||
//
|
||||
@@ -955,6 +1250,13 @@ func (i *compactionIter) deleteSizedNext() (*base.InternalKey, []byte) {
|
||||
// the snapshot stripe and return.
|
||||
if len(i.value) == 0 {
|
||||
i.key.SetKind(InternalKeyKindDelete)
|
||||
// NB: We skipInStripe now, rather than returning leaving
|
||||
// i.skip=true and returning early, because Next() requires
|
||||
// that i.skip=true only if i.iterPos = iterPosCurForward.
|
||||
//
|
||||
// Ignore any error caused by skipInStripe since it does not affect
|
||||
// the key/value being returned here, and the next call to Next() will
|
||||
// expose it.
|
||||
i.skipInStripe()
|
||||
return &i.key, i.value
|
||||
}
|
||||
@@ -982,13 +1284,28 @@ func (i *compactionIter) deleteSizedNext() (*base.InternalKey, []byte) {
|
||||
// We opt for (4) under the rationale that we can't rely on the
|
||||
// user-provided size for accuracy, so ordinary DEL heuristics
|
||||
// are safer.
|
||||
i.key.SetKind(InternalKeyKindDelete)
|
||||
i.stats.countMissizedDels++
|
||||
i.key.SetKind(InternalKeyKindDelete)
|
||||
i.value = i.valueBuf[:0]
|
||||
// NB: We skipInStripe now, rather than returning leaving
|
||||
// i.skip=true and returning early, because Next() requires
|
||||
// that i.skip=true only if i.iterPos = iterPosCurForward.
|
||||
//
|
||||
// Ignore any error caused by skipInStripe since it does not affect
|
||||
// the key/value being returned here, and the next call to Next() will
|
||||
// expose it.
|
||||
i.skipInStripe()
|
||||
return &i.key, i.value
|
||||
}
|
||||
// NB: We remove the value regardless of whether the key was sized
|
||||
// appropriately. The size encoded is 'consumed' the first time it
|
||||
// meets a key that it deletes.
|
||||
i.value = i.valueBuf[:0]
|
||||
|
||||
default:
|
||||
i.err = base.CorruptionErrorf("invalid internal key kind: %d", errors.Safe(i.iterKey.Kind()))
|
||||
i.valid = false
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
// Reset skip if we landed outside the original stripe. Otherwise, we landed
|
||||
@@ -998,6 +1315,9 @@ func (i *compactionIter) deleteSizedNext() (*base.InternalKey, []byte) {
|
||||
if i.iterStripeChange == newStripeNewKey || i.iterStripeChange == newStripeSameKey {
|
||||
i.skip = false
|
||||
}
|
||||
if i.err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &i.key, i.value
|
||||
}
|
||||
|
||||
|
||||
378
vendor/github.com/cockroachdb/pebble/compaction_picker.go
generated
vendored
378
vendor/github.com/cockroachdb/pebble/compaction_picker.go
generated
vendored
@@ -90,18 +90,38 @@ func (info compactionInfo) String() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
type sortCompactionLevelsDecreasingScore []candidateLevelInfo
|
||||
type sortCompactionLevelsByPriority []candidateLevelInfo
|
||||
|
||||
func (s sortCompactionLevelsDecreasingScore) Len() int {
|
||||
func (s sortCompactionLevelsByPriority) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s sortCompactionLevelsDecreasingScore) Less(i, j int) bool {
|
||||
if s[i].score != s[j].score {
|
||||
return s[i].score > s[j].score
|
||||
|
||||
// A level should be picked for compaction if the compensatedScoreRatio is >= the
|
||||
// compactionScoreThreshold.
|
||||
const compactionScoreThreshold = 1
|
||||
|
||||
// Less should return true if s[i] must be placed earlier than s[j] in the final
|
||||
// sorted list. The candidateLevelInfo for the level placed earlier is more likely
|
||||
// to be picked for a compaction.
|
||||
func (s sortCompactionLevelsByPriority) Less(i, j int) bool {
|
||||
iShouldCompact := s[i].compensatedScoreRatio >= compactionScoreThreshold
|
||||
jShouldCompact := s[j].compensatedScoreRatio >= compactionScoreThreshold
|
||||
// Ordering is defined as decreasing on (shouldCompact, uncompensatedScoreRatio)
|
||||
// where shouldCompact is 1 for true and 0 for false.
|
||||
if iShouldCompact && !jShouldCompact {
|
||||
return true
|
||||
}
|
||||
if !iShouldCompact && jShouldCompact {
|
||||
return false
|
||||
}
|
||||
|
||||
if s[i].uncompensatedScoreRatio != s[j].uncompensatedScoreRatio {
|
||||
return s[i].uncompensatedScoreRatio > s[j].uncompensatedScoreRatio
|
||||
}
|
||||
return s[i].level < s[j].level
|
||||
}
|
||||
func (s sortCompactionLevelsDecreasingScore) Swap(i, j int) {
|
||||
|
||||
func (s sortCompactionLevelsByPriority) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
@@ -151,13 +171,22 @@ func generateSublevelInfo(cmp base.Compare, levelFiles manifest.LevelSlice) []su
|
||||
return levelSlices
|
||||
}
|
||||
|
||||
// compactionPickerMetrics holds metrics related to the compaction picking process
|
||||
type compactionPickerMetrics struct {
|
||||
// scores contains the compensatedScoreRatio from the candidateLevelInfo.
|
||||
scores []float64
|
||||
singleLevelOverlappingRatio float64
|
||||
multiLevelOverlappingRatio float64
|
||||
}
|
||||
|
||||
// pickedCompaction contains information about a compaction that has already
|
||||
// been chosen, and is being constructed. Compaction construction info lives in
|
||||
// this struct, and is copied over into the compaction struct when that's
|
||||
// created.
|
||||
type pickedCompaction struct {
|
||||
cmp Compare
|
||||
// score of the chosen compaction. Taken from candidateLevelInfo.
|
||||
// score of the chosen compaction. This is the same as the
|
||||
// compensatedScoreRatio in the candidateLevelInfo.
|
||||
score float64
|
||||
// kind indicates the kind of compaction.
|
||||
kind compactionKind
|
||||
@@ -173,17 +202,12 @@ type pickedCompaction struct {
|
||||
// extraLevels contain additional levels in between the input and output
|
||||
// levels that get compacted in multi level compactions
|
||||
extraLevels []*compactionLevel
|
||||
// adjustedOutputLevel is the output level used for the purpose of
|
||||
// determining the target output file size, overlap bytes, and expanded
|
||||
// bytes, taking into account the base level.
|
||||
adjustedOutputLevel int
|
||||
inputs []compactionLevel
|
||||
inputs []compactionLevel
|
||||
// LBase at the time of compaction picking.
|
||||
baseLevel int
|
||||
// L0-specific compaction info. Set to a non-nil value for all compactions
|
||||
// where startLevel == 0 that were generated by L0Sublevels.
|
||||
lcf *manifest.L0CompactionFiles
|
||||
// L0SublevelInfo is used for compactions out of L0. It is nil for all
|
||||
// other compactions.
|
||||
l0SublevelInfo []sublevelInfo
|
||||
// maxOutputFileSize is the maximum size of an individual table created
|
||||
// during compaction.
|
||||
maxOutputFileSize uint64
|
||||
@@ -195,9 +219,10 @@ type pickedCompaction struct {
|
||||
// maxReadCompaction bytes, then we don't proceed with the compaction.
|
||||
maxReadCompactionBytes uint64
|
||||
// The boundaries of the input data.
|
||||
smallest InternalKey
|
||||
largest InternalKey
|
||||
version *version
|
||||
smallest InternalKey
|
||||
largest InternalKey
|
||||
version *version
|
||||
pickerMetrics compactionPickerMetrics
|
||||
}
|
||||
|
||||
func defaultOutputLevel(startLevel, baseLevel int) int {
|
||||
@@ -219,28 +244,35 @@ func newPickedCompaction(
|
||||
startLevel, baseLevel))
|
||||
}
|
||||
|
||||
adjustedOutputLevel := outputLevel
|
||||
if adjustedOutputLevel > 0 {
|
||||
// Output level is in the range [baseLevel,numLevels]. For the purpose of
|
||||
// determining the target output file size, overlap bytes, and expanded
|
||||
// bytes, we want to adjust the range to [1,numLevels].
|
||||
adjustedOutputLevel = 1 + outputLevel - baseLevel
|
||||
}
|
||||
|
||||
adjustedLevel := adjustedOutputLevel(outputLevel, baseLevel)
|
||||
pc := &pickedCompaction{
|
||||
cmp: opts.Comparer.Compare,
|
||||
version: cur,
|
||||
baseLevel: baseLevel,
|
||||
inputs: []compactionLevel{{level: startLevel}, {level: outputLevel}},
|
||||
adjustedOutputLevel: adjustedOutputLevel,
|
||||
maxOutputFileSize: uint64(opts.Level(adjustedOutputLevel).TargetFileSize),
|
||||
maxOverlapBytes: maxGrandparentOverlapBytes(opts, adjustedOutputLevel),
|
||||
maxReadCompactionBytes: maxReadCompactionBytes(opts, adjustedOutputLevel),
|
||||
maxOutputFileSize: uint64(opts.Level(adjustedLevel).TargetFileSize),
|
||||
maxOverlapBytes: maxGrandparentOverlapBytes(opts, adjustedLevel),
|
||||
maxReadCompactionBytes: maxReadCompactionBytes(opts, adjustedLevel),
|
||||
}
|
||||
pc.startLevel = &pc.inputs[0]
|
||||
pc.outputLevel = &pc.inputs[1]
|
||||
return pc
|
||||
}
|
||||
|
||||
// adjustedOutputLevel is the output level used for the purpose of
|
||||
// determining the target output file size, overlap bytes, and expanded
|
||||
// bytes, taking into account the base level.
|
||||
func adjustedOutputLevel(outputLevel int, baseLevel int) int {
|
||||
adjustedOutputLevel := outputLevel
|
||||
if adjustedOutputLevel > 0 {
|
||||
// Output level is in the range [baseLevel, numLevels]. For the purpose of
|
||||
// determining the target output file size, overlap bytes, and expanded
|
||||
// bytes, we want to adjust the range to [1,numLevels].
|
||||
adjustedOutputLevel = 1 + outputLevel - baseLevel
|
||||
}
|
||||
return adjustedOutputLevel
|
||||
}
|
||||
|
||||
func newPickedCompactionFromL0(
|
||||
lcf *manifest.L0CompactionFiles, opts *Options, vers *version, baseLevel int, isBase bool,
|
||||
) *pickedCompaction {
|
||||
@@ -273,7 +305,7 @@ func (pc *pickedCompaction) String() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(fmt.Sprintf(`Score=%f, `, pc.score))
|
||||
builder.WriteString(fmt.Sprintf(`Kind=%s, `, pc.kind))
|
||||
builder.WriteString(fmt.Sprintf(`AdjustedOutputLevel=%d, `, pc.adjustedOutputLevel))
|
||||
builder.WriteString(fmt.Sprintf(`AdjustedOutputLevel=%d, `, adjustedOutputLevel(pc.outputLevel.level, pc.baseLevel)))
|
||||
builder.WriteString(fmt.Sprintf(`maxOutputFileSize=%d, `, pc.maxOutputFileSize))
|
||||
builder.WriteString(fmt.Sprintf(`maxReadCompactionBytes=%d, `, pc.maxReadCompactionBytes))
|
||||
builder.WriteString(fmt.Sprintf(`smallest=%s, `, pc.smallest))
|
||||
@@ -283,7 +315,7 @@ func (pc *pickedCompaction) String() string {
|
||||
builder.WriteString(fmt.Sprintf(`startlevel=%s, `, pc.startLevel))
|
||||
builder.WriteString(fmt.Sprintf(`outputLevel=%s, `, pc.outputLevel))
|
||||
builder.WriteString(fmt.Sprintf(`extraLevels=%s, `, pc.extraLevels))
|
||||
builder.WriteString(fmt.Sprintf(`l0SublevelInfo=%s, `, pc.l0SublevelInfo))
|
||||
builder.WriteString(fmt.Sprintf(`l0SublevelInfo=%s, `, pc.startLevel.l0SublevelInfo))
|
||||
builder.WriteString(fmt.Sprintf(`lcf=%s`, pc.lcf))
|
||||
return builder.String()
|
||||
}
|
||||
@@ -297,13 +329,16 @@ func (pc *pickedCompaction) clone() *pickedCompaction {
|
||||
cmp: pc.cmp,
|
||||
score: pc.score,
|
||||
kind: pc.kind,
|
||||
adjustedOutputLevel: pc.adjustedOutputLevel,
|
||||
baseLevel: pc.baseLevel,
|
||||
maxOutputFileSize: pc.maxOutputFileSize,
|
||||
maxOverlapBytes: pc.maxOverlapBytes,
|
||||
maxReadCompactionBytes: pc.maxReadCompactionBytes,
|
||||
smallest: pc.smallest.Clone(),
|
||||
largest: pc.largest.Clone(),
|
||||
|
||||
// TODO(msbutler): properly clone picker metrics
|
||||
pickerMetrics: pc.pickerMetrics,
|
||||
|
||||
// Both copies see the same manifest, therefore, it's ok for them to se
|
||||
// share the same pc. version.
|
||||
version: pc.version,
|
||||
@@ -322,9 +357,11 @@ func (pc *pickedCompaction) clone() *pickedCompaction {
|
||||
}
|
||||
}
|
||||
|
||||
newPC.l0SublevelInfo = make([]sublevelInfo, len(pc.l0SublevelInfo))
|
||||
for i := range pc.l0SublevelInfo {
|
||||
newPC.l0SublevelInfo[i] = pc.l0SublevelInfo[i].Clone()
|
||||
if len(pc.startLevel.l0SublevelInfo) > 0 {
|
||||
newPC.startLevel.l0SublevelInfo = make([]sublevelInfo, len(pc.startLevel.l0SublevelInfo))
|
||||
for i := range pc.startLevel.l0SublevelInfo {
|
||||
newPC.startLevel.l0SublevelInfo[i] = pc.startLevel.l0SublevelInfo[i].Clone()
|
||||
}
|
||||
}
|
||||
if pc.lcf != nil {
|
||||
newPC.lcf = pc.lcf.Clone()
|
||||
@@ -370,7 +407,7 @@ func (pc *pickedCompaction) setupInputs(
|
||||
// growing a compaction results in a larger size, the original compaction
|
||||
// is used instead.
|
||||
maxExpandedBytes := expandedCompactionByteSizeLimit(
|
||||
opts, pc.adjustedOutputLevel, diskAvailBytes,
|
||||
opts, adjustedOutputLevel(pc.outputLevel.level, pc.baseLevel), diskAvailBytes,
|
||||
)
|
||||
|
||||
// Expand the initial inputs to a clean cut.
|
||||
@@ -465,7 +502,7 @@ func (pc *pickedCompaction) setupInputs(
|
||||
|
||||
if pc.startLevel.level == 0 {
|
||||
// We don't change the input files for the compaction beyond this point.
|
||||
pc.l0SublevelInfo = generateSublevelInfo(pc.cmp, pc.startLevel.files)
|
||||
pc.startLevel.l0SublevelInfo = generateSublevelInfo(pc.cmp, pc.startLevel.files)
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -528,8 +565,6 @@ func (pc *pickedCompaction) setupMultiLevelCandidate(opts *Options, diskAvailByt
|
||||
pc.startLevel = &pc.inputs[0]
|
||||
pc.extraLevels = []*compactionLevel{&pc.inputs[1]}
|
||||
pc.outputLevel = &pc.inputs[2]
|
||||
|
||||
pc.adjustedOutputLevel++
|
||||
return pc.setupInputs(opts, diskAvailBytes, pc.extraLevels[len(pc.extraLevels)-1])
|
||||
}
|
||||
|
||||
@@ -653,16 +688,23 @@ func newCompactionPicker(
|
||||
// Information about a candidate compaction level that has been identified by
|
||||
// the compaction picker.
|
||||
type candidateLevelInfo struct {
|
||||
// The score of the level to be compacted, with compensated file sizes and
|
||||
// adjustments.
|
||||
score float64
|
||||
// The original score of the level to be compacted, before adjusting
|
||||
// according to other levels' sizes.
|
||||
origScore float64
|
||||
// The raw score of the level to be compacted, calculated using
|
||||
// uncompensated file sizes and without any adjustments.
|
||||
rawScore float64
|
||||
level int
|
||||
// The compensatedScore of the level after adjusting according to the other
|
||||
// levels' sizes. For L0, the compensatedScoreRatio is equivalent to the
|
||||
// uncompensatedScoreRatio as we don't account for level size compensation in
|
||||
// L0.
|
||||
compensatedScoreRatio float64
|
||||
// The score of the level after accounting for level size compensation before
|
||||
// adjusting according to other levels' sizes. For L0, the compensatedScore
|
||||
// is equivalent to the uncompensatedScore as we don't account for level
|
||||
// size compensation in L0.
|
||||
compensatedScore float64
|
||||
// The score of the level to be compacted, calculated using uncompensated file
|
||||
// sizes and without any adjustments.
|
||||
uncompensatedScore float64
|
||||
// uncompensatedScoreRatio is the uncompensatedScore adjusted according to
|
||||
// the other levels' sizes.
|
||||
uncompensatedScoreRatio float64
|
||||
level int
|
||||
// The level to compact to.
|
||||
outputLevel int
|
||||
// The file in level that will be compacted. Additional files may be
|
||||
@@ -671,6 +713,10 @@ type candidateLevelInfo struct {
|
||||
file manifest.LevelFile
|
||||
}
|
||||
|
||||
func (c *candidateLevelInfo) shouldCompact() bool {
|
||||
return c.compensatedScoreRatio >= compactionScoreThreshold
|
||||
}
|
||||
|
||||
func fileCompensation(f *fileMetadata) uint64 {
|
||||
return uint64(f.Stats.PointDeletionsBytesEstimate) + f.Stats.RangeDeletionsBytesEstimate
|
||||
}
|
||||
@@ -748,7 +794,7 @@ var _ compactionPicker = &compactionPickerByScore{}
|
||||
func (p *compactionPickerByScore) getScores(inProgress []compactionInfo) [numLevels]float64 {
|
||||
var scores [numLevels]float64
|
||||
for _, info := range p.calculateLevelScores(inProgress) {
|
||||
scores[info.level] = info.score
|
||||
scores[info.level] = info.compensatedScoreRatio
|
||||
}
|
||||
return scores
|
||||
}
|
||||
@@ -967,68 +1013,83 @@ func (p *compactionPickerByScore) calculateLevelScores(
|
||||
scores[i].level = i
|
||||
scores[i].outputLevel = i + 1
|
||||
}
|
||||
l0UncompensatedScore := calculateL0UncompensatedScore(p.vers, p.opts, inProgressCompactions)
|
||||
scores[0] = candidateLevelInfo{
|
||||
outputLevel: p.baseLevel,
|
||||
score: calculateL0Score(p.vers, p.opts, inProgressCompactions),
|
||||
outputLevel: p.baseLevel,
|
||||
uncompensatedScore: l0UncompensatedScore,
|
||||
compensatedScore: l0UncompensatedScore, /* No level size compensation for L0 */
|
||||
}
|
||||
sizeAdjust := calculateSizeAdjust(inProgressCompactions)
|
||||
for level := 1; level < numLevels; level++ {
|
||||
compensatedLevelSize := levelCompensatedSize(p.vers.Levels[level]) + sizeAdjust[level].compensated()
|
||||
scores[level].score = float64(compensatedLevelSize) / float64(p.levelMaxBytes[level])
|
||||
scores[level].origScore = scores[level].score
|
||||
|
||||
// In addition to the compensated score, we calculate a separate score
|
||||
// that uses actual file sizes, not compensated sizes. This is used
|
||||
// during score smoothing down below to prevent excessive
|
||||
// prioritization of reclaiming disk space.
|
||||
scores[level].rawScore = float64(p.vers.Levels[level].Size()+sizeAdjust[level].actual()) / float64(p.levelMaxBytes[level])
|
||||
scores[level].compensatedScore = float64(compensatedLevelSize) / float64(p.levelMaxBytes[level])
|
||||
scores[level].uncompensatedScore = float64(p.vers.Levels[level].Size()+sizeAdjust[level].actual()) / float64(p.levelMaxBytes[level])
|
||||
}
|
||||
|
||||
// Adjust each level's score by the score of the next level. If the next
|
||||
// level has a high score, and is thus a priority for compaction, this
|
||||
// reduces the priority for compacting the current level. If the next level
|
||||
// has a low score (i.e. it is below its target size), this increases the
|
||||
// priority for compacting the current level.
|
||||
// Adjust each level's {compensated, uncompensated}Score by the uncompensatedScore
|
||||
// of the next level to get a {compensated, uncompensated}ScoreRatio. If the
|
||||
// next level has a high uncompensatedScore, and is thus a priority for compaction,
|
||||
// this reduces the priority for compacting the current level. If the next level
|
||||
// has a low uncompensatedScore (i.e. it is below its target size), this increases
|
||||
// the priority for compacting the current level.
|
||||
//
|
||||
// The effect of this adjustment is to help prioritize compactions in lower
|
||||
// levels. The following shows the new score and original score. In this
|
||||
// scenario, L0 has 68 sublevels. L3 (a.k.a. Lbase) is significantly above
|
||||
// its target size. The original score prioritizes compactions from those two
|
||||
// levels, but doing so ends up causing a future problem: data piles up in
|
||||
// the higher levels, starving L5->L6 compactions, and to a lesser degree
|
||||
// starving L4->L5 compactions.
|
||||
// levels. The following example shows the compensatedScoreRatio and the
|
||||
// compensatedScore. In this scenario, L0 has 68 sublevels. L3 (a.k.a. Lbase)
|
||||
// is significantly above its target size. The original score prioritizes
|
||||
// compactions from those two levels, but doing so ends up causing a future
|
||||
// problem: data piles up in the higher levels, starving L5->L6 compactions,
|
||||
// and to a lesser degree starving L4->L5 compactions.
|
||||
//
|
||||
// adjusted original
|
||||
// score score size max-size
|
||||
// L0 3.2 68.0 2.2 G -
|
||||
// L3 3.2 21.1 1.3 G 64 M
|
||||
// L4 3.4 6.7 3.1 G 467 M
|
||||
// L5 3.4 2.0 6.6 G 3.3 G
|
||||
// L6 0.6 0.6 14 G 24 G
|
||||
// Note that in the example shown there is no level size compensation so the
|
||||
// compensatedScore and the uncompensatedScore is the same for each level.
|
||||
//
|
||||
// compensatedScoreRatio compensatedScore uncompensatedScore size max-size
|
||||
// L0 3.2 68.0 68.0 2.2 G -
|
||||
// L3 3.2 21.1 21.1 1.3 G 64 M
|
||||
// L4 3.4 6.7 6.7 3.1 G 467 M
|
||||
// L5 3.4 2.0 2.0 6.6 G 3.3 G
|
||||
// L6 0.6 0.6 0.6 14 G 24 G
|
||||
var prevLevel int
|
||||
for level := p.baseLevel; level < numLevels; level++ {
|
||||
if scores[prevLevel].score >= 1 {
|
||||
// Avoid absurdly large scores by placing a floor on the score that we'll
|
||||
// adjust a level by. The value of 0.01 was chosen somewhat arbitrarily
|
||||
const minScore = 0.01
|
||||
if scores[level].rawScore >= minScore {
|
||||
scores[prevLevel].score /= scores[level].rawScore
|
||||
// The compensated scores, and uncompensated scores will be turned into
|
||||
// ratios as they're adjusted according to other levels' sizes.
|
||||
scores[prevLevel].compensatedScoreRatio = scores[prevLevel].compensatedScore
|
||||
scores[prevLevel].uncompensatedScoreRatio = scores[prevLevel].uncompensatedScore
|
||||
|
||||
// Avoid absurdly large scores by placing a floor on the score that we'll
|
||||
// adjust a level by. The value of 0.01 was chosen somewhat arbitrarily.
|
||||
const minScore = 0.01
|
||||
if scores[prevLevel].compensatedScoreRatio >= compactionScoreThreshold {
|
||||
if scores[level].uncompensatedScore >= minScore {
|
||||
scores[prevLevel].compensatedScoreRatio /= scores[level].uncompensatedScore
|
||||
} else {
|
||||
scores[prevLevel].score /= minScore
|
||||
scores[prevLevel].compensatedScoreRatio /= minScore
|
||||
}
|
||||
}
|
||||
if scores[prevLevel].uncompensatedScoreRatio >= compactionScoreThreshold {
|
||||
if scores[level].uncompensatedScore >= minScore {
|
||||
scores[prevLevel].uncompensatedScoreRatio /= scores[level].uncompensatedScore
|
||||
} else {
|
||||
scores[prevLevel].uncompensatedScoreRatio /= minScore
|
||||
}
|
||||
}
|
||||
prevLevel = level
|
||||
}
|
||||
// Set the score ratios for the lowest level.
|
||||
// INVARIANT: prevLevel == numLevels-1
|
||||
scores[prevLevel].compensatedScoreRatio = scores[prevLevel].compensatedScore
|
||||
scores[prevLevel].uncompensatedScoreRatio = scores[prevLevel].uncompensatedScore
|
||||
|
||||
sort.Sort(sortCompactionLevelsDecreasingScore(scores[:]))
|
||||
sort.Sort(sortCompactionLevelsByPriority(scores[:]))
|
||||
return scores
|
||||
}
|
||||
|
||||
// calculateL0Score calculates a float score representing the relative priority
|
||||
// of compacting L0. Level L0 is special in that files within L0 may overlap one
|
||||
// another, so a different set of heuristics that take into account
|
||||
// read amplification apply.
|
||||
func calculateL0Score(
|
||||
// calculateL0UncompensatedScore calculates a float score representing the
|
||||
// relative priority of compacting L0. Level L0 is special in that files within
|
||||
// L0 may overlap one another, so a different set of heuristics that take into
|
||||
// account read amplification apply.
|
||||
func calculateL0UncompensatedScore(
|
||||
vers *version, opts *Options, inProgressCompactions []compactionInfo,
|
||||
) float64 {
|
||||
// Use the sublevel count to calculate the score. The base vs intra-L0
|
||||
@@ -1231,8 +1292,9 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
|
||||
if pc.startLevel.level == info.level {
|
||||
marker = "*"
|
||||
}
|
||||
fmt.Fprintf(&buf, " %sL%d: %5.1f %5.1f %5.1f %8s %8s",
|
||||
marker, info.level, info.score, info.origScore, info.rawScore,
|
||||
fmt.Fprintf(&buf, " %sL%d: %5.1f %5.1f %5.1f %5.1f %8s %8s",
|
||||
marker, info.level, info.compensatedScoreRatio, info.compensatedScore,
|
||||
info.uncompensatedScoreRatio, info.uncompensatedScore,
|
||||
humanize.Bytes.Int64(int64(totalCompensatedSize(
|
||||
p.vers.Levels[info.level].Iter(),
|
||||
))),
|
||||
@@ -1262,12 +1324,12 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
|
||||
pc.startLevel.level, pc.outputLevel.level, buf.String())
|
||||
}
|
||||
|
||||
// Check for a score-based compaction. "scores" has been sorted in order of
|
||||
// decreasing score. For each level with a score >= 1, we attempt to find a
|
||||
// compaction anchored at at that level.
|
||||
// Check for a score-based compaction. candidateLevelInfos are first sorted
|
||||
// by whether they should be compacted, so if we find a level which shouldn't
|
||||
// be compacted, we can break early.
|
||||
for i := range scores {
|
||||
info := &scores[i]
|
||||
if info.score < 1 {
|
||||
if !info.shouldCompact() {
|
||||
break
|
||||
}
|
||||
if info.level == numLevels-1 {
|
||||
@@ -1279,7 +1341,8 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
|
||||
// Fail-safe to protect against compacting the same sstable
|
||||
// concurrently.
|
||||
if pc != nil && !inputRangeAlreadyCompacting(env, pc) {
|
||||
pc.score = info.score
|
||||
p.addScoresToPickedCompactionMetrics(pc, scores)
|
||||
pc.score = info.compensatedScoreRatio
|
||||
// TODO(bananabrick): Create an EventListener for logCompaction.
|
||||
if false {
|
||||
logCompaction(pc)
|
||||
@@ -1299,7 +1362,8 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
|
||||
pc := pickAutoLPositive(env, p.opts, p.vers, *info, p.baseLevel, p.levelMaxBytes)
|
||||
// Fail-safe to protect against compacting the same sstable concurrently.
|
||||
if pc != nil && !inputRangeAlreadyCompacting(env, pc) {
|
||||
pc.score = info.score
|
||||
p.addScoresToPickedCompactionMetrics(pc, scores)
|
||||
pc.score = info.compensatedScoreRatio
|
||||
// TODO(bananabrick): Create an EventListener for logCompaction.
|
||||
if false {
|
||||
logCompaction(pc)
|
||||
@@ -1360,6 +1424,30 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *compactionPickerByScore) addScoresToPickedCompactionMetrics(
|
||||
pc *pickedCompaction, candInfo [numLevels]candidateLevelInfo,
|
||||
) {
|
||||
|
||||
// candInfo is sorted by score, not by compaction level.
|
||||
infoByLevel := [numLevels]candidateLevelInfo{}
|
||||
for i := range candInfo {
|
||||
level := candInfo[i].level
|
||||
infoByLevel[level] = candInfo[i]
|
||||
}
|
||||
// Gather the compaction scores for the levels participating in the compaction.
|
||||
pc.pickerMetrics.scores = make([]float64, len(pc.inputs))
|
||||
inputIdx := 0
|
||||
for i := range infoByLevel {
|
||||
if pc.inputs[inputIdx].level == infoByLevel[i].level {
|
||||
pc.pickerMetrics.scores[inputIdx] = infoByLevel[i].compensatedScoreRatio
|
||||
inputIdx++
|
||||
}
|
||||
if inputIdx == len(pc.inputs) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// elisionOnlyAnnotator implements the manifest.Annotator interface,
|
||||
// annotating B-Tree nodes with the *fileMetadata of a file meeting the
|
||||
// obsolete keys criteria for an elision-only compaction within the subtree.
|
||||
@@ -1558,7 +1646,7 @@ func (p *compactionPickerByScore) pickRewriteCompaction(env compactionEnv) (pc *
|
||||
// Fail-safe to protect against compacting the same sstable concurrently.
|
||||
if !inputRangeAlreadyCompacting(env, pc) {
|
||||
if pc.startLevel.level == 0 {
|
||||
pc.l0SublevelInfo = generateSublevelInfo(pc.cmp, pc.startLevel.files)
|
||||
pc.startLevel.l0SublevelInfo = generateSublevelInfo(pc.cmp, pc.startLevel.files)
|
||||
}
|
||||
return pc
|
||||
}
|
||||
@@ -1605,33 +1693,113 @@ func pickAutoLPositive(
|
||||
|
||||
// maybeAddLevel maybe adds a level to the picked compaction.
|
||||
func (pc *pickedCompaction) maybeAddLevel(opts *Options, diskAvailBytes uint64) *pickedCompaction {
|
||||
pc.pickerMetrics.singleLevelOverlappingRatio = pc.overlappingRatio()
|
||||
if pc.outputLevel.level == numLevels-1 {
|
||||
// Don't add a level if the current output level is in L6
|
||||
return pc
|
||||
}
|
||||
if !opts.Experimental.MultiLevelCompactionHeuristic.allowL0() && pc.startLevel.level == 0 {
|
||||
return pc
|
||||
}
|
||||
if pc.compactionSize() > expandedCompactionByteSizeLimit(
|
||||
opts, pc.adjustedOutputLevel, diskAvailBytes) {
|
||||
opts, adjustedOutputLevel(pc.outputLevel.level, pc.baseLevel), diskAvailBytes) {
|
||||
// Don't add a level if the current compaction exceeds the compaction size limit
|
||||
return pc
|
||||
}
|
||||
return opts.Experimental.MultiLevelCompactionHueristic.pick(pc, opts, diskAvailBytes)
|
||||
return opts.Experimental.MultiLevelCompactionHeuristic.pick(pc, opts, diskAvailBytes)
|
||||
}
|
||||
|
||||
// MultiLevelHeuristic evaluates whether to add files from the next level into the compaction.
|
||||
type MultiLevelHeuristic interface {
|
||||
// evaluate returns the preferred compaction.
|
||||
// Evaluate returns the preferred compaction.
|
||||
pick(pc *pickedCompaction, opts *Options, diskAvailBytes uint64) *pickedCompaction
|
||||
|
||||
// Returns if the heuristic allows L0 to be involved in ML compaction
|
||||
allowL0() bool
|
||||
}
|
||||
|
||||
// NoMultiLevel will never add an additional level to the compaction.
|
||||
type NoMultiLevel struct{}
|
||||
|
||||
var _ MultiLevelHeuristic = (*NoMultiLevel)(nil)
|
||||
|
||||
func (nml NoMultiLevel) pick(
|
||||
pc *pickedCompaction, opts *Options, diskAvailBytes uint64,
|
||||
) *pickedCompaction {
|
||||
return pc
|
||||
}
|
||||
|
||||
func (nml NoMultiLevel) allowL0() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (pc *pickedCompaction) predictedWriteAmp() float64 {
|
||||
var bytesToCompact uint64
|
||||
var higherLevelBytes uint64
|
||||
for i := range pc.inputs {
|
||||
levelSize := pc.inputs[i].files.SizeSum()
|
||||
bytesToCompact += levelSize
|
||||
if i != len(pc.inputs)-1 {
|
||||
higherLevelBytes += levelSize
|
||||
}
|
||||
}
|
||||
return float64(bytesToCompact) / float64(higherLevelBytes)
|
||||
}
|
||||
|
||||
func (pc *pickedCompaction) overlappingRatio() float64 {
|
||||
var higherLevelBytes uint64
|
||||
var lowestLevelBytes uint64
|
||||
for i := range pc.inputs {
|
||||
levelSize := pc.inputs[i].files.SizeSum()
|
||||
if i == len(pc.inputs)-1 {
|
||||
lowestLevelBytes += levelSize
|
||||
continue
|
||||
}
|
||||
higherLevelBytes += levelSize
|
||||
}
|
||||
return float64(lowestLevelBytes) / float64(higherLevelBytes)
|
||||
}
|
||||
|
||||
// WriteAmpHeuristic defines a multi level compaction heuristic which will add
|
||||
// an additional level to the picked compaction if it reduces predicted write
|
||||
// amp of the compaction + the addPropensity constant.
|
||||
type WriteAmpHeuristic struct {
|
||||
// addPropensity is a constant that affects the propensity to conduct multilevel
|
||||
// compactions. If positive, a multilevel compaction may get picked even if
|
||||
// the single level compaction has lower write amp, and vice versa.
|
||||
AddPropensity float64
|
||||
|
||||
// AllowL0 if true, allow l0 to be involved in a ML compaction.
|
||||
AllowL0 bool
|
||||
}
|
||||
|
||||
var _ MultiLevelHeuristic = (*WriteAmpHeuristic)(nil)
|
||||
|
||||
// TODO(msbutler): microbenchmark the extent to which multilevel compaction
|
||||
// picking slows down the compaction picking process. This should be as fast as
|
||||
// possible since Compaction-picking holds d.mu, which prevents WAL rotations,
|
||||
// in-progress flushes and compactions from completing, etc. Consider ways to
|
||||
// deduplicate work, given that setupInputs has already been called.
|
||||
func (wa WriteAmpHeuristic) pick(
|
||||
pcOrig *pickedCompaction, opts *Options, diskAvailBytes uint64,
|
||||
) *pickedCompaction {
|
||||
pcMulti := pcOrig.clone()
|
||||
if !pcMulti.setupMultiLevelCandidate(opts, diskAvailBytes) {
|
||||
return pcOrig
|
||||
}
|
||||
picked := pcOrig
|
||||
if pcMulti.predictedWriteAmp() <= pcOrig.predictedWriteAmp()+wa.AddPropensity {
|
||||
picked = pcMulti
|
||||
}
|
||||
// Regardless of what compaction was picked, log the multilevelOverlapping ratio.
|
||||
picked.pickerMetrics.multiLevelOverlappingRatio = pcMulti.overlappingRatio()
|
||||
return picked
|
||||
}
|
||||
|
||||
func (wa WriteAmpHeuristic) allowL0() bool {
|
||||
return wa.AllowL0
|
||||
}
|
||||
|
||||
// Helper method to pick compactions originating from L0. Uses information about
|
||||
// sublevels to generate a compaction.
|
||||
func pickL0(env compactionEnv, opts *Options, vers *version, baseLevel int) (pc *pickedCompaction) {
|
||||
@@ -1653,7 +1821,7 @@ func pickL0(env compactionEnv, opts *Options, vers *version, baseLevel int) (pc
|
||||
if pc.startLevel.files.Empty() {
|
||||
opts.Logger.Fatalf("empty compaction chosen")
|
||||
}
|
||||
return pc
|
||||
return pc.maybeAddLevel(opts, env.diskAvailBytes)
|
||||
}
|
||||
|
||||
// Couldn't choose a base compaction. Try choosing an intra-L0
|
||||
|
||||
112
vendor/github.com/cockroachdb/pebble/db.go
generated
vendored
112
vendor/github.com/cockroachdb/pebble/db.go
generated
vendored
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/arenaskl"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invalidating"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||
"github.com/cockroachdb/pebble/internal/manifest"
|
||||
@@ -315,9 +316,6 @@ type DB struct {
|
||||
closedCh chan struct{}
|
||||
|
||||
cleanupManager *cleanupManager
|
||||
// testingAlwaysWaitForCleanup is set by some tests to force waiting for
|
||||
// obsolete file deletion (to make events deterministic).
|
||||
testingAlwaysWaitForCleanup bool
|
||||
|
||||
// During an iterator close, we may asynchronously schedule read compactions.
|
||||
// We want to wait for those goroutines to finish, before closing the DB.
|
||||
@@ -558,9 +556,7 @@ func (d *DB) getInternal(key []byte, b *Batch, s *Snapshot) ([]byte, io.Closer,
|
||||
get := &buf.get
|
||||
*get = getIter{
|
||||
logger: d.opts.Logger,
|
||||
cmp: d.cmp,
|
||||
equal: d.equal,
|
||||
split: d.split,
|
||||
comparer: d.opts.Comparer,
|
||||
newIters: d.newIters,
|
||||
snapshot: seqNum,
|
||||
key: key,
|
||||
@@ -808,6 +804,9 @@ func (d *DB) applyInternal(batch *Batch, opts *WriteOptions, noSyncWait bool) er
|
||||
if err := d.closed.Load(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if batch.committing {
|
||||
panic("pebble: batch already committing")
|
||||
}
|
||||
if batch.applied.Load() {
|
||||
panic("pebble: batch already applied")
|
||||
}
|
||||
@@ -838,12 +837,19 @@ func (d *DB) applyInternal(batch *Batch, opts *WriteOptions, noSyncWait bool) er
|
||||
}
|
||||
// TODO(jackson): Assert that all range key operands are suffixless.
|
||||
}
|
||||
batch.committing = true
|
||||
|
||||
if batch.db == nil {
|
||||
batch.refreshMemTableSize()
|
||||
if err := batch.refreshMemTableSize(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if batch.memTableSize >= d.largeBatchThreshold {
|
||||
batch.flushable = newFlushableBatch(batch, d.opts.Comparer)
|
||||
var err error
|
||||
batch.flushable, err = newFlushableBatch(batch, d.opts.Comparer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.commit.Commit(batch, sync, noSyncWait); err != nil {
|
||||
// There isn't much we can do on an error here. The commit pipeline will be
|
||||
@@ -986,15 +992,14 @@ var iterAllocPool = sync.Pool{
|
||||
// - No snapshot: All fields are zero values.
|
||||
// - Classic snapshot: Only `seqNum` is set. The latest readState will be used
|
||||
// and the specified seqNum will be used as the snapshot seqNum.
|
||||
// - EventuallyFileOnlySnapshot (EFOS) in pre-file-only state: `readState` and
|
||||
// `seqNum` are set. The specified readState is used with the snapshot
|
||||
// sequence number.
|
||||
// - EFOS in file-only state: Only `seqNum` and `vers` are set. No readState
|
||||
// is referenced, as all the relevant SSTs are referenced by the *version.
|
||||
// - EventuallyFileOnlySnapshot (EFOS) behaving as a classic snapshot. Only
|
||||
// the `seqNum` is set. The latest readState will be used
|
||||
// and the specified seqNum will be used as the snapshot seqNum.
|
||||
// - EFOS in file-only state: Only `seqNum` and `vers` are set. All the
|
||||
// relevant SSTs are referenced by the *version.
|
||||
type snapshotIterOpts struct {
|
||||
seqNum uint64
|
||||
readState *readState
|
||||
vers *version
|
||||
seqNum uint64
|
||||
vers *version
|
||||
}
|
||||
|
||||
// newIter constructs a new iterator, merging in batch iterators as an extra
|
||||
@@ -1027,12 +1032,10 @@ func (d *DB) newIter(
|
||||
// Grab and reference the current readState. This prevents the underlying
|
||||
// files in the associated version from being deleted if there is a current
|
||||
// compaction. The readState is unref'd by Iterator.Close().
|
||||
readState := sOpts.readState
|
||||
if readState == nil && sOpts.vers == nil {
|
||||
var readState *readState
|
||||
if sOpts.vers == nil {
|
||||
// NB: loadReadState() calls readState.ref().
|
||||
readState = d.loadReadState()
|
||||
} else if readState != nil {
|
||||
readState.ref()
|
||||
} else {
|
||||
// s.vers != nil
|
||||
sOpts.vers.Ref()
|
||||
@@ -1149,6 +1152,7 @@ func finishInitializingIter(ctx context.Context, buf *iterAlloc) *Iterator {
|
||||
},
|
||||
}
|
||||
dbi.iter = &dbi.lazyCombinedIter
|
||||
dbi.iter = invalidating.MaybeWrapIfInvariants(dbi.iter)
|
||||
} else {
|
||||
dbi.lazyCombinedIter.combinedIterState = combinedIterState{
|
||||
initialized: true,
|
||||
@@ -1249,11 +1253,9 @@ func (d *DB) newInternalIter(sOpts snapshotIterOpts, o *scanInternalOptions) *sc
|
||||
// Grab and reference the current readState. This prevents the underlying
|
||||
// files in the associated version from being deleted if there is a current
|
||||
// compaction. The readState is unref'd by Iterator.Close().
|
||||
readState := sOpts.readState
|
||||
if readState == nil && sOpts.vers == nil {
|
||||
var readState *readState
|
||||
if sOpts.vers == nil {
|
||||
readState = d.loadReadState()
|
||||
} else if readState != nil {
|
||||
readState.ref()
|
||||
}
|
||||
if sOpts.vers != nil {
|
||||
sOpts.vers.Ref()
|
||||
@@ -1421,12 +1423,12 @@ func (i *Iterator) constructPointIter(
|
||||
addLevelIterForFiles := func(files manifest.LevelIterator, level manifest.Level) {
|
||||
li := &levels[levelsIndex]
|
||||
|
||||
li.init(
|
||||
ctx, i.opts, i.comparer.Compare, i.comparer.Split, i.newIters, files, level, internalOpts)
|
||||
li.init(ctx, i.opts, &i.comparer, i.newIters, files, level, internalOpts)
|
||||
li.initRangeDel(&mlevels[mlevelsIndex].rangeDelIter)
|
||||
li.initBoundaryContext(&mlevels[mlevelsIndex].levelIterBoundaryContext)
|
||||
li.initCombinedIterState(&i.lazyCombinedIter.combinedIterState)
|
||||
mlevels[mlevelsIndex].iter = li
|
||||
mlevels[mlevelsIndex].levelIter = li
|
||||
mlevels[mlevelsIndex].iter = invalidating.MaybeWrapIfInvariants(li)
|
||||
|
||||
levelsIndex++
|
||||
mlevelsIndex++
|
||||
@@ -1452,7 +1454,7 @@ func (i *Iterator) constructPointIter(
|
||||
buf.merging.snapshot = i.seqNum
|
||||
buf.merging.batchSnapshot = i.batchSeqNum
|
||||
buf.merging.combinedIterState = &i.lazyCombinedIter.combinedIterState
|
||||
i.pointIter = &buf.merging
|
||||
i.pointIter = invalidating.MaybeWrapIfInvariants(&buf.merging)
|
||||
i.merging = &buf.merging
|
||||
}
|
||||
|
||||
@@ -1968,6 +1970,17 @@ func (d *DB) Metrics() *Metrics {
|
||||
|
||||
d.mu.versions.logLock()
|
||||
metrics.private.manifestFileSize = uint64(d.mu.versions.manifest.Size())
|
||||
metrics.Table.BackingTableCount = uint64(len(d.mu.versions.backingState.fileBackingMap))
|
||||
metrics.Table.BackingTableSize = d.mu.versions.backingState.fileBackingSize
|
||||
if invariants.Enabled {
|
||||
var totalSize uint64
|
||||
for _, backing := range d.mu.versions.backingState.fileBackingMap {
|
||||
totalSize += backing.Size
|
||||
}
|
||||
if totalSize != metrics.Table.BackingTableSize {
|
||||
panic("pebble: invalid backing table size accounting")
|
||||
}
|
||||
}
|
||||
d.mu.versions.logUnlock()
|
||||
|
||||
metrics.LogWriter.FsyncLatency = d.mu.log.metrics.fsyncLatency
|
||||
@@ -2746,14 +2759,40 @@ func (d *DB) SetCreatorID(creatorID uint64) error {
|
||||
|
||||
// KeyStatistics keeps track of the number of keys that have been pinned by a
|
||||
// snapshot as well as counts of the different key kinds in the lsm.
|
||||
//
|
||||
// One way of using the accumulated stats, when we only have sets and dels,
|
||||
// and say the counts are represented as del_count, set_count,
|
||||
// del_latest_count, set_latest_count, snapshot_pinned_count.
|
||||
//
|
||||
// - del_latest_count + set_latest_count is the set of unique user keys
|
||||
// (unique).
|
||||
//
|
||||
// - set_latest_count is the set of live unique user keys (live_unique).
|
||||
//
|
||||
// - Garbage is del_count + set_count - live_unique.
|
||||
//
|
||||
// - If everything were in the LSM, del_count+set_count-snapshot_pinned_count
|
||||
// would also be the set of unique user keys (note that
|
||||
// snapshot_pinned_count is counting something different -- see comment below).
|
||||
// But snapshot_pinned_count only counts keys in the LSM so the excess here
|
||||
// must be keys in memtables.
|
||||
type KeyStatistics struct {
|
||||
// when a compaction determines a key is obsolete, but cannot elide the key
|
||||
// because it's required by an open snapshot.
|
||||
// TODO(sumeer): the SnapshotPinned* are incorrect in that these older
|
||||
// versions can be in a different level. Either fix the accounting or
|
||||
// rename these fields.
|
||||
|
||||
// SnapshotPinnedKeys represents obsolete keys that cannot be elided during
|
||||
// a compaction, because they are required by an open snapshot.
|
||||
SnapshotPinnedKeys int
|
||||
// the total number of bytes of all snapshot pinned keys.
|
||||
// SnapshotPinnedKeysBytes is the total number of bytes of all snapshot
|
||||
// pinned keys.
|
||||
SnapshotPinnedKeysBytes uint64
|
||||
// Note: these fields are currently only populated for point keys (including range deletes).
|
||||
// KindsCount is the count for each kind of key. It includes point keys,
|
||||
// range deletes and range keys.
|
||||
KindsCount [InternalKeyKindMax + 1]int
|
||||
// LatestKindsCount is the count for each kind of key when it is the latest
|
||||
// kind for a user key. It is only populated for point keys.
|
||||
LatestKindsCount [InternalKeyKindMax + 1]int
|
||||
}
|
||||
|
||||
// LSMKeyStatistics is used by DB.ScanStatistics.
|
||||
@@ -2798,7 +2837,8 @@ func (d *DB) ScanStatistics(
|
||||
// pinned by a snapshot.
|
||||
size := uint64(key.Size())
|
||||
kind := key.Kind()
|
||||
if iterInfo.Kind == IteratorLevelLSM && d.equal(prevKey.UserKey, key.UserKey) {
|
||||
sameKey := d.equal(prevKey.UserKey, key.UserKey)
|
||||
if iterInfo.Kind == IteratorLevelLSM && sameKey {
|
||||
stats.Levels[iterInfo.Level].SnapshotPinnedKeys++
|
||||
stats.Levels[iterInfo.Level].SnapshotPinnedKeysBytes += size
|
||||
stats.Accumulated.SnapshotPinnedKeys++
|
||||
@@ -2807,6 +2847,12 @@ func (d *DB) ScanStatistics(
|
||||
if iterInfo.Kind == IteratorLevelLSM {
|
||||
stats.Levels[iterInfo.Level].KindsCount[kind]++
|
||||
}
|
||||
if !sameKey {
|
||||
if iterInfo.Kind == IteratorLevelLSM {
|
||||
stats.Levels[iterInfo.Level].LatestKindsCount[kind]++
|
||||
}
|
||||
stats.Accumulated.LatestKindsCount[kind]++
|
||||
}
|
||||
|
||||
stats.Accumulated.KindsCount[kind]++
|
||||
prevKey.CopyFrom(*key)
|
||||
|
||||
60
vendor/github.com/cockroachdb/pebble/event.go
generated
vendored
60
vendor/github.com/cockroachdb/pebble/event.go
generated
vendored
@@ -39,10 +39,11 @@ func formatFileNums(tables []TableInfo) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// LevelInfo contains info pertaining to a partificular level.
|
||||
// LevelInfo contains info pertaining to a particular level.
|
||||
type LevelInfo struct {
|
||||
Level int
|
||||
Tables []TableInfo
|
||||
Score float64
|
||||
}
|
||||
|
||||
func (i LevelInfo) String() string {
|
||||
@@ -51,8 +52,11 @@ func (i LevelInfo) String() string {
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (i LevelInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("L%d [%s] (%s)", redact.Safe(i.Level), redact.Safe(formatFileNums(i.Tables)),
|
||||
redact.Safe(humanize.Bytes.Uint64(tablesTotalSize(i.Tables))))
|
||||
w.Printf("L%d [%s] (%s) Score=%.2f",
|
||||
redact.Safe(i.Level),
|
||||
redact.Safe(formatFileNums(i.Tables)),
|
||||
redact.Safe(humanize.Bytes.Uint64(tablesTotalSize(i.Tables))),
|
||||
redact.Safe(i.Score))
|
||||
}
|
||||
|
||||
// CompactionInfo contains the info for a compaction event.
|
||||
@@ -75,6 +79,27 @@ type CompactionInfo struct {
|
||||
TotalDuration time.Duration
|
||||
Done bool
|
||||
Err error
|
||||
|
||||
SingleLevelOverlappingRatio float64
|
||||
MultiLevelOverlappingRatio float64
|
||||
|
||||
// Annotations specifies additional info to appear in a compaction's event log line
|
||||
Annotations compactionAnnotations
|
||||
}
|
||||
|
||||
type compactionAnnotations []string
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (ca compactionAnnotations) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if len(ca) == 0 {
|
||||
return
|
||||
}
|
||||
for i := range ca {
|
||||
if i != 0 {
|
||||
w.Print(" ")
|
||||
}
|
||||
w.Printf("%s", redact.SafeString(ca[i]))
|
||||
}
|
||||
}
|
||||
|
||||
func (i CompactionInfo) String() string {
|
||||
@@ -90,12 +115,17 @@ func (i CompactionInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
}
|
||||
|
||||
if !i.Done {
|
||||
w.Printf("[JOB %d] compacting(%s) ", redact.Safe(i.JobID), redact.SafeString(i.Reason))
|
||||
w.Print(levelInfos(i.Input))
|
||||
w.Printf("[JOB %d] compacting(%s) ",
|
||||
redact.Safe(i.JobID),
|
||||
redact.SafeString(i.Reason))
|
||||
w.Printf("%s", i.Annotations)
|
||||
w.Printf("%s; ", levelInfos(i.Input))
|
||||
w.Printf("OverlappingRatio: Single %.2f, Multi %.2f", i.SingleLevelOverlappingRatio, i.MultiLevelOverlappingRatio)
|
||||
return
|
||||
}
|
||||
outputSize := tablesTotalSize(i.Output.Tables)
|
||||
w.Printf("[JOB %d] compacted(%s) ", redact.Safe(i.JobID), redact.SafeString(i.Reason))
|
||||
w.Printf("%s", i.Annotations)
|
||||
w.Print(levelInfos(i.Input))
|
||||
w.Printf(" -> L%d [%s] (%s), in %.1fs (%.1fs total), output rate %s/s",
|
||||
redact.Safe(i.Output.Level),
|
||||
@@ -202,7 +232,7 @@ func (i FlushInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if j > 0 {
|
||||
w.Printf(" +")
|
||||
}
|
||||
w.Printf(" L%d:%s (%s)", level, redact.Safe(file.FileNum), humanize.Bytes.Uint64(file.Size))
|
||||
w.Printf(" L%d:%s (%s)", level, file.FileNum, humanize.Bytes.Uint64(file.Size))
|
||||
}
|
||||
w.Printf(" in %.1fs (%.1fs total), output rate %s/s",
|
||||
redact.Safe(i.Duration.Seconds()),
|
||||
@@ -231,7 +261,7 @@ func (i ManifestCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("[JOB %d] MANIFEST create error: %s", redact.Safe(i.JobID), i.Err)
|
||||
return
|
||||
}
|
||||
w.Printf("[JOB %d] MANIFEST created %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] MANIFEST created %s", redact.Safe(i.JobID), i.FileNum)
|
||||
}
|
||||
|
||||
// ManifestDeleteInfo contains the info for a Manifest deletion event.
|
||||
@@ -253,7 +283,7 @@ func (i ManifestDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("[JOB %d] MANIFEST delete error: %s", redact.Safe(i.JobID), i.Err)
|
||||
return
|
||||
}
|
||||
w.Printf("[JOB %d] MANIFEST deleted %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] MANIFEST deleted %s", redact.Safe(i.JobID), i.FileNum)
|
||||
}
|
||||
|
||||
// TableCreateInfo contains the info for a table creation event.
|
||||
@@ -273,7 +303,7 @@ func (i TableCreateInfo) String() string {
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (i TableCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("[JOB %d] %s: sstable created %s",
|
||||
redact.Safe(i.JobID), redact.Safe(i.Reason), redact.Safe(i.FileNum))
|
||||
redact.Safe(i.JobID), redact.Safe(i.Reason), i.FileNum)
|
||||
}
|
||||
|
||||
// TableDeleteInfo contains the info for a table deletion event.
|
||||
@@ -292,10 +322,10 @@ func (i TableDeleteInfo) String() string {
|
||||
func (i TableDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if i.Err != nil {
|
||||
w.Printf("[JOB %d] sstable delete error %s: %s",
|
||||
redact.Safe(i.JobID), redact.Safe(i.FileNum), i.Err)
|
||||
redact.Safe(i.JobID), i.FileNum, i.Err)
|
||||
return
|
||||
}
|
||||
w.Printf("[JOB %d] sstable deleted %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] sstable deleted %s", redact.Safe(i.JobID), i.FileNum)
|
||||
}
|
||||
|
||||
// TableIngestInfo contains the info for a table ingestion event.
|
||||
@@ -341,7 +371,7 @@ func (i TableIngestInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if !i.flushable {
|
||||
levelStr = fmt.Sprintf("L%d:", t.Level)
|
||||
}
|
||||
w.Printf(" %s%s (%s)", redact.Safe(levelStr), redact.Safe(t.FileNum),
|
||||
w.Printf(" %s%s (%s)", redact.Safe(levelStr), t.FileNum,
|
||||
redact.Safe(humanize.Bytes.Uint64(t.Size)))
|
||||
}
|
||||
}
|
||||
@@ -403,12 +433,12 @@ func (i WALCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
}
|
||||
|
||||
if i.RecycledFileNum == 0 {
|
||||
w.Printf("[JOB %d] WAL created %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] WAL created %s", redact.Safe(i.JobID), i.FileNum)
|
||||
return
|
||||
}
|
||||
|
||||
w.Printf("[JOB %d] WAL created %s (recycled %s)",
|
||||
redact.Safe(i.JobID), redact.Safe(i.FileNum), redact.Safe(i.RecycledFileNum))
|
||||
redact.Safe(i.JobID), i.FileNum, i.RecycledFileNum)
|
||||
}
|
||||
|
||||
// WALDeleteInfo contains the info for a WAL deletion event.
|
||||
@@ -430,7 +460,7 @@ func (i WALDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("[JOB %d] WAL delete error: %s", redact.Safe(i.JobID), i.Err)
|
||||
return
|
||||
}
|
||||
w.Printf("[JOB %d] WAL deleted %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] WAL deleted %s", redact.Safe(i.JobID), i.FileNum)
|
||||
}
|
||||
|
||||
// WriteStallBeginInfo contains the info for a write stall begin event.
|
||||
|
||||
2
vendor/github.com/cockroachdb/pebble/external_iterator.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/external_iterator.go
generated
vendored
@@ -311,7 +311,7 @@ func finishInitializingExternal(ctx context.Context, it *Iterator) {
|
||||
base.InternalKeySeqNumMax,
|
||||
it.opts.LowerBound, it.opts.UpperBound,
|
||||
&it.hasPrefix, &it.prefixOrFullSeekKey,
|
||||
true /* onlySets */, &it.rangeKey.internal,
|
||||
false /* internalKeys */, &it.rangeKey.internal,
|
||||
)
|
||||
for i := range rangeKeyIters {
|
||||
it.rangeKey.iterConfig.AddLevel(rangeKeyIters[i])
|
||||
|
||||
17
vendor/github.com/cockroachdb/pebble/flushable.go
generated
vendored
17
vendor/github.com/cockroachdb/pebble/flushable.go
generated
vendored
@@ -117,8 +117,7 @@ type flushableList []*flushableEntry
|
||||
// ingesting sstables which are added to the flushable list.
|
||||
type ingestedFlushable struct {
|
||||
files []physicalMeta
|
||||
cmp Compare
|
||||
split Split
|
||||
comparer *Comparer
|
||||
newIters tableNewIters
|
||||
newRangeKeyIters keyspan.TableNewSpanIter
|
||||
|
||||
@@ -131,8 +130,7 @@ type ingestedFlushable struct {
|
||||
|
||||
func newIngestedFlushable(
|
||||
files []*fileMetadata,
|
||||
cmp Compare,
|
||||
split Split,
|
||||
comparer *Comparer,
|
||||
newIters tableNewIters,
|
||||
newRangeKeyIters keyspan.TableNewSpanIter,
|
||||
) *ingestedFlushable {
|
||||
@@ -147,12 +145,11 @@ func newIngestedFlushable(
|
||||
|
||||
ret := &ingestedFlushable{
|
||||
files: physicalFiles,
|
||||
cmp: cmp,
|
||||
split: split,
|
||||
comparer: comparer,
|
||||
newIters: newIters,
|
||||
newRangeKeyIters: newRangeKeyIters,
|
||||
// slice is immutable and can be set once and used many times.
|
||||
slice: manifest.NewLevelSliceKeySorted(cmp, files),
|
||||
slice: manifest.NewLevelSliceKeySorted(comparer.Compare, files),
|
||||
hasRangeKeys: hasRangeKeys,
|
||||
}
|
||||
|
||||
@@ -173,7 +170,7 @@ func (s *ingestedFlushable) newIter(o *IterOptions) internalIterator {
|
||||
// aren't truly levels in the lsm. Right now, the encoding only supports
|
||||
// L0 sublevels, and the rest of the levels in the lsm.
|
||||
return newLevelIter(
|
||||
opts, s.cmp, s.split, s.newIters, s.slice.Iter(), manifest.Level(0), internalIterOpts{},
|
||||
opts, s.comparer, s.newIters, s.slice.Iter(), manifest.Level(0), internalIterOpts{},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -206,7 +203,7 @@ func (s *ingestedFlushable) constructRangeDelIter(
|
||||
// surface range deletes is more efficient.
|
||||
func (s *ingestedFlushable) newRangeDelIter(_ *IterOptions) keyspan.FragmentIterator {
|
||||
return keyspan.NewLevelIter(
|
||||
keyspan.SpanIterOptions{}, s.cmp,
|
||||
keyspan.SpanIterOptions{}, s.comparer.Compare,
|
||||
s.constructRangeDelIter, s.slice.Iter(), manifest.Level(0),
|
||||
manifest.KeyTypePoint,
|
||||
)
|
||||
@@ -219,7 +216,7 @@ func (s *ingestedFlushable) newRangeKeyIter(o *IterOptions) keyspan.FragmentIter
|
||||
}
|
||||
|
||||
return keyspan.NewLevelIter(
|
||||
keyspan.SpanIterOptions{}, s.cmp, s.newRangeKeyIters,
|
||||
keyspan.SpanIterOptions{}, s.comparer.Compare, s.newRangeKeyIters,
|
||||
s.slice.Iter(), manifest.Level(0), manifest.KeyTypeRange,
|
||||
)
|
||||
}
|
||||
|
||||
54
vendor/github.com/cockroachdb/pebble/format_major_version.go
generated
vendored
54
vendor/github.com/cockroachdb/pebble/format_major_version.go
generated
vendored
@@ -28,10 +28,13 @@ import (
|
||||
// FormatVersion that the default corresponds to may change with time.
|
||||
type FormatMajorVersion uint64
|
||||
|
||||
// SafeValue implements redact.SafeValue.
|
||||
func (v FormatMajorVersion) SafeValue() {}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (v FormatMajorVersion) String() string {
|
||||
// NB: This must not change. It's used as the value for the the
|
||||
// on-disk version marker file.
|
||||
// NB: This must not change. It's used as the value for the on-disk
|
||||
// version marker file.
|
||||
//
|
||||
// Specifically, this value must always parse as a base 10 integer
|
||||
// that fits in a uint64. We format it as zero-padded, 3-digit
|
||||
@@ -110,12 +113,12 @@ const (
|
||||
|
||||
// 23.1 versions.
|
||||
|
||||
// FormatUnusedPrePebblev1MarkedCompacted is an unused format major version.
|
||||
// formatUnusedPrePebblev1MarkedCompacted is an unused format major version.
|
||||
// This format major version was originally intended to ship in the 23.1
|
||||
// release. It was later decided that this should be deferred until a
|
||||
// subsequent release. The original ordering is preserved so as not to
|
||||
// introduce breaking changes in Cockroach.
|
||||
FormatUnusedPrePebblev1MarkedCompacted
|
||||
formatUnusedPrePebblev1MarkedCompacted
|
||||
|
||||
// FormatSSTableValueBlocks is a format major version that adds support for
|
||||
// storing values in value blocks in the sstable. Value block support is not
|
||||
@@ -151,20 +154,20 @@ const (
|
||||
// compactions for files marked for compaction are complete.
|
||||
FormatPrePebblev1MarkedCompacted
|
||||
|
||||
// ExperimentalFormatDeleteSizedAndObsolete is a format major version that adds support
|
||||
// FormatDeleteSizedAndObsolete is a format major version that adds support
|
||||
// for deletion tombstones that encode the size of the value they're
|
||||
// expected to delete. This format major version is required before the
|
||||
// associated key kind may be committed through batch applications or
|
||||
// ingests. It also adds support for keys that are marked obsolete (see
|
||||
// sstable/format.go for details).
|
||||
ExperimentalFormatDeleteSizedAndObsolete
|
||||
FormatDeleteSizedAndObsolete
|
||||
|
||||
// ExperimentalFormatVirtualSSTables is a format major version that adds support for
|
||||
// FormatVirtualSSTables is a format major version that adds support for
|
||||
// virtual sstables that can reference a sub-range of keys in an underlying
|
||||
// physical sstable. This information is persisted through new,
|
||||
// backward-incompatible fields in the Manifest, and therefore requires
|
||||
// a format major version.
|
||||
ExperimentalFormatVirtualSSTables
|
||||
FormatVirtualSSTables
|
||||
|
||||
// internalFormatNewest holds the newest format major version, including
|
||||
// experimental ones excluded from the exported FormatNewest constant until
|
||||
@@ -172,8 +175,7 @@ const (
|
||||
internalFormatNewest FormatMajorVersion = iota - 1
|
||||
|
||||
// FormatNewest always contains the most recent format major version.
|
||||
// TODO(jackson): restore FormatNewest to internalFormatNewest.
|
||||
FormatNewest FormatMajorVersion = FormatPrePebblev1MarkedCompacted
|
||||
FormatNewest FormatMajorVersion = internalFormatNewest
|
||||
)
|
||||
|
||||
// MaxTableFormat returns the maximum sstable.TableFormat that can be used at
|
||||
@@ -187,11 +189,11 @@ func (v FormatMajorVersion) MaxTableFormat() sstable.TableFormat {
|
||||
FormatSplitUserKeysMarkedCompacted:
|
||||
return sstable.TableFormatPebblev1
|
||||
case FormatRangeKeys, FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
|
||||
FormatUnusedPrePebblev1MarkedCompacted:
|
||||
formatUnusedPrePebblev1MarkedCompacted:
|
||||
return sstable.TableFormatPebblev2
|
||||
case FormatSSTableValueBlocks, FormatFlushableIngest, FormatPrePebblev1MarkedCompacted:
|
||||
return sstable.TableFormatPebblev3
|
||||
case ExperimentalFormatDeleteSizedAndObsolete, ExperimentalFormatVirtualSSTables:
|
||||
case FormatDeleteSizedAndObsolete, FormatVirtualSSTables:
|
||||
return sstable.TableFormatPebblev4
|
||||
default:
|
||||
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
|
||||
@@ -208,15 +210,27 @@ func (v FormatMajorVersion) MinTableFormat() sstable.TableFormat {
|
||||
FormatRangeKeys:
|
||||
return sstable.TableFormatLevelDB
|
||||
case FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
|
||||
FormatUnusedPrePebblev1MarkedCompacted, FormatSSTableValueBlocks,
|
||||
formatUnusedPrePebblev1MarkedCompacted, FormatSSTableValueBlocks,
|
||||
FormatFlushableIngest, FormatPrePebblev1MarkedCompacted,
|
||||
ExperimentalFormatDeleteSizedAndObsolete, ExperimentalFormatVirtualSSTables:
|
||||
FormatDeleteSizedAndObsolete, FormatVirtualSSTables:
|
||||
return sstable.TableFormatPebblev1
|
||||
default:
|
||||
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
|
||||
}
|
||||
}
|
||||
|
||||
// orderingInvariants returns an enum encoding the set of invariants that must
|
||||
// hold within the receiver format major version. Invariants only get stricter
|
||||
// as the format major version advances, so it is okay to retrieve the
|
||||
// invariants from the current format major version and by the time the
|
||||
// invariants are enforced, the format major version has advanced.
|
||||
func (v FormatMajorVersion) orderingInvariants() manifest.OrderingInvariants {
|
||||
if v < FormatSplitUserKeysMarkedCompacted {
|
||||
return manifest.AllowSplitUserKeys
|
||||
}
|
||||
return manifest.ProhibitSplitUserKeys
|
||||
}
|
||||
|
||||
// formatMajorVersionMigrations defines the migrations from one format
|
||||
// major version to the next. Each migration is defined as a closure
|
||||
// which will be invoked on the database before the new format major
|
||||
@@ -324,9 +338,9 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
|
||||
}
|
||||
return d.finalizeFormatVersUpgrade(FormatPrePebblev1Marked)
|
||||
},
|
||||
FormatUnusedPrePebblev1MarkedCompacted: func(d *DB) error {
|
||||
formatUnusedPrePebblev1MarkedCompacted: func(d *DB) error {
|
||||
// Intentional no-op.
|
||||
return d.finalizeFormatVersUpgrade(FormatUnusedPrePebblev1MarkedCompacted)
|
||||
return d.finalizeFormatVersUpgrade(formatUnusedPrePebblev1MarkedCompacted)
|
||||
},
|
||||
FormatSSTableValueBlocks: func(d *DB) error {
|
||||
return d.finalizeFormatVersUpgrade(FormatSSTableValueBlocks)
|
||||
@@ -343,11 +357,11 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
|
||||
}
|
||||
return d.finalizeFormatVersUpgrade(FormatPrePebblev1MarkedCompacted)
|
||||
},
|
||||
ExperimentalFormatDeleteSizedAndObsolete: func(d *DB) error {
|
||||
return d.finalizeFormatVersUpgrade(ExperimentalFormatDeleteSizedAndObsolete)
|
||||
FormatDeleteSizedAndObsolete: func(d *DB) error {
|
||||
return d.finalizeFormatVersUpgrade(FormatDeleteSizedAndObsolete)
|
||||
},
|
||||
ExperimentalFormatVirtualSSTables: func(d *DB) error {
|
||||
return d.finalizeFormatVersUpgrade(ExperimentalFormatVirtualSSTables)
|
||||
FormatVirtualSSTables: func(d *DB) error {
|
||||
return d.finalizeFormatVersUpgrade(FormatVirtualSSTables)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
20
vendor/github.com/cockroachdb/pebble/get_iter.go
generated
vendored
20
vendor/github.com/cockroachdb/pebble/get_iter.go
generated
vendored
@@ -19,9 +19,7 @@ import (
|
||||
// lazily.
|
||||
type getIter struct {
|
||||
logger Logger
|
||||
cmp Compare
|
||||
equal Equal
|
||||
split Split
|
||||
comparer *Comparer
|
||||
newIters tableNewIters
|
||||
snapshot uint64
|
||||
key []byte
|
||||
@@ -84,7 +82,7 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
// key. Every call to levelIter.Next() potentially switches to a new
|
||||
// table and thus reinitializes rangeDelIter.
|
||||
if g.rangeDelIter != nil {
|
||||
g.tombstone = keyspan.Get(g.cmp, g.rangeDelIter, g.key)
|
||||
g.tombstone = keyspan.Get(g.comparer.Compare, g.rangeDelIter, g.key)
|
||||
if g.err = g.rangeDelIter.Close(); g.err != nil {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
@@ -102,7 +100,7 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
g.iter = nil
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
if g.equal(g.key, key.UserKey) {
|
||||
if g.comparer.Equal(g.key, key.UserKey) {
|
||||
if !key.Visible(g.snapshot, base.InternalKeySeqNumMax) {
|
||||
g.iterKey, g.iterValue = g.iter.Next()
|
||||
continue
|
||||
@@ -160,7 +158,7 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
files := g.l0[n-1].Iter()
|
||||
g.l0 = g.l0[:n-1]
|
||||
iterOpts := IterOptions{logger: g.logger, snapshotForHideObsoletePoints: g.snapshot}
|
||||
g.levelIter.init(context.Background(), iterOpts, g.cmp, g.split, g.newIters,
|
||||
g.levelIter.init(context.Background(), iterOpts, g.comparer, g.newIters,
|
||||
files, manifest.L0Sublevel(n), internalIterOpts{})
|
||||
g.levelIter.initRangeDel(&g.rangeDelIter)
|
||||
bc := levelIterBoundaryContext{}
|
||||
@@ -170,8 +168,8 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
// Compute the key prefix for bloom filtering if split function is
|
||||
// specified, or use the user key as default.
|
||||
prefix := g.key
|
||||
if g.split != nil {
|
||||
prefix = g.key[:g.split(g.key)]
|
||||
if g.comparer.Split != nil {
|
||||
prefix = g.key[:g.comparer.Split(g.key)]
|
||||
}
|
||||
g.iterKey, g.iterValue = g.iter.SeekPrefixGE(prefix, g.key, base.SeekGEFlagsNone)
|
||||
if bc.isSyntheticIterBoundsKey || bc.isIgnorableBoundaryKey {
|
||||
@@ -192,7 +190,7 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
}
|
||||
|
||||
iterOpts := IterOptions{logger: g.logger, snapshotForHideObsoletePoints: g.snapshot}
|
||||
g.levelIter.init(context.Background(), iterOpts, g.cmp, g.split, g.newIters,
|
||||
g.levelIter.init(context.Background(), iterOpts, g.comparer, g.newIters,
|
||||
g.version.Levels[g.level].Iter(), manifest.Level(g.level), internalIterOpts{})
|
||||
g.levelIter.initRangeDel(&g.rangeDelIter)
|
||||
bc := levelIterBoundaryContext{}
|
||||
@@ -203,8 +201,8 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
// Compute the key prefix for bloom filtering if split function is
|
||||
// specified, or use the user key as default.
|
||||
prefix := g.key
|
||||
if g.split != nil {
|
||||
prefix = g.key[:g.split(g.key)]
|
||||
if g.comparer.Split != nil {
|
||||
prefix = g.key[:g.comparer.Split(g.key)]
|
||||
}
|
||||
g.iterKey, g.iterValue = g.iter.SeekPrefixGE(prefix, g.key, base.SeekGEFlagsNone)
|
||||
if bc.isSyntheticIterBoundsKey || bc.isIgnorableBoundaryKey {
|
||||
|
||||
425
vendor/github.com/cockroachdb/pebble/ingest.go
generated
vendored
425
vendor/github.com/cockroachdb/pebble/ingest.go
generated
vendored
@@ -113,11 +113,28 @@ func ingestSynthesizeShared(
|
||||
meta.FileBacking.Size = sm.Size
|
||||
if sm.LargestRangeKey.Valid() && sm.LargestRangeKey.UserKey != nil {
|
||||
// Initialize meta.{HasRangeKeys,Smallest,Largest}, etc.
|
||||
meta.ExtendRangeKeyBounds(opts.Comparer.Compare, sm.SmallestRangeKey, sm.LargestRangeKey)
|
||||
//
|
||||
// NB: We create new internal keys and pass them into ExternalRangeKeyBounds
|
||||
// so that we can sub a zero sequence number into the bounds. We can set
|
||||
// the sequence number to anything here; it'll be reset in ingestUpdateSeqNum
|
||||
// anyway. However we do need to use the same sequence number across all
|
||||
// bound keys at this step so that we end up with bounds that are consistent
|
||||
// across point/range keys.
|
||||
smallestRangeKey := base.MakeInternalKey(sm.SmallestRangeKey.UserKey, 0, sm.SmallestRangeKey.Kind())
|
||||
largestRangeKey := base.MakeExclusiveSentinelKey(sm.LargestRangeKey.Kind(), sm.LargestRangeKey.UserKey)
|
||||
meta.ExtendRangeKeyBounds(opts.Comparer.Compare, smallestRangeKey, largestRangeKey)
|
||||
}
|
||||
if sm.LargestPointKey.Valid() && sm.LargestPointKey.UserKey != nil {
|
||||
// Initialize meta.{HasPointKeys,Smallest,Largest}, etc.
|
||||
meta.ExtendPointKeyBounds(opts.Comparer.Compare, sm.SmallestPointKey, sm.LargestPointKey)
|
||||
//
|
||||
// See point above in the ExtendRangeKeyBounds call on why we use a zero
|
||||
// sequence number here.
|
||||
smallestPointKey := base.MakeInternalKey(sm.SmallestPointKey.UserKey, 0, sm.SmallestPointKey.Kind())
|
||||
largestPointKey := base.MakeInternalKey(sm.LargestPointKey.UserKey, 0, sm.LargestPointKey.Kind())
|
||||
if sm.LargestPointKey.IsExclusiveSentinel() {
|
||||
largestPointKey = base.MakeRangeDeleteSentinelKey(sm.LargestPointKey.UserKey)
|
||||
}
|
||||
meta.ExtendPointKeyBounds(opts.Comparer.Compare, smallestPointKey, largestPointKey)
|
||||
}
|
||||
if err := meta.Validate(opts.Comparer.Compare, opts.Comparer.FormatKey); err != nil {
|
||||
return nil, err
|
||||
@@ -780,16 +797,21 @@ func overlapWithIterator(
|
||||
return computeOverlapWithSpans(*rangeDelIter)
|
||||
}
|
||||
|
||||
// ingestTargetLevel returns the target level for a file being ingested.
|
||||
// If suggestSplit is true, it accounts for ingest-time splitting as part of
|
||||
// its target level calculation, and if a split candidate is found, that file
|
||||
// is returned as the splitFile.
|
||||
func ingestTargetLevel(
|
||||
newIters tableNewIters,
|
||||
newRangeKeyIter keyspan.TableNewSpanIter,
|
||||
iterOps IterOptions,
|
||||
cmp Compare,
|
||||
comparer *Comparer,
|
||||
v *version,
|
||||
baseLevel int,
|
||||
compactions map[*compaction]struct{},
|
||||
meta *fileMetadata,
|
||||
) (int, error) {
|
||||
suggestSplit bool,
|
||||
) (targetLevel int, splitFile *fileMetadata, err error) {
|
||||
// Find the lowest level which does not have any files which overlap meta. We
|
||||
// search from L0 to L6 looking for whether there are any files in the level
|
||||
// which overlap meta. We want the "lowest" level (where lower means
|
||||
@@ -804,6 +826,14 @@ func ingestTargetLevel(
|
||||
// violate the sequence number invariant.
|
||||
// - no file boundary overlap with level i, since that will violate the
|
||||
// invariant that files do not overlap in levels i > 0.
|
||||
// - if there is only a file overlap at a given level, and no data overlap,
|
||||
// we can still slot a file at that level. We return the fileMetadata with
|
||||
// which we have file boundary overlap (must be only one file, as sstable
|
||||
// bounds are usually tight on user keys) and the caller is expected to split
|
||||
// that sstable into two virtual sstables, allowing this file to go into that
|
||||
// level. Note that if we have file boundary overlap with two files, which
|
||||
// should only happen on rare occasions, we treat it as data overlap and
|
||||
// don't use this optimization.
|
||||
//
|
||||
// The file boundary overlap check is simpler to conceptualize. Consider the
|
||||
// following example, in which the ingested file lies completely before or
|
||||
@@ -848,16 +878,14 @@ func ingestTargetLevel(
|
||||
// existing point that falls within the ingested table bounds as being "data
|
||||
// overlap".
|
||||
|
||||
targetLevel := 0
|
||||
|
||||
// This assertion implicitly checks that we have the current version of
|
||||
// the metadata.
|
||||
if v.L0Sublevels == nil {
|
||||
return 0, errors.AssertionFailedf("could not read L0 sublevels")
|
||||
return 0, nil, errors.AssertionFailedf("could not read L0 sublevels")
|
||||
}
|
||||
// Check for overlap over the keys of L0 by iterating over the sublevels.
|
||||
for subLevel := 0; subLevel < len(v.L0SublevelFiles); subLevel++ {
|
||||
iter := newLevelIter(iterOps, cmp, nil /* split */, newIters,
|
||||
iter := newLevelIter(iterOps, comparer, newIters,
|
||||
v.L0Sublevels.Levels[subLevel].Iter(), manifest.Level(0), internalIterOpts{})
|
||||
|
||||
var rangeDelIter keyspan.FragmentIterator
|
||||
@@ -867,7 +895,7 @@ func ingestTargetLevel(
|
||||
|
||||
levelIter := keyspan.LevelIter{}
|
||||
levelIter.Init(
|
||||
keyspan.SpanIterOptions{}, cmp, newRangeKeyIter,
|
||||
keyspan.SpanIterOptions{}, comparer.Compare, newRangeKeyIter,
|
||||
v.L0Sublevels.Levels[subLevel].Iter(), manifest.Level(0), manifest.KeyTypeRange,
|
||||
)
|
||||
|
||||
@@ -875,20 +903,20 @@ func ingestTargetLevel(
|
||||
smallest: meta.Smallest,
|
||||
largest: meta.Largest,
|
||||
}
|
||||
overlap := overlapWithIterator(iter, &rangeDelIter, &levelIter, kr, cmp)
|
||||
overlap := overlapWithIterator(iter, &rangeDelIter, &levelIter, kr, comparer.Compare)
|
||||
err := iter.Close() // Closes range del iter as well.
|
||||
err = firstError(err, levelIter.Close())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, nil, err
|
||||
}
|
||||
if overlap {
|
||||
return targetLevel, nil
|
||||
return targetLevel, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
level := baseLevel
|
||||
for ; level < numLevels; level++ {
|
||||
levelIter := newLevelIter(iterOps, cmp, nil /* split */, newIters,
|
||||
levelIter := newLevelIter(iterOps, comparer, newIters,
|
||||
v.Levels[level].Iter(), manifest.Level(level), internalIterOpts{})
|
||||
var rangeDelIter keyspan.FragmentIterator
|
||||
// Pass in a non-nil pointer to rangeDelIter so that levelIter.findFileGE
|
||||
@@ -897,7 +925,7 @@ func ingestTargetLevel(
|
||||
|
||||
rkeyLevelIter := &keyspan.LevelIter{}
|
||||
rkeyLevelIter.Init(
|
||||
keyspan.SpanIterOptions{}, cmp, newRangeKeyIter,
|
||||
keyspan.SpanIterOptions{}, comparer.Compare, newRangeKeyIter,
|
||||
v.Levels[level].Iter(), manifest.Level(level), manifest.KeyTypeRange,
|
||||
)
|
||||
|
||||
@@ -905,46 +933,68 @@ func ingestTargetLevel(
|
||||
smallest: meta.Smallest,
|
||||
largest: meta.Largest,
|
||||
}
|
||||
overlap := overlapWithIterator(levelIter, &rangeDelIter, rkeyLevelIter, kr, cmp)
|
||||
overlap := overlapWithIterator(levelIter, &rangeDelIter, rkeyLevelIter, kr, comparer.Compare)
|
||||
err := levelIter.Close() // Closes range del iter as well.
|
||||
err = firstError(err, rkeyLevelIter.Close())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, nil, err
|
||||
}
|
||||
if overlap {
|
||||
return targetLevel, nil
|
||||
return targetLevel, splitFile, nil
|
||||
}
|
||||
|
||||
// Check boundary overlap.
|
||||
boundaryOverlaps := v.Overlaps(level, cmp, meta.Smallest.UserKey,
|
||||
var candidateSplitFile *fileMetadata
|
||||
boundaryOverlaps := v.Overlaps(level, comparer.Compare, meta.Smallest.UserKey,
|
||||
meta.Largest.UserKey, meta.Largest.IsExclusiveSentinel())
|
||||
if !boundaryOverlaps.Empty() {
|
||||
continue
|
||||
// We are already guaranteed to not have any data overlaps with files
|
||||
// in boundaryOverlaps, otherwise we'd have returned in the above if
|
||||
// statements. Use this, plus boundaryOverlaps.Len() == 1 to detect for
|
||||
// the case where we can slot this file into the current level despite
|
||||
// a boundary overlap, by splitting one existing file into two virtual
|
||||
// sstables.
|
||||
if suggestSplit && boundaryOverlaps.Len() == 1 {
|
||||
iter := boundaryOverlaps.Iter()
|
||||
candidateSplitFile = iter.First()
|
||||
} else {
|
||||
// We either don't want to suggest ingest-time splits (i.e.
|
||||
// !suggestSplit), or we boundary-overlapped with more than one file.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Check boundary overlap with any ongoing compactions.
|
||||
// Check boundary overlap with any ongoing compactions. We consider an
|
||||
// overlapping compaction that's writing files to an output level as
|
||||
// equivalent to boundary overlap with files in that output level.
|
||||
//
|
||||
// We cannot check for data overlap with the new SSTs compaction will
|
||||
// produce since compaction hasn't been done yet. However, there's no need
|
||||
// to check since all keys in them will either be from c.startLevel or
|
||||
// c.outputLevel, both levels having their data overlap already tested
|
||||
// negative (else we'd have returned earlier).
|
||||
// We cannot check for data overlap with the new SSTs compaction will produce
|
||||
// since compaction hasn't been done yet. However, there's no need to check
|
||||
// since all keys in them will be from levels in [c.startLevel,
|
||||
// c.outputLevel], and all those levels have already had their data overlap
|
||||
// tested negative (else we'd have returned earlier).
|
||||
//
|
||||
// An alternative approach would be to cancel these compactions and proceed
|
||||
// with an ingest-time split on this level if necessary. However, compaction
|
||||
// cancellation can result in significant wasted effort and is best avoided
|
||||
// unless necessary.
|
||||
overlaps := false
|
||||
for c := range compactions {
|
||||
if c.outputLevel == nil || level != c.outputLevel.level {
|
||||
continue
|
||||
}
|
||||
if cmp(meta.Smallest.UserKey, c.largest.UserKey) <= 0 &&
|
||||
cmp(meta.Largest.UserKey, c.smallest.UserKey) >= 0 {
|
||||
if comparer.Compare(meta.Smallest.UserKey, c.largest.UserKey) <= 0 &&
|
||||
comparer.Compare(meta.Largest.UserKey, c.smallest.UserKey) >= 0 {
|
||||
overlaps = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !overlaps {
|
||||
targetLevel = level
|
||||
splitFile = candidateSplitFile
|
||||
}
|
||||
}
|
||||
return targetLevel, nil
|
||||
return targetLevel, splitFile, nil
|
||||
}
|
||||
|
||||
// Ingest ingests a set of sstables into the DB. Ingestion of the files is
|
||||
@@ -1126,7 +1176,7 @@ func (d *DB) newIngestedFlushableEntry(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f := newIngestedFlushable(meta, d.cmp, d.split, d.newIters, d.tableNewRangeKeyIter)
|
||||
f := newIngestedFlushable(meta, d.opts.Comparer, d.newIters, d.tableNewRangeKeyIter)
|
||||
|
||||
// NB: The logNum/seqNum are the WAL number which we're writing this entry
|
||||
// to and the sequence number within the WAL which we'll write this entry
|
||||
@@ -1226,7 +1276,7 @@ func (d *DB) ingest(
|
||||
if len(shared) > 0 && d.opts.Experimental.RemoteStorage == nil {
|
||||
panic("cannot ingest shared sstables with nil SharedStorage")
|
||||
}
|
||||
if (exciseSpan.Valid() || len(shared) > 0 || len(external) > 0) && d.FormatMajorVersion() < ExperimentalFormatVirtualSSTables {
|
||||
if (exciseSpan.Valid() || len(shared) > 0 || len(external) > 0) && d.FormatMajorVersion() < FormatVirtualSSTables {
|
||||
return IngestOperationStats{}, errors.New("pebble: format major version too old for excise, shared or external sstable ingestion")
|
||||
}
|
||||
// Allocate file numbers for all of the files being ingested and mark them as
|
||||
@@ -1558,7 +1608,7 @@ func (d *DB) excise(
|
||||
var iter internalIterator
|
||||
var rangeDelIter keyspan.FragmentIterator
|
||||
var rangeKeyIter keyspan.FragmentIterator
|
||||
backingTableCreated := false
|
||||
needsBacking := false
|
||||
// Create a file to the left of the excise span, if necessary.
|
||||
// The bounds of this file will be [m.Smallest, lastKeyBefore(exciseSpan.Start)].
|
||||
//
|
||||
@@ -1672,14 +1722,20 @@ func (d *DB) excise(
|
||||
leftFile.ValidateVirtual(m)
|
||||
d.checkVirtualBounds(leftFile)
|
||||
ve.NewFiles = append(ve.NewFiles, newFileEntry{Level: level, Meta: leftFile})
|
||||
ve.CreatedBackingTables = append(ve.CreatedBackingTables, leftFile.FileBacking)
|
||||
backingTableCreated = true
|
||||
needsBacking = true
|
||||
numCreatedFiles++
|
||||
}
|
||||
}
|
||||
// Create a file to the right, if necessary.
|
||||
if exciseSpan.Contains(d.cmp, m.Largest) {
|
||||
// No key exists to the right of the excise span in this file.
|
||||
if needsBacking && !m.Virtual {
|
||||
// If m is virtual, then its file backing is already known to the manifest.
|
||||
// We don't need to create another file backing. Note that there must be
|
||||
// only one CreatedBackingTables entry per backing sstable. This is
|
||||
// indicated by the VersionEdit.CreatedBackingTables invariant.
|
||||
ve.CreatedBackingTables = append(ve.CreatedBackingTables, m.FileBacking)
|
||||
}
|
||||
return ve.NewFiles[len(ve.NewFiles)-numCreatedFiles:], nil
|
||||
}
|
||||
// Create a new file, rightFile, between [firstKeyAfter(exciseSpan.End), m.Largest].
|
||||
@@ -1734,7 +1790,8 @@ func (d *DB) excise(
|
||||
smallestPointKey.UserKey = firstRangeDel
|
||||
rightFile.ExtendPointKeyBounds(d.cmp, smallestPointKey, largestPointKey)
|
||||
}
|
||||
} else if m.HasRangeKeys && !exciseSpan.Contains(d.cmp, m.LargestRangeKey) {
|
||||
}
|
||||
if m.HasRangeKeys && !exciseSpan.Contains(d.cmp, m.LargestRangeKey) {
|
||||
// This file will contain range keys.
|
||||
largestRangeKey := m.LargestRangeKey
|
||||
if rangeKeyIter == nil {
|
||||
@@ -1781,13 +1838,18 @@ func (d *DB) excise(
|
||||
rightFile.ValidateVirtual(m)
|
||||
d.checkVirtualBounds(rightFile)
|
||||
ve.NewFiles = append(ve.NewFiles, newFileEntry{Level: level, Meta: rightFile})
|
||||
if !backingTableCreated {
|
||||
ve.CreatedBackingTables = append(ve.CreatedBackingTables, rightFile.FileBacking)
|
||||
backingTableCreated = true
|
||||
}
|
||||
needsBacking = true
|
||||
numCreatedFiles++
|
||||
}
|
||||
|
||||
if needsBacking && !m.Virtual {
|
||||
// If m is virtual, then its file backing is already known to the manifest.
|
||||
// We don't need to create another file backing. Note that there must be
|
||||
// only one CreatedBackingTables entry per backing sstable. This is
|
||||
// indicated by the VersionEdit.CreatedBackingTables invariant.
|
||||
ve.CreatedBackingTables = append(ve.CreatedBackingTables, m.FileBacking)
|
||||
}
|
||||
|
||||
if err := rightFile.Validate(d.cmp, d.opts.Comparer.FormatKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1798,12 +1860,129 @@ type ingestTargetLevelFunc func(
|
||||
newIters tableNewIters,
|
||||
newRangeKeyIter keyspan.TableNewSpanIter,
|
||||
iterOps IterOptions,
|
||||
cmp Compare,
|
||||
comparer *Comparer,
|
||||
v *version,
|
||||
baseLevel int,
|
||||
compactions map[*compaction]struct{},
|
||||
meta *fileMetadata,
|
||||
) (int, error)
|
||||
suggestSplit bool,
|
||||
) (int, *fileMetadata, error)
|
||||
|
||||
type ingestSplitFile struct {
|
||||
// ingestFile is the file being ingested.
|
||||
ingestFile *fileMetadata
|
||||
// splitFile is the file that needs to be split to allow ingestFile to slot
|
||||
// into `level` level.
|
||||
splitFile *fileMetadata
|
||||
// The level where ingestFile will go (and where splitFile already is).
|
||||
level int
|
||||
}
|
||||
|
||||
// ingestSplit splits files specified in `files` and updates ve in-place to
|
||||
// account for existing files getting split into two virtual sstables. The map
|
||||
// `replacedFiles` contains an in-progress map of all files that have been
|
||||
// replaced with new virtual sstables in this version edit so far, which is also
|
||||
// updated in-place.
|
||||
//
|
||||
// d.mu as well as the manifest lock must be held when calling this method.
|
||||
func (d *DB) ingestSplit(
|
||||
ve *versionEdit,
|
||||
updateMetrics func(*fileMetadata, int, []newFileEntry),
|
||||
files []ingestSplitFile,
|
||||
replacedFiles map[base.FileNum][]newFileEntry,
|
||||
) error {
|
||||
for _, s := range files {
|
||||
// replacedFiles can be thought of as a tree, where we start iterating with
|
||||
// s.splitFile and run its fileNum through replacedFiles, then find which of
|
||||
// the replaced files overlaps with s.ingestFile, which becomes the new
|
||||
// splitFile, then we check splitFile's replacements in replacedFiles again
|
||||
// for overlap with s.ingestFile, and so on until we either can't find the
|
||||
// current splitFile in replacedFiles (i.e. that's the file that now needs to
|
||||
// be split), or we don't find a file that overlaps with s.ingestFile, which
|
||||
// means a prior ingest split already produced enough room for s.ingestFile
|
||||
// to go into this level without necessitating another ingest split.
|
||||
splitFile := s.splitFile
|
||||
for splitFile != nil {
|
||||
replaced, ok := replacedFiles[splitFile.FileNum]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
updatedSplitFile := false
|
||||
for i := range replaced {
|
||||
if replaced[i].Meta.Overlaps(d.cmp, s.ingestFile.Smallest.UserKey, s.ingestFile.Largest.UserKey, s.ingestFile.Largest.IsExclusiveSentinel()) {
|
||||
if updatedSplitFile {
|
||||
// This should never happen because the earlier ingestTargetLevel
|
||||
// function only finds split file candidates that are guaranteed to
|
||||
// have no data overlap, only boundary overlap. See the comments
|
||||
// in that method to see the definitions of data vs boundary
|
||||
// overlap. That, plus the fact that files in `replaced` are
|
||||
// guaranteed to have file bounds that are tight on user keys
|
||||
// (as that's what `d.excise` produces), means that the only case
|
||||
// where we overlap with two or more files in `replaced` is if we
|
||||
// actually had data overlap all along, or if the ingestion files
|
||||
// were overlapping, either of which is an invariant violation.
|
||||
panic("updated with two files in ingestSplit")
|
||||
}
|
||||
splitFile = replaced[i].Meta
|
||||
updatedSplitFile = true
|
||||
}
|
||||
}
|
||||
if !updatedSplitFile {
|
||||
// None of the replaced files overlapped with the file being ingested.
|
||||
// This can happen if we've already excised a span overlapping with
|
||||
// this file, or if we have consecutive ingested files that can slide
|
||||
// within the same gap between keys in an existing file. For instance,
|
||||
// if an existing file has keys a and g and we're ingesting b-c, d-e,
|
||||
// the first loop iteration will split the existing file into one that
|
||||
// ends in a and another that starts at g, and the second iteration will
|
||||
// fall into this case and require no splitting.
|
||||
//
|
||||
// No splitting necessary.
|
||||
splitFile = nil
|
||||
}
|
||||
}
|
||||
if splitFile == nil {
|
||||
continue
|
||||
}
|
||||
// NB: excise operates on [start, end). We're splitting at [start, end]
|
||||
// (assuming !s.ingestFile.Largest.IsExclusiveSentinel()). The conflation
|
||||
// of exclusive vs inclusive end bounds should not make a difference here
|
||||
// as we're guaranteed to not have any data overlap between splitFile and
|
||||
// s.ingestFile, so panic if we do see a newly added file with an endKey
|
||||
// equalling s.ingestFile.Largest, and !s.ingestFile.Largest.IsExclusiveSentinel()
|
||||
added, err := d.excise(KeyRange{Start: s.ingestFile.Smallest.UserKey, End: s.ingestFile.Largest.UserKey}, splitFile, ve, s.level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := ve.DeletedFiles[deletedFileEntry{
|
||||
Level: s.level,
|
||||
FileNum: splitFile.FileNum,
|
||||
}]; !ok {
|
||||
panic("did not split file that was expected to be split")
|
||||
}
|
||||
replacedFiles[splitFile.FileNum] = added
|
||||
for i := range added {
|
||||
if s.ingestFile.Overlaps(d.cmp, added[i].Meta.Smallest.UserKey, added[i].Meta.Largest.UserKey, added[i].Meta.Largest.IsExclusiveSentinel()) {
|
||||
panic("ingest-time split produced a file that overlaps with ingested file")
|
||||
}
|
||||
}
|
||||
updateMetrics(splitFile, s.level, added)
|
||||
}
|
||||
// Flatten the version edit by removing any entries from ve.NewFiles that
|
||||
// are also in ve.DeletedFiles.
|
||||
newNewFiles := ve.NewFiles[:0]
|
||||
for i := range ve.NewFiles {
|
||||
fn := ve.NewFiles[i].Meta.FileNum
|
||||
deEntry := deletedFileEntry{Level: ve.NewFiles[i].Level, FileNum: fn}
|
||||
if _, ok := ve.DeletedFiles[deEntry]; ok {
|
||||
delete(ve.DeletedFiles, deEntry)
|
||||
} else {
|
||||
newNewFiles = append(newNewFiles, ve.NewFiles[i])
|
||||
}
|
||||
}
|
||||
ve.NewFiles = newNewFiles
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DB) ingestApply(
|
||||
jobID int,
|
||||
@@ -1818,7 +1997,7 @@ func (d *DB) ingestApply(
|
||||
ve := &versionEdit{
|
||||
NewFiles: make([]newFileEntry, lr.fileCount),
|
||||
}
|
||||
if exciseSpan.Valid() {
|
||||
if exciseSpan.Valid() || (d.opts.Experimental.IngestSplit != nil && d.opts.Experimental.IngestSplit()) {
|
||||
ve.DeletedFiles = map[manifest.DeletedFileEntry]*manifest.FileMetadata{}
|
||||
}
|
||||
metrics := make(map[int]*LevelMetrics)
|
||||
@@ -1843,9 +2022,17 @@ func (d *DB) ingestApply(
|
||||
}
|
||||
}
|
||||
|
||||
shouldIngestSplit := d.opts.Experimental.IngestSplit != nil &&
|
||||
d.opts.Experimental.IngestSplit() && d.FormatMajorVersion() >= FormatVirtualSSTables
|
||||
current := d.mu.versions.currentVersion()
|
||||
baseLevel := d.mu.versions.picker.getBaseLevel()
|
||||
iterOps := IterOptions{logger: d.opts.Logger}
|
||||
// filesToSplit is a list where each element is a pair consisting of a file
|
||||
// being ingested and a file being split to make room for an ingestion into
|
||||
// that level. Each ingested file will appear at most once in this list. It
|
||||
// is possible for split files to appear twice in this list.
|
||||
filesToSplit := make([]ingestSplitFile, 0)
|
||||
checkCompactions := false
|
||||
for i := 0; i < lr.fileCount; i++ {
|
||||
// Determine the lowest level in the LSM for which the sstable doesn't
|
||||
// overlap any existing files in the level.
|
||||
@@ -1878,6 +2065,7 @@ func (d *DB) ingestApply(
|
||||
if externalFile {
|
||||
ve.CreatedBackingTables = append(ve.CreatedBackingTables, m.FileBacking)
|
||||
}
|
||||
var splitFile *fileMetadata
|
||||
if exciseSpan.Valid() && exciseSpan.Contains(d.cmp, m.Smallest) && exciseSpan.Contains(d.cmp, m.Largest) {
|
||||
// This file fits perfectly within the excise span. We can slot it at
|
||||
// L6, or sharedLevelsStart - 1 if we have shared files.
|
||||
@@ -1890,7 +2078,31 @@ func (d *DB) ingestApply(
|
||||
f.Level = 6
|
||||
}
|
||||
} else {
|
||||
f.Level, err = findTargetLevel(d.newIters, d.tableNewRangeKeyIter, iterOps, d.cmp, current, baseLevel, d.mu.compact.inProgress, m)
|
||||
// TODO(bilal): findTargetLevel does disk IO (reading files for data
|
||||
// overlap) even though we're holding onto d.mu. Consider unlocking
|
||||
// d.mu while we do this. We already hold versions.logLock so we should
|
||||
// not see any version applications while we're at this. The one
|
||||
// complication here would be pulling out the mu.compact.inProgress
|
||||
// check from findTargetLevel, as that requires d.mu to be held.
|
||||
f.Level, splitFile, err = findTargetLevel(
|
||||
d.newIters, d.tableNewRangeKeyIter, iterOps, d.opts.Comparer, current, baseLevel, d.mu.compact.inProgress, m, shouldIngestSplit)
|
||||
}
|
||||
|
||||
if splitFile != nil {
|
||||
if invariants.Enabled {
|
||||
if lf := current.Levels[f.Level].Find(d.cmp, splitFile); lf == nil {
|
||||
panic("splitFile returned is not in level it should be")
|
||||
}
|
||||
}
|
||||
// We take advantage of the fact that we won't drop the db mutex
|
||||
// between now and the call to logAndApply. So, no files should
|
||||
// get added to a new in-progress compaction at this point. We can
|
||||
// avoid having to iterate on in-progress compactions to cancel them
|
||||
// if none of the files being split have a compacting state.
|
||||
if splitFile.IsCompacting() {
|
||||
checkCompactions = true
|
||||
}
|
||||
filesToSplit = append(filesToSplit, ingestSplitFile{ingestFile: m, splitFile: splitFile, level: f.Level})
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
@@ -1908,6 +2120,26 @@ func (d *DB) ingestApply(
|
||||
levelMetrics.BytesIngested += m.Size
|
||||
levelMetrics.TablesIngested++
|
||||
}
|
||||
// replacedFiles maps files excised due to exciseSpan (or splitFiles returned
|
||||
// by ingestTargetLevel), to files that were created to replace it. This map
|
||||
// is used to resolve references to split files in filesToSplit, as it is
|
||||
// possible for a file that we want to split to no longer exist or have a
|
||||
// newer fileMetadata due to a split induced by another ingestion file, or an
|
||||
// excise.
|
||||
replacedFiles := make(map[base.FileNum][]newFileEntry)
|
||||
updateLevelMetricsOnExcise := func(m *fileMetadata, level int, added []newFileEntry) {
|
||||
levelMetrics := metrics[level]
|
||||
if levelMetrics == nil {
|
||||
levelMetrics = &LevelMetrics{}
|
||||
metrics[level] = levelMetrics
|
||||
}
|
||||
levelMetrics.NumFiles--
|
||||
levelMetrics.Size -= int64(m.Size)
|
||||
for i := range added {
|
||||
levelMetrics.NumFiles++
|
||||
levelMetrics.Size += int64(added[i].Meta.Size)
|
||||
}
|
||||
}
|
||||
if exciseSpan.Valid() {
|
||||
// Iterate through all levels and find files that intersect with exciseSpan.
|
||||
//
|
||||
@@ -1929,7 +2161,7 @@ func (d *DB) ingestApply(
|
||||
iter := overlaps.Iter()
|
||||
|
||||
for m := iter.First(); m != nil; m = iter.Next() {
|
||||
excised, err := d.excise(exciseSpan, m, ve, level)
|
||||
newFiles, err := d.excise(exciseSpan, m, ve, level)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1941,19 +2173,19 @@ func (d *DB) ingestApply(
|
||||
// We did not excise this file.
|
||||
continue
|
||||
}
|
||||
levelMetrics := metrics[level]
|
||||
if levelMetrics == nil {
|
||||
levelMetrics = &LevelMetrics{}
|
||||
metrics[level] = levelMetrics
|
||||
}
|
||||
levelMetrics.NumFiles--
|
||||
levelMetrics.Size -= int64(m.Size)
|
||||
for i := range excised {
|
||||
levelMetrics.NumFiles++
|
||||
levelMetrics.Size += int64(excised[i].Meta.Size)
|
||||
}
|
||||
replacedFiles[m.FileNum] = newFiles
|
||||
updateLevelMetricsOnExcise(m, level, newFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(filesToSplit) > 0 {
|
||||
// For the same reasons as the above call to excise, we hold the db mutex
|
||||
// while calling this method.
|
||||
if err := d.ingestSplit(ve, updateLevelMetricsOnExcise, filesToSplit, replacedFiles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(filesToSplit) > 0 || exciseSpan.Valid() {
|
||||
for c := range d.mu.compact.inProgress {
|
||||
if c.versionEditApplied {
|
||||
continue
|
||||
@@ -1968,22 +2200,41 @@ func (d *DB) ingestApply(
|
||||
if exciseSpan.OverlapsInternalKeyRange(d.cmp, c.smallest, c.largest) {
|
||||
c.cancel.Store(true)
|
||||
}
|
||||
// Check if this compaction's inputs have been replaced due to an
|
||||
// ingest-time split. In that case, cancel the compaction as a newly picked
|
||||
// compaction would need to include any new files that slid in between
|
||||
// previously-existing files. Note that we cancel any compaction that has a
|
||||
// file that was ingest-split as an input, even if it started before this
|
||||
// ingestion.
|
||||
if checkCompactions {
|
||||
for i := range c.inputs {
|
||||
iter := c.inputs[i].files.Iter()
|
||||
for f := iter.First(); f != nil; f = iter.Next() {
|
||||
if _, ok := replacedFiles[f.FileNum]; ok {
|
||||
c.cancel.Store(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for any EventuallyFileOnlySnapshots that could be watching for
|
||||
// an excise on this span.
|
||||
for s := d.mu.snapshots.root.next; s != &d.mu.snapshots.root; s = s.next {
|
||||
if s.efos == nil {
|
||||
continue
|
||||
}
|
||||
efos := s.efos
|
||||
// TODO(bilal): We can make this faster by taking advantage of the sorted
|
||||
// nature of protectedRanges to do a sort.Search, or even maintaining a
|
||||
// global list of all protected ranges instead of having to peer into every
|
||||
// snapshot.
|
||||
for i := range efos.protectedRanges {
|
||||
if efos.protectedRanges[i].OverlapsKeyRange(d.cmp, exciseSpan) {
|
||||
efos.excised.Store(true)
|
||||
break
|
||||
if exciseSpan.Valid() {
|
||||
for s := d.mu.snapshots.root.next; s != &d.mu.snapshots.root; s = s.next {
|
||||
if s.efos == nil {
|
||||
continue
|
||||
}
|
||||
efos := s.efos
|
||||
// TODO(bilal): We can make this faster by taking advantage of the sorted
|
||||
// nature of protectedRanges to do a sort.Search, or even maintaining a
|
||||
// global list of all protected ranges instead of having to peer into every
|
||||
// snapshot.
|
||||
for i := range efos.protectedRanges {
|
||||
if efos.protectedRanges[i].OverlapsKeyRange(d.cmp, exciseSpan) {
|
||||
efos.excised.Store(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2004,28 +2255,31 @@ func (d *DB) ingestApply(
|
||||
// The ingestion may have pushed a level over the threshold for compaction,
|
||||
// so check to see if one is necessary and schedule it.
|
||||
d.maybeScheduleCompaction()
|
||||
d.maybeValidateSSTablesLocked(ve.NewFiles)
|
||||
var toValidate []manifest.NewFileEntry
|
||||
dedup := make(map[base.DiskFileNum]struct{})
|
||||
for _, entry := range ve.NewFiles {
|
||||
if _, ok := dedup[entry.Meta.FileBacking.DiskFileNum]; !ok {
|
||||
toValidate = append(toValidate, entry)
|
||||
dedup[entry.Meta.FileBacking.DiskFileNum] = struct{}{}
|
||||
}
|
||||
}
|
||||
d.maybeValidateSSTablesLocked(toValidate)
|
||||
return ve, nil
|
||||
}
|
||||
|
||||
// maybeValidateSSTablesLocked adds the slice of newFileEntrys to the pending
|
||||
// queue of files to be validated, when the feature is enabled.
|
||||
// DB.mu must be locked when calling.
|
||||
//
|
||||
// TODO(bananabrick): Make sure that the ingestion step only passes in the
|
||||
// physical sstables for validation here.
|
||||
// Note that if two entries with the same backing file are added twice, then the
|
||||
// block checksums for the backing file will be validated twice.
|
||||
//
|
||||
// DB.mu must be locked when calling.
|
||||
func (d *DB) maybeValidateSSTablesLocked(newFiles []newFileEntry) {
|
||||
// Only add to the validation queue when the feature is enabled.
|
||||
if !d.opts.Experimental.ValidateOnIngest {
|
||||
return
|
||||
}
|
||||
|
||||
for _, f := range newFiles {
|
||||
if f.Meta.Virtual {
|
||||
panic("pebble: invalid call to maybeValidateSSTablesLocked")
|
||||
}
|
||||
}
|
||||
|
||||
d.mu.tableValidation.pending = append(d.mu.tableValidation.pending, newFiles...)
|
||||
if d.shouldValidateSSTablesLocked() {
|
||||
go d.validateSSTables()
|
||||
@@ -2085,10 +2339,19 @@ func (d *DB) validateSSTables() {
|
||||
}
|
||||
}
|
||||
|
||||
err := d.tableCache.withReader(
|
||||
f.Meta.PhysicalMeta(), func(r *sstable.Reader) error {
|
||||
return r.ValidateBlockChecksums()
|
||||
})
|
||||
var err error
|
||||
if f.Meta.Virtual {
|
||||
err = d.tableCache.withVirtualReader(
|
||||
f.Meta.VirtualMeta(), func(v sstable.VirtualReader) error {
|
||||
return v.ValidateBlockChecksumsOnBacking()
|
||||
})
|
||||
} else {
|
||||
err = d.tableCache.withReader(
|
||||
f.Meta.PhysicalMeta(), func(r *sstable.Reader) error {
|
||||
return r.ValidateBlockChecksums()
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// TODO(travers): Hook into the corruption reporting pipeline, once
|
||||
// available. See pebble#1192.
|
||||
|
||||
10
vendor/github.com/cockroachdb/pebble/internal/base/filenames.go
generated
vendored
10
vendor/github.com/cockroachdb/pebble/internal/base/filenames.go
generated
vendored
@@ -20,6 +20,11 @@ type FileNum uint64
|
||||
// String returns a string representation of the file number.
|
||||
func (fn FileNum) String() string { return fmt.Sprintf("%06d", fn) }
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (fn FileNum) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("%06d", redact.SafeUint(fn))
|
||||
}
|
||||
|
||||
// DiskFileNum converts a FileNum to a DiskFileNum. DiskFileNum should only be
|
||||
// called if the caller can ensure that the FileNum belongs to a physical file
|
||||
// on disk. These could be manifests, log files, physical sstables on disk, the
|
||||
@@ -40,6 +45,11 @@ type DiskFileNum struct {
|
||||
|
||||
func (dfn DiskFileNum) String() string { return dfn.fn.String() }
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (dfn DiskFileNum) SafeFormat(w redact.SafePrinter, verb rune) {
|
||||
dfn.fn.SafeFormat(w, verb)
|
||||
}
|
||||
|
||||
// FileNum converts a DiskFileNum to a FileNum. This conversion is always valid.
|
||||
func (dfn DiskFileNum) FileNum() FileNum {
|
||||
return dfn.fn
|
||||
|
||||
12
vendor/github.com/cockroachdb/pebble/internal/base/internal.go
generated
vendored
12
vendor/github.com/cockroachdb/pebble/internal/base/internal.go
generated
vendored
@@ -9,6 +9,8 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/redact"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -161,6 +163,11 @@ func (k InternalKeyKind) String() string {
|
||||
return fmt.Sprintf("UNKNOWN:%d", k)
|
||||
}
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (k InternalKeyKind) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Print(redact.SafeString(k.String()))
|
||||
}
|
||||
|
||||
// InternalKey is a key used for the in-memory and on-disk partial DBs that
|
||||
// make up a pebble DB.
|
||||
//
|
||||
@@ -374,6 +381,11 @@ func (k InternalKey) SeqNum() uint64 {
|
||||
return k.Trailer >> 8
|
||||
}
|
||||
|
||||
// SeqNumFromTrailer returns the sequence number component of a trailer.
|
||||
func SeqNumFromTrailer(t uint64) uint64 {
|
||||
return t >> 8
|
||||
}
|
||||
|
||||
// Visible returns true if the key is visible at the specified snapshot
|
||||
// sequence number.
|
||||
func (k InternalKey) Visible(snapshot, batchSnapshot uint64) bool {
|
||||
|
||||
162
vendor/github.com/cockroachdb/pebble/internal/invalidating/iter.go
generated
vendored
Normal file
162
vendor/github.com/cockroachdb/pebble/internal/invalidating/iter.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2023 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 invalidating
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/fastrand"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
)
|
||||
|
||||
// MaybeWrapIfInvariants wraps some iterators with an invalidating iterator.
|
||||
// MaybeWrapIfInvariants does nothing in non-invariant builds.
|
||||
func MaybeWrapIfInvariants(iter base.InternalIterator) base.InternalIterator {
|
||||
if invariants.Enabled {
|
||||
if fastrand.Uint32n(10) == 1 {
|
||||
return NewIter(iter)
|
||||
}
|
||||
}
|
||||
return iter
|
||||
}
|
||||
|
||||
// iter tests unsafe key/value slice reuse by modifying the last
|
||||
// returned key/value to all 1s.
|
||||
type iter struct {
|
||||
iter base.InternalIterator
|
||||
lastKey *base.InternalKey
|
||||
lastValue base.LazyValue
|
||||
ignoreKinds [base.InternalKeyKindMax + 1]bool
|
||||
err error
|
||||
}
|
||||
|
||||
// Option configures the behavior of an invalidating iterator.
|
||||
type Option interface {
|
||||
apply(*iter)
|
||||
}
|
||||
|
||||
type funcOpt func(*iter)
|
||||
|
||||
func (f funcOpt) apply(i *iter) { f(i) }
|
||||
|
||||
// IgnoreKinds constructs an Option that configures an invalidating iterator to
|
||||
// skip trashing k/v pairs with the provided key kinds. Some iterators provided
|
||||
// key stability guarantees for specific key kinds.
|
||||
func IgnoreKinds(kinds ...base.InternalKeyKind) Option {
|
||||
return funcOpt(func(i *iter) {
|
||||
for _, kind := range kinds {
|
||||
i.ignoreKinds[kind] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// NewIter constructs a new invalidating iterator that wraps the provided
|
||||
// iterator, trashing buffers for previously returned keys.
|
||||
func NewIter(originalIterator base.InternalIterator, opts ...Option) base.InternalIterator {
|
||||
i := &iter{iter: originalIterator}
|
||||
for _, opt := range opts {
|
||||
opt.apply(i)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *iter) update(
|
||||
key *base.InternalKey, value base.LazyValue,
|
||||
) (*base.InternalKey, base.LazyValue) {
|
||||
i.trashLastKV()
|
||||
if key == nil {
|
||||
i.lastKey = nil
|
||||
i.lastValue = base.LazyValue{}
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
|
||||
i.lastKey = &base.InternalKey{}
|
||||
*i.lastKey = key.Clone()
|
||||
i.lastValue = base.LazyValue{
|
||||
ValueOrHandle: append(make([]byte, 0, len(value.ValueOrHandle)), value.ValueOrHandle...),
|
||||
}
|
||||
if value.Fetcher != nil {
|
||||
fetcher := new(base.LazyFetcher)
|
||||
*fetcher = *value.Fetcher
|
||||
i.lastValue.Fetcher = fetcher
|
||||
}
|
||||
return i.lastKey, i.lastValue
|
||||
}
|
||||
|
||||
func (i *iter) trashLastKV() {
|
||||
if i.lastKey == nil {
|
||||
return
|
||||
}
|
||||
if i.ignoreKinds[i.lastKey.Kind()] {
|
||||
return
|
||||
}
|
||||
|
||||
if i.lastKey != nil {
|
||||
for j := range i.lastKey.UserKey {
|
||||
i.lastKey.UserKey[j] = 0xff
|
||||
}
|
||||
i.lastKey.Trailer = 0xffffffffffffffff
|
||||
}
|
||||
for j := range i.lastValue.ValueOrHandle {
|
||||
i.lastValue.ValueOrHandle[j] = 0xff
|
||||
}
|
||||
if i.lastValue.Fetcher != nil {
|
||||
// Not all the LazyFetcher fields are visible, so we zero out the last
|
||||
// value's Fetcher struct entirely.
|
||||
*i.lastValue.Fetcher = base.LazyFetcher{}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *iter) SeekGE(key []byte, flags base.SeekGEFlags) (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.SeekGE(key, flags))
|
||||
}
|
||||
|
||||
func (i *iter) SeekPrefixGE(
|
||||
prefix, key []byte, flags base.SeekGEFlags,
|
||||
) (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.SeekPrefixGE(prefix, key, flags))
|
||||
}
|
||||
|
||||
func (i *iter) SeekLT(key []byte, flags base.SeekLTFlags) (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.SeekLT(key, flags))
|
||||
}
|
||||
|
||||
func (i *iter) First() (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.First())
|
||||
}
|
||||
|
||||
func (i *iter) Last() (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.Last())
|
||||
}
|
||||
|
||||
func (i *iter) Next() (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.Next())
|
||||
}
|
||||
|
||||
func (i *iter) Prev() (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.Prev())
|
||||
}
|
||||
|
||||
func (i *iter) NextPrefix(succKey []byte) (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.NextPrefix(succKey))
|
||||
}
|
||||
|
||||
func (i *iter) Error() error {
|
||||
if err := i.iter.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
return i.err
|
||||
}
|
||||
|
||||
func (i *iter) Close() error {
|
||||
return i.iter.Close()
|
||||
}
|
||||
|
||||
func (i *iter) SetBounds(lower, upper []byte) {
|
||||
i.iter.SetBounds(lower, upper)
|
||||
}
|
||||
|
||||
func (i *iter) String() string {
|
||||
return i.iter.String()
|
||||
}
|
||||
38
vendor/github.com/cockroachdb/pebble/internal/keyspan/defragment.go
generated
vendored
38
vendor/github.com/cockroachdb/pebble/internal/keyspan/defragment.go
generated
vendored
@@ -228,6 +228,14 @@ func (i *DefragmentingIter) SeekGE(key []byte) *Span {
|
||||
// fragments before this span. Defragment backward to find the start of the
|
||||
// defragmented span.
|
||||
i.defragmentBackward()
|
||||
|
||||
// Defragmenting backward may have stopped because it encountered an error.
|
||||
// If so, we must not continue so that i.iter.Error() (and thus i.Error())
|
||||
// yields the error.
|
||||
if i.iterSpan == nil && i.iter.Error() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.iterPos == iterPosPrev {
|
||||
// Next once back onto the span.
|
||||
i.iterSpan = i.iter.Next()
|
||||
@@ -259,6 +267,14 @@ func (i *DefragmentingIter) SeekLT(key []byte) *Span {
|
||||
// fragments after this span. Defragment forward to find the end of the
|
||||
// defragmented span.
|
||||
i.defragmentForward()
|
||||
|
||||
// Defragmenting forward may have stopped because it encountered an error.
|
||||
// If so, we must not continue so that i.iter.Error() (and thus i.Error())
|
||||
// yields the error.
|
||||
if i.iterSpan == nil && i.iter.Error() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.iterPos == iterPosNext {
|
||||
// Prev once back onto the span.
|
||||
i.iterSpan = i.iter.Prev()
|
||||
@@ -304,7 +320,7 @@ func (i *DefragmentingIter) Next() *Span {
|
||||
// Next once to move onto y, defragment forward to land on the first z
|
||||
// position.
|
||||
i.iterSpan = i.iter.Next()
|
||||
if invariants.Enabled && i.iterSpan == nil {
|
||||
if invariants.Enabled && i.iterSpan == nil && i.iter.Error() == nil {
|
||||
panic("pebble: invariant violation: no next span while switching directions")
|
||||
}
|
||||
// We're now positioned on the first span that was defragmented into the
|
||||
@@ -380,7 +396,7 @@ func (i *DefragmentingIter) Prev() *Span {
|
||||
// Prev once to move onto y, defragment backward to land on the last x
|
||||
// position.
|
||||
i.iterSpan = i.iter.Prev()
|
||||
if invariants.Enabled && i.iterSpan == nil {
|
||||
if invariants.Enabled && i.iterSpan == nil && i.iter.Error() == nil {
|
||||
panic("pebble: invariant violation: no previous span while switching directions")
|
||||
}
|
||||
// We're now positioned on the last span that was defragmented into the
|
||||
@@ -437,6 +453,15 @@ func (i *DefragmentingIter) defragmentForward() *Span {
|
||||
i.keysBuf = i.reduce(i.keysBuf, i.iterSpan.Keys)
|
||||
i.iterSpan = i.iter.Next()
|
||||
}
|
||||
// i.iterSpan == nil
|
||||
//
|
||||
// The inner iterator may return nil when it encounters an error. If there
|
||||
// was an error, we don't know whether there is another span we should
|
||||
// defragment or not. Return nil so that the caller knows they should check
|
||||
// Error().
|
||||
if i.iter.Error() != nil {
|
||||
return nil
|
||||
}
|
||||
i.curr.Keys = i.keysBuf
|
||||
return &i.curr
|
||||
}
|
||||
@@ -470,6 +495,15 @@ func (i *DefragmentingIter) defragmentBackward() *Span {
|
||||
i.keysBuf = i.reduce(i.keysBuf, i.iterSpan.Keys)
|
||||
i.iterSpan = i.iter.Prev()
|
||||
}
|
||||
// i.iterSpan == nil
|
||||
//
|
||||
// The inner iterator may return nil when it encounters an error. If there
|
||||
// was an error, we don't know whether there is another span we should
|
||||
// defragment or not. Return nil so that the caller knows they should check
|
||||
// Error().
|
||||
if i.iter.Error() != nil {
|
||||
return nil
|
||||
}
|
||||
i.curr.Keys = i.keysBuf
|
||||
return &i.curr
|
||||
}
|
||||
|
||||
25
vendor/github.com/cockroachdb/pebble/internal/keyspan/filter.go
generated
vendored
25
vendor/github.com/cockroachdb/pebble/internal/keyspan/filter.go
generated
vendored
@@ -4,6 +4,8 @@
|
||||
|
||||
package keyspan
|
||||
|
||||
import "github.com/cockroachdb/pebble/internal/base"
|
||||
|
||||
// FilterFunc defines a transform from the input Span into the output Span. The
|
||||
// function returns true if the Span should be returned by the iterator, and
|
||||
// false if the Span should be skipped. The FilterFunc is permitted to mutate
|
||||
@@ -21,6 +23,7 @@ type FilterFunc func(in *Span, out *Span) (keep bool)
|
||||
type filteringIter struct {
|
||||
iter FragmentIterator
|
||||
filterFn FilterFunc
|
||||
cmp base.Compare
|
||||
|
||||
// span is a mutable Span passed to the filterFn. The filterFn is free to
|
||||
// mutate this Span. The slice of Keys in the Span is reused with every call
|
||||
@@ -32,18 +35,32 @@ var _ FragmentIterator = (*filteringIter)(nil)
|
||||
|
||||
// Filter returns a new filteringIter that will filter the Spans from the
|
||||
// provided child iterator using the provided FilterFunc.
|
||||
func Filter(iter FragmentIterator, filter FilterFunc) FragmentIterator {
|
||||
return &filteringIter{iter: iter, filterFn: filter}
|
||||
func Filter(iter FragmentIterator, filter FilterFunc, cmp base.Compare) FragmentIterator {
|
||||
return &filteringIter{iter: iter, filterFn: filter, cmp: cmp}
|
||||
}
|
||||
|
||||
// SeekGE implements FragmentIterator.
|
||||
func (i *filteringIter) SeekGE(key []byte) *Span {
|
||||
return i.filter(i.iter.SeekGE(key), +1)
|
||||
span := i.filter(i.iter.SeekGE(key), +1)
|
||||
// i.filter could return a span that's less than key, _if_ the filterFunc
|
||||
// (which has no knowledge of the seek key) mutated the span to end at a key
|
||||
// less than or equal to `key`. Detect this case and next/invalidate the iter.
|
||||
if span != nil && i.cmp(span.End, key) <= 0 {
|
||||
return i.Next()
|
||||
}
|
||||
return span
|
||||
}
|
||||
|
||||
// SeekLT implements FragmentIterator.
|
||||
func (i *filteringIter) SeekLT(key []byte) *Span {
|
||||
return i.filter(i.iter.SeekLT(key), -1)
|
||||
span := i.filter(i.iter.SeekLT(key), -1)
|
||||
// i.filter could return a span that's >= key, _if_ the filterFunc (which has
|
||||
// no knowledge of the seek key) mutated the span to start at a key greater
|
||||
// than or equal to `key`. Detect this case and prev/invalidate the iter.
|
||||
if span != nil && i.cmp(span.Start, key) >= 0 {
|
||||
return i.Prev()
|
||||
}
|
||||
return span
|
||||
}
|
||||
|
||||
// First implements FragmentIterator.
|
||||
|
||||
225
vendor/github.com/cockroachdb/pebble/internal/keyspan/interleaving_iter.go
generated
vendored
225
vendor/github.com/cockroachdb/pebble/internal/keyspan/interleaving_iter.go
generated
vendored
@@ -7,6 +7,7 @@ package keyspan
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
)
|
||||
@@ -111,6 +112,22 @@ type InterleavingIter struct {
|
||||
nextPrefixBuf []byte
|
||||
pointKey *base.InternalKey
|
||||
pointVal base.LazyValue
|
||||
// err holds an iterator error from either pointIter or keyspanIter. It's
|
||||
// reset to nil on seeks. An overview of error-handling mechanics:
|
||||
//
|
||||
// Whenever either pointIter or keyspanIter is respositioned and a nil
|
||||
// key/span is returned, the code performing the positioning is responsible
|
||||
// for checking the iterator's Error() value. This happens in savePoint and
|
||||
// saveSpan[Forward,Backward].
|
||||
//
|
||||
// Once i.err is non-nil, the computation of i.pos must set i.pos =
|
||||
// posExhausted. This happens in compute[Smallest|Largest]Pos and
|
||||
// [next|prev]Pos. Setting i.pos to posExhausted ensures we'll yield nil to
|
||||
// the caller, which they'll interpret as a signal they must check Error().
|
||||
//
|
||||
// INVARIANTS:
|
||||
// i.err != nil => i.pos = posExhausted
|
||||
err error
|
||||
// prefix records the iterator's current prefix if the iterator is in prefix
|
||||
// mode. During prefix mode, Pebble will truncate spans to the next prefix.
|
||||
// If the iterator subsequently leaves prefix mode, the existing span cached
|
||||
@@ -216,7 +233,7 @@ func (i *InterleavingIter) InitSeekGE(
|
||||
i.dir = +1
|
||||
i.clearMask()
|
||||
i.prefix = prefix
|
||||
i.pointKey, i.pointVal = pointKey, pointValue
|
||||
i.savePoint(pointKey, pointValue)
|
||||
// NB: This keyspanSeekGE call will truncate the span to the seek key if
|
||||
// necessary. This truncation is important for cases where a switch to
|
||||
// combined iteration is made during a user-initiated SeekGE.
|
||||
@@ -240,7 +257,7 @@ func (i *InterleavingIter) InitSeekLT(
|
||||
) (*base.InternalKey, base.LazyValue) {
|
||||
i.dir = -1
|
||||
i.clearMask()
|
||||
i.pointKey, i.pointVal = pointKey, pointValue
|
||||
i.savePoint(pointKey, pointValue)
|
||||
i.keyspanSeekLT(key)
|
||||
i.computeLargestPos()
|
||||
return i.yieldPosition(i.lower, i.prevPos)
|
||||
@@ -260,9 +277,10 @@ func (i *InterleavingIter) InitSeekLT(
|
||||
func (i *InterleavingIter) SeekGE(
|
||||
key []byte, flags base.SeekGEFlags,
|
||||
) (*base.InternalKey, base.LazyValue) {
|
||||
i.err = nil
|
||||
i.clearMask()
|
||||
i.disablePrefixMode()
|
||||
i.pointKey, i.pointVal = i.pointIter.SeekGE(key, flags)
|
||||
i.savePoint(i.pointIter.SeekGE(key, flags))
|
||||
|
||||
// We need to seek the keyspan iterator too. If the keyspan iterator was
|
||||
// already positioned at a span, we might be able to avoid the seek if the
|
||||
@@ -270,7 +288,7 @@ func (i *InterleavingIter) SeekGE(
|
||||
if i.span != nil && i.cmp(key, i.span.End) < 0 && i.cmp(key, i.span.Start) >= 0 {
|
||||
// We're seeking within the existing span's bounds. We still might need
|
||||
// truncate the span to the iterator's bounds.
|
||||
i.checkForwardBound()
|
||||
i.saveSpanForward(i.span)
|
||||
i.savedKeyspan()
|
||||
} else {
|
||||
i.keyspanSeekGE(key, nil /* prefix */)
|
||||
@@ -295,9 +313,10 @@ func (i *InterleavingIter) SeekGE(
|
||||
func (i *InterleavingIter) SeekPrefixGE(
|
||||
prefix, key []byte, flags base.SeekGEFlags,
|
||||
) (*base.InternalKey, base.LazyValue) {
|
||||
i.err = nil
|
||||
i.clearMask()
|
||||
i.prefix = prefix
|
||||
i.pointKey, i.pointVal = i.pointIter.SeekPrefixGE(prefix, key, flags)
|
||||
i.savePoint(i.pointIter.SeekPrefixGE(prefix, key, flags))
|
||||
|
||||
// We need to seek the keyspan iterator too. If the keyspan iterator was
|
||||
// already positioned at a span, we might be able to avoid the seek if the
|
||||
@@ -326,7 +345,7 @@ func (i *InterleavingIter) SeekPrefixGE(
|
||||
if ei := i.comparer.Split(i.span.End); i.cmp(prefix, i.span.End[:ei]) < 0 {
|
||||
// We're seeking within the existing span's bounds. We still might need
|
||||
// truncate the span to the iterator's bounds.
|
||||
i.checkForwardBound()
|
||||
i.saveSpanForward(i.span)
|
||||
i.savedKeyspan()
|
||||
seekKeyspanIter = false
|
||||
}
|
||||
@@ -344,9 +363,10 @@ func (i *InterleavingIter) SeekPrefixGE(
|
||||
func (i *InterleavingIter) SeekLT(
|
||||
key []byte, flags base.SeekLTFlags,
|
||||
) (*base.InternalKey, base.LazyValue) {
|
||||
i.err = nil
|
||||
i.clearMask()
|
||||
i.disablePrefixMode()
|
||||
i.pointKey, i.pointVal = i.pointIter.SeekLT(key, flags)
|
||||
i.savePoint(i.pointIter.SeekLT(key, flags))
|
||||
|
||||
// We need to seek the keyspan iterator too. If the keyspan iterator was
|
||||
// already positioned at a span, we might be able to avoid the seek if the
|
||||
@@ -354,13 +374,13 @@ func (i *InterleavingIter) SeekLT(
|
||||
if i.span != nil && i.cmp(key, i.span.Start) > 0 && i.cmp(key, i.span.End) < 0 {
|
||||
// We're seeking within the existing span's bounds. We still might need
|
||||
// truncate the span to the iterator's bounds.
|
||||
i.checkBackwardBound()
|
||||
i.saveSpanBackward(i.span)
|
||||
// The span's start key is still not guaranteed to be less than key,
|
||||
// because of the bounds enforcement. Consider the following example:
|
||||
//
|
||||
// Bounds are set to [d,e). The user performs a SeekLT(d). The
|
||||
// FragmentIterator.SeekLT lands on a span [b,f). This span has a start
|
||||
// key less than d, as expected. Above, checkBackwardBound truncates the
|
||||
// key less than d, as expected. Above, saveSpanBackward truncates the
|
||||
// span to match the iterator's current bounds, modifying the span to
|
||||
// [d,e), which does not overlap the search space of [-∞, d).
|
||||
//
|
||||
@@ -382,11 +402,11 @@ func (i *InterleavingIter) SeekLT(
|
||||
|
||||
// First implements (base.InternalIterator).First.
|
||||
func (i *InterleavingIter) First() (*base.InternalKey, base.LazyValue) {
|
||||
i.err = nil
|
||||
i.clearMask()
|
||||
i.disablePrefixMode()
|
||||
i.pointKey, i.pointVal = i.pointIter.First()
|
||||
i.span = i.keyspanIter.First()
|
||||
i.checkForwardBound()
|
||||
i.savePoint(i.pointIter.First())
|
||||
i.saveSpanForward(i.keyspanIter.First())
|
||||
i.savedKeyspan()
|
||||
i.dir = +1
|
||||
i.computeSmallestPos()
|
||||
@@ -395,11 +415,11 @@ func (i *InterleavingIter) First() (*base.InternalKey, base.LazyValue) {
|
||||
|
||||
// Last implements (base.InternalIterator).Last.
|
||||
func (i *InterleavingIter) Last() (*base.InternalKey, base.LazyValue) {
|
||||
i.err = nil
|
||||
i.clearMask()
|
||||
i.disablePrefixMode()
|
||||
i.pointKey, i.pointVal = i.pointIter.Last()
|
||||
i.span = i.keyspanIter.Last()
|
||||
i.checkBackwardBound()
|
||||
i.savePoint(i.pointIter.Last())
|
||||
i.saveSpanBackward(i.keyspanIter.Last())
|
||||
i.savedKeyspan()
|
||||
i.dir = -1
|
||||
i.computeLargestPos()
|
||||
@@ -436,8 +456,7 @@ func (i *InterleavingIter) Next() (*base.InternalKey, base.LazyValue) {
|
||||
// entirely behind the current key (!i.withinSpan), then we
|
||||
// need to move it to the first span in the forward direction.
|
||||
if !i.withinSpan {
|
||||
i.span = i.keyspanIter.Next()
|
||||
i.checkForwardBound()
|
||||
i.saveSpanForward(i.keyspanIter.Next())
|
||||
i.savedKeyspan()
|
||||
}
|
||||
case posKeyspanStart:
|
||||
@@ -445,12 +464,12 @@ func (i *InterleavingIter) Next() (*base.InternalKey, base.LazyValue) {
|
||||
// Since we're positioned on a Span, the pointIter is positioned
|
||||
// entirely behind the current iterator position. Reposition it
|
||||
// ahead of the current iterator position.
|
||||
i.pointKey, i.pointVal = i.pointIter.Next()
|
||||
i.savePoint(i.pointIter.Next())
|
||||
case posKeyspanEnd:
|
||||
// Since we're positioned on a Span, the pointIter is positioned
|
||||
// entirely behind of the current iterator position. Reposition it
|
||||
// ahead the current iterator position.
|
||||
i.pointKey, i.pointVal = i.pointIter.Next()
|
||||
i.savePoint(i.pointIter.Next())
|
||||
}
|
||||
// Fallthrough to calling i.nextPos.
|
||||
}
|
||||
@@ -468,7 +487,7 @@ func (i *InterleavingIter) NextPrefix(succKey []byte) (*base.InternalKey, base.L
|
||||
case posExhausted:
|
||||
return nil, base.LazyValue{}
|
||||
case posPointKey:
|
||||
i.pointKey, i.pointVal = i.pointIter.NextPrefix(succKey)
|
||||
i.savePoint(i.pointIter.NextPrefix(succKey))
|
||||
if i.withinSpan {
|
||||
if i.pointKey == nil || i.cmp(i.span.End, i.pointKey.UserKey) <= 0 {
|
||||
i.pos = posKeyspanEnd
|
||||
@@ -514,15 +533,14 @@ func (i *InterleavingIter) Prev() (*base.InternalKey, base.LazyValue) {
|
||||
// current key (!i.withinSpan), then we need to move it to the first
|
||||
// span in the reverse direction.
|
||||
if !i.withinSpan {
|
||||
i.span = i.keyspanIter.Prev()
|
||||
i.checkBackwardBound()
|
||||
i.saveSpanBackward(i.keyspanIter.Prev())
|
||||
i.savedKeyspan()
|
||||
}
|
||||
case posKeyspanStart:
|
||||
// Since we're positioned on a Span, the pointIter is positioned
|
||||
// entirely ahead of the current iterator position. Reposition it
|
||||
// behind the current iterator position.
|
||||
i.pointKey, i.pointVal = i.pointIter.Prev()
|
||||
i.savePoint(i.pointIter.Prev())
|
||||
// Without considering truncation of spans to seek keys, the keyspan
|
||||
// iterator is already in the right place. But consider span [a, z)
|
||||
// and this sequence of iterator calls:
|
||||
@@ -544,7 +562,7 @@ func (i *InterleavingIter) Prev() (*base.InternalKey, base.LazyValue) {
|
||||
// Since we're positioned on a Span, the pointIter is positioned
|
||||
// entirely ahead of the current iterator position. Reposition it
|
||||
// behind the current iterator position.
|
||||
i.pointKey, i.pointVal = i.pointIter.Prev()
|
||||
i.savePoint(i.pointIter.Prev())
|
||||
}
|
||||
|
||||
if i.spanMarkerTruncated {
|
||||
@@ -561,15 +579,17 @@ func (i *InterleavingIter) Prev() (*base.InternalKey, base.LazyValue) {
|
||||
//
|
||||
// MIN(i.pointKey, i.span.Start)
|
||||
func (i *InterleavingIter) computeSmallestPos() {
|
||||
if i.span != nil && (i.pointKey == nil || i.cmp(i.startKey(), i.pointKey.UserKey) <= 0) {
|
||||
i.withinSpan = true
|
||||
i.pos = posKeyspanStart
|
||||
return
|
||||
}
|
||||
i.withinSpan = false
|
||||
if i.pointKey != nil {
|
||||
i.pos = posPointKey
|
||||
return
|
||||
if i.err == nil {
|
||||
if i.span != nil && (i.pointKey == nil || i.cmp(i.startKey(), i.pointKey.UserKey) <= 0) {
|
||||
i.withinSpan = true
|
||||
i.pos = posKeyspanStart
|
||||
return
|
||||
}
|
||||
i.withinSpan = false
|
||||
if i.pointKey != nil {
|
||||
i.pos = posPointKey
|
||||
return
|
||||
}
|
||||
}
|
||||
i.pos = posExhausted
|
||||
}
|
||||
@@ -578,30 +598,52 @@ func (i *InterleavingIter) computeSmallestPos() {
|
||||
//
|
||||
// MAX(i.pointKey, i.span.End)
|
||||
func (i *InterleavingIter) computeLargestPos() {
|
||||
if i.span != nil && (i.pointKey == nil || i.cmp(i.span.End, i.pointKey.UserKey) > 0) {
|
||||
i.withinSpan = true
|
||||
i.pos = posKeyspanEnd
|
||||
return
|
||||
}
|
||||
i.withinSpan = false
|
||||
if i.pointKey != nil {
|
||||
i.pos = posPointKey
|
||||
return
|
||||
if i.err == nil {
|
||||
if i.span != nil && (i.pointKey == nil || i.cmp(i.span.End, i.pointKey.UserKey) > 0) {
|
||||
i.withinSpan = true
|
||||
i.pos = posKeyspanEnd
|
||||
return
|
||||
}
|
||||
i.withinSpan = false
|
||||
if i.pointKey != nil {
|
||||
i.pos = posPointKey
|
||||
return
|
||||
}
|
||||
}
|
||||
i.pos = posExhausted
|
||||
}
|
||||
|
||||
// nextPos advances the iterator one position in the forward direction.
|
||||
func (i *InterleavingIter) nextPos() {
|
||||
if invariants.Enabled {
|
||||
defer func() {
|
||||
if i.err != nil && i.pos != posExhausted {
|
||||
panic(errors.AssertionFailedf("iterator has accumulated error but i.pos = %d", i.pos))
|
||||
}
|
||||
}()
|
||||
}
|
||||
// NB: If i.err != nil or any of the positioning methods performed in this
|
||||
// function result in i.err != nil, we must set i.pos = posExhausted. We
|
||||
// perform this check explicitly here, but if any of the branches below
|
||||
// advance either iterator, they must also check i.err and set posExhausted
|
||||
// if necessary.
|
||||
if i.err != nil {
|
||||
i.pos = posExhausted
|
||||
return
|
||||
}
|
||||
|
||||
switch i.pos {
|
||||
case posExhausted:
|
||||
i.pointKey, i.pointVal = i.pointIter.Next()
|
||||
i.span = i.keyspanIter.Next()
|
||||
i.checkForwardBound()
|
||||
i.savePoint(i.pointIter.Next())
|
||||
i.saveSpanForward(i.keyspanIter.Next())
|
||||
i.savedKeyspan()
|
||||
i.computeSmallestPos()
|
||||
case posPointKey:
|
||||
i.pointKey, i.pointVal = i.pointIter.Next()
|
||||
i.savePoint(i.pointIter.Next())
|
||||
if i.err != nil {
|
||||
i.pos = posExhausted
|
||||
return
|
||||
}
|
||||
// If we're not currently within the span, we want to chose the
|
||||
// MIN(pointKey,span.Start), which is exactly the calculation performed
|
||||
// by computeSmallestPos.
|
||||
@@ -635,26 +677,45 @@ func (i *InterleavingIter) nextPos() {
|
||||
i.pos = posKeyspanEnd
|
||||
}
|
||||
case posKeyspanEnd:
|
||||
i.span = i.keyspanIter.Next()
|
||||
i.checkForwardBound()
|
||||
i.saveSpanForward(i.keyspanIter.Next())
|
||||
i.savedKeyspan()
|
||||
i.computeSmallestPos()
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected pos=%d\n", i.pos))
|
||||
panic(fmt.Sprintf("unexpected pos=%d", i.pos))
|
||||
}
|
||||
}
|
||||
|
||||
// prevPos advances the iterator one position in the reverse direction.
|
||||
func (i *InterleavingIter) prevPos() {
|
||||
if invariants.Enabled {
|
||||
defer func() {
|
||||
if i.err != nil && i.pos != posExhausted {
|
||||
panic(errors.AssertionFailedf("iterator has accumulated error but i.pos = %d", i.pos))
|
||||
}
|
||||
}()
|
||||
}
|
||||
// NB: If i.err != nil or any of the positioning methods performed in this
|
||||
// function result in i.err != nil, we must set i.pos = posExhausted. We
|
||||
// perform this check explicitly here, but if any of the branches below
|
||||
// advance either iterator, they must also check i.err and set posExhausted
|
||||
// if necessary.
|
||||
if i.err != nil {
|
||||
i.pos = posExhausted
|
||||
return
|
||||
}
|
||||
|
||||
switch i.pos {
|
||||
case posExhausted:
|
||||
i.pointKey, i.pointVal = i.pointIter.Prev()
|
||||
i.span = i.keyspanIter.Prev()
|
||||
i.checkBackwardBound()
|
||||
i.savePoint(i.pointIter.Prev())
|
||||
i.saveSpanBackward(i.keyspanIter.Prev())
|
||||
i.savedKeyspan()
|
||||
i.computeLargestPos()
|
||||
case posPointKey:
|
||||
i.pointKey, i.pointVal = i.pointIter.Prev()
|
||||
i.savePoint(i.pointIter.Prev())
|
||||
if i.err != nil {
|
||||
i.pos = posExhausted
|
||||
return
|
||||
}
|
||||
// If we're not currently covered by the span, we want to chose the
|
||||
// MAX(pointKey,span.End), which is exactly the calculation performed
|
||||
// by computeLargestPos.
|
||||
@@ -676,8 +737,7 @@ func (i *InterleavingIter) prevPos() {
|
||||
}
|
||||
}
|
||||
case posKeyspanStart:
|
||||
i.span = i.keyspanIter.Prev()
|
||||
i.checkBackwardBound()
|
||||
i.saveSpanBackward(i.keyspanIter.Prev())
|
||||
i.savedKeyspan()
|
||||
i.computeLargestPos()
|
||||
case posKeyspanEnd:
|
||||
@@ -688,7 +748,7 @@ func (i *InterleavingIter) prevPos() {
|
||||
i.pos = posKeyspanStart
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected pos=%d\n", i.pos))
|
||||
panic(fmt.Sprintf("unexpected pos=%d", i.pos))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -760,21 +820,19 @@ func (i *InterleavingIter) yieldPosition(
|
||||
|
||||
// keyspanSeekGE seeks the keyspan iterator to the first span covering a key ≥ k.
|
||||
func (i *InterleavingIter) keyspanSeekGE(k []byte, prefix []byte) {
|
||||
i.span = i.keyspanIter.SeekGE(k)
|
||||
i.checkForwardBound()
|
||||
i.saveSpanForward(i.keyspanIter.SeekGE(k))
|
||||
i.savedKeyspan()
|
||||
}
|
||||
|
||||
// keyspanSeekLT seeks the keyspan iterator to the last span covering a key < k.
|
||||
func (i *InterleavingIter) keyspanSeekLT(k []byte) {
|
||||
i.span = i.keyspanIter.SeekLT(k)
|
||||
i.checkBackwardBound()
|
||||
i.saveSpanBackward(i.keyspanIter.SeekLT(k))
|
||||
// The current span's start key is not guaranteed to be less than key,
|
||||
// because of the bounds enforcement. Consider the following example:
|
||||
//
|
||||
// Bounds are set to [d,e). The user performs a SeekLT(d). The
|
||||
// FragmentIterator.SeekLT lands on a span [b,f). This span has a start key
|
||||
// less than d, as expected. Above, checkBackwardBound truncates the span to
|
||||
// less than d, as expected. Above, saveSpanBackward truncates the span to
|
||||
// match the iterator's current bounds, modifying the span to [d,e), which
|
||||
// does not overlap the search space of [-∞, d).
|
||||
//
|
||||
@@ -786,12 +844,21 @@ func (i *InterleavingIter) keyspanSeekLT(k []byte) {
|
||||
i.savedKeyspan()
|
||||
}
|
||||
|
||||
func (i *InterleavingIter) checkForwardBound() {
|
||||
func (i *InterleavingIter) saveSpanForward(span *Span) {
|
||||
i.span = span
|
||||
i.truncated = false
|
||||
i.truncatedSpan = Span{}
|
||||
if i.span == nil {
|
||||
i.err = firstError(i.err, i.keyspanIter.Error())
|
||||
return
|
||||
}
|
||||
if invariants.Enabled {
|
||||
if err := i.keyspanIter.Error(); err != nil {
|
||||
panic(errors.WithSecondaryError(
|
||||
errors.AssertionFailedf("pebble: %T keyspan iterator returned non-nil span %s while iter has error", i.keyspanIter, i.span),
|
||||
err))
|
||||
}
|
||||
}
|
||||
// Check the upper bound if we have one.
|
||||
if i.upper != nil && i.cmp(i.span.Start, i.upper) >= 0 {
|
||||
i.span = nil
|
||||
@@ -839,12 +906,22 @@ func (i *InterleavingIter) checkForwardBound() {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *InterleavingIter) checkBackwardBound() {
|
||||
func (i *InterleavingIter) saveSpanBackward(span *Span) {
|
||||
i.span = span
|
||||
i.truncated = false
|
||||
i.truncatedSpan = Span{}
|
||||
if i.span == nil {
|
||||
i.err = firstError(i.err, i.keyspanIter.Error())
|
||||
return
|
||||
}
|
||||
if invariants.Enabled {
|
||||
if err := i.keyspanIter.Error(); err != nil {
|
||||
panic(errors.WithSecondaryError(
|
||||
errors.AssertionFailedf("pebble: %T keyspan iterator returned non-nil span %s while iter has error", i.keyspanIter, i.span),
|
||||
err))
|
||||
}
|
||||
}
|
||||
|
||||
// Check the lower bound if we have one.
|
||||
if i.lower != nil && i.cmp(i.span.End, i.lower) <= 0 {
|
||||
i.span = nil
|
||||
@@ -947,6 +1024,12 @@ func (i *InterleavingIter) verify(
|
||||
panic("pebble: invariant violation: key < lower bound")
|
||||
case k != nil && i.upper != nil && i.cmp(k.UserKey, i.upper) >= 0:
|
||||
panic("pebble: invariant violation: key ≥ upper bound")
|
||||
case i.err != nil && k != nil:
|
||||
panic("pebble: invariant violation: accumulated error swallowed")
|
||||
case i.err == nil && i.pointIter.Error() != nil:
|
||||
panic("pebble: invariant violation: pointIter swallowed")
|
||||
case i.err == nil && i.keyspanIter.Error() != nil:
|
||||
panic("pebble: invariant violation: keyspanIter error swallowed")
|
||||
}
|
||||
}
|
||||
return k, v
|
||||
@@ -990,6 +1073,20 @@ func (i *InterleavingIter) startKey() []byte {
|
||||
return i.span.Start
|
||||
}
|
||||
|
||||
func (i *InterleavingIter) savePoint(key *base.InternalKey, value base.LazyValue) {
|
||||
i.pointKey, i.pointVal = key, value
|
||||
if key == nil {
|
||||
i.err = firstError(i.err, i.pointIter.Error())
|
||||
}
|
||||
if invariants.Enabled {
|
||||
if err := i.pointIter.Error(); key != nil && err != nil {
|
||||
panic(errors.WithSecondaryError(
|
||||
errors.AssertionFailedf("pebble: %T point iterator returned non-nil key %q while iter has error", i.pointIter, key),
|
||||
err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Span returns the span covering the last key returned, if any. A span key is
|
||||
// considered to 'cover' a key if the key falls within the span's user key
|
||||
// bounds. The returned span is owned by the InterleavingIter. The caller is
|
||||
@@ -1023,7 +1120,7 @@ func (i *InterleavingIter) Invalidate() {
|
||||
|
||||
// Error implements (base.InternalIterator).Error.
|
||||
func (i *InterleavingIter) Error() error {
|
||||
return firstError(i.pointIter.Error(), i.keyspanIter.Error())
|
||||
return i.err
|
||||
}
|
||||
|
||||
// Close implements (base.InternalIterator).Close.
|
||||
|
||||
2
vendor/github.com/cockroachdb/pebble/internal/keyspan/iter.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/internal/keyspan/iter.go
generated
vendored
@@ -51,6 +51,8 @@ type FragmentIterator interface {
|
||||
Prev() *Span
|
||||
|
||||
// Error returns any accumulated error.
|
||||
//
|
||||
// TODO(jackson): Lift errors into return values on the positioning methods.
|
||||
Error() error
|
||||
|
||||
// Close closes the iterator and returns any accumulated error. Exhausting
|
||||
|
||||
2
vendor/github.com/cockroachdb/pebble/internal/keyspan/truncate.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/internal/keyspan/truncate.go
generated
vendored
@@ -69,5 +69,5 @@ func Truncate(
|
||||
}
|
||||
|
||||
return !out.Empty() && cmp(out.Start, out.End) < 0
|
||||
})
|
||||
}, cmp)
|
||||
}
|
||||
|
||||
14
vendor/github.com/cockroachdb/pebble/internal/manifest/btree.go
generated
vendored
14
vendor/github.com/cockroachdb/pebble/internal/manifest/btree.go
generated
vendored
@@ -195,7 +195,7 @@ func (n *node) incRef() {
|
||||
// new nodes pass contentsToo=false to preserve existing reference counts during
|
||||
// operations that should yield a net-zero change to descendant refcounts.
|
||||
// When a node is released, its contained files are dereferenced.
|
||||
func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
||||
func (n *node) decRef(contentsToo bool, obsolete *[]*FileBacking) {
|
||||
if n.ref.Add(-1) > 0 {
|
||||
// Other references remain. Can't free.
|
||||
return
|
||||
@@ -215,7 +215,13 @@ func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
||||
if obsolete == nil {
|
||||
panic(fmt.Sprintf("file metadata %s dereferenced to zero during tree mutation", f.FileNum))
|
||||
}
|
||||
*obsolete = append(*obsolete, f)
|
||||
// Reference counting is performed on the FileBacking. In the case
|
||||
// of a virtual sstable, this reference counting is performed on
|
||||
// a FileBacking which is shared by every single virtual sstable
|
||||
// with the same backing sstable. If the reference count hits 0,
|
||||
// then we know that the FileBacking won't be required by any
|
||||
// sstable in Pebble, and that the backing sstable can be deleted.
|
||||
*obsolete = append(*obsolete, f.FileBacking)
|
||||
}
|
||||
}
|
||||
if !n.leaf {
|
||||
@@ -761,8 +767,8 @@ type btree struct {
|
||||
|
||||
// Release dereferences and clears the root node of the btree, removing all
|
||||
// items from the btree. In doing so, it decrements contained file counts.
|
||||
// It returns a slice of newly obsolete files, if any.
|
||||
func (t *btree) Release() (obsolete []*FileMetadata) {
|
||||
// It returns a slice of newly obsolete backing files, if any.
|
||||
func (t *btree) Release() (obsolete []*FileBacking) {
|
||||
if t.root != nil {
|
||||
t.root.decRef(true /* contentsToo */, &obsolete)
|
||||
t.root = nil
|
||||
|
||||
54
vendor/github.com/cockroachdb/pebble/internal/manifest/level_metadata.go
generated
vendored
54
vendor/github.com/cockroachdb/pebble/internal/manifest/level_metadata.go
generated
vendored
@@ -17,20 +17,26 @@ import (
|
||||
type LevelMetadata struct {
|
||||
level int
|
||||
totalSize uint64
|
||||
tree btree
|
||||
// NumVirtual is the number of virtual sstables in the level.
|
||||
NumVirtual uint64
|
||||
// VirtualSize is the size of the virtual sstables in the level.
|
||||
VirtualSize uint64
|
||||
tree btree
|
||||
}
|
||||
|
||||
// clone makes a copy of the level metadata, implicitly increasing the ref
|
||||
// count of every file contained within lm.
|
||||
func (lm *LevelMetadata) clone() LevelMetadata {
|
||||
return LevelMetadata{
|
||||
level: lm.level,
|
||||
totalSize: lm.totalSize,
|
||||
tree: lm.tree.Clone(),
|
||||
level: lm.level,
|
||||
totalSize: lm.totalSize,
|
||||
NumVirtual: lm.NumVirtual,
|
||||
VirtualSize: lm.VirtualSize,
|
||||
tree: lm.tree.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
func (lm *LevelMetadata) release() (obsolete []*FileMetadata) {
|
||||
func (lm *LevelMetadata) release() (obsolete []*FileBacking) {
|
||||
return lm.tree.Release()
|
||||
}
|
||||
|
||||
@@ -44,6 +50,10 @@ func makeLevelMetadata(cmp Compare, level int, files []*FileMetadata) LevelMetad
|
||||
lm.tree, _ = makeBTree(bcmp, files)
|
||||
for _, f := range files {
|
||||
lm.totalSize += f.Size
|
||||
if f.Virtual {
|
||||
lm.NumVirtual++
|
||||
lm.VirtualSize += f.Size
|
||||
}
|
||||
}
|
||||
return lm
|
||||
}
|
||||
@@ -62,11 +72,19 @@ func (lm *LevelMetadata) insert(f *FileMetadata) error {
|
||||
return err
|
||||
}
|
||||
lm.totalSize += f.Size
|
||||
if f.Virtual {
|
||||
lm.NumVirtual++
|
||||
lm.VirtualSize += f.Size
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lm *LevelMetadata) remove(f *FileMetadata) bool {
|
||||
lm.totalSize -= f.Size
|
||||
if f.Virtual {
|
||||
lm.NumVirtual--
|
||||
lm.VirtualSize -= f.Size
|
||||
}
|
||||
return lm.tree.Delete(f)
|
||||
}
|
||||
|
||||
@@ -303,6 +321,32 @@ func (ls *LevelSlice) SizeSum() uint64 {
|
||||
return sum
|
||||
}
|
||||
|
||||
// NumVirtual returns the number of virtual sstables in the level. Its runtime is
|
||||
// linear in the length of the slice.
|
||||
func (ls *LevelSlice) NumVirtual() uint64 {
|
||||
var n uint64
|
||||
iter := ls.Iter()
|
||||
for f := iter.First(); f != nil; f = iter.Next() {
|
||||
if f.Virtual {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// VirtualSizeSum returns the sum of the sizes of the virtual sstables in the
|
||||
// level.
|
||||
func (ls *LevelSlice) VirtualSizeSum() uint64 {
|
||||
var sum uint64
|
||||
iter := ls.Iter()
|
||||
for f := iter.First(); f != nil; f = iter.Next() {
|
||||
if f.Virtual {
|
||||
sum += f.Size
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// Reslice constructs a new slice backed by the same underlying level, with
|
||||
// new start and end positions. Reslice invokes the provided function, passing
|
||||
// two LevelIterators: one positioned to i's inclusive start and one
|
||||
|
||||
101
vendor/github.com/cockroachdb/pebble/internal/manifest/version.go
generated
vendored
101
vendor/github.com/cockroachdb/pebble/internal/manifest/version.go
generated
vendored
@@ -49,6 +49,9 @@ type TableStats struct {
|
||||
// The number of point and range deletion entries in the table.
|
||||
NumDeletions uint64
|
||||
// NumRangeKeySets is the total number of range key sets in the table.
|
||||
//
|
||||
// NB: If there's a chance that the sstable contains any range key sets,
|
||||
// then NumRangeKeySets must be > 0.
|
||||
NumRangeKeySets uint64
|
||||
// Estimate of the total disk space that may be dropped by this table's
|
||||
// point deletions by compacting them.
|
||||
@@ -169,22 +172,13 @@ type FileMetadata struct {
|
||||
// FileNum is the file number.
|
||||
//
|
||||
// INVARIANT: when !FileMetadata.Virtual, FileNum == FileBacking.DiskFileNum.
|
||||
//
|
||||
// TODO(bananabrick): Consider creating separate types for
|
||||
// FileMetadata.FileNum and FileBacking.FileNum. FileNum is used both as
|
||||
// an indentifier for the FileMetadata in Pebble, and also as a handle to
|
||||
// perform reads and writes. We should ensure through types that
|
||||
// FileMetadata.FileNum isn't used to perform reads, and that
|
||||
// FileBacking.FileNum isn't used as an identifier for the FileMetadata.
|
||||
FileNum base.FileNum
|
||||
// Size is the size of the file, in bytes. Size is an approximate value for
|
||||
// virtual sstables.
|
||||
//
|
||||
// INVARIANT: when !FileMetadata.Virtual, Size == FileBacking.Size.
|
||||
//
|
||||
// TODO(bananabrick): Size is currently used in metrics, and for many key
|
||||
// Pebble level heuristics. Make sure that the heuristics will still work
|
||||
// appropriately with an approximate value of size.
|
||||
// INVARIANTS:
|
||||
// - When !FileMetadata.Virtual, Size == FileBacking.Size.
|
||||
// - Size should be non-zero. Size 0 virtual sstables must not be created.
|
||||
Size uint64
|
||||
// File creation time in seconds since the epoch (1970-01-01 00:00:00
|
||||
// UTC). For ingested sstables, this corresponds to the time the file was
|
||||
@@ -309,6 +303,7 @@ type PhysicalFileMeta struct {
|
||||
// The underlying file's size is stored in FileBacking.Size, though it could
|
||||
// also be estimated or could correspond to just the referenced portion of
|
||||
// a file (eg. if the file originated on another node).
|
||||
// - Size must be > 0.
|
||||
// - SmallestSeqNum and LargestSeqNum are loose bounds for virtual sstables.
|
||||
// This means that all keys in the virtual sstable must have seqnums within
|
||||
// [SmallestSeqNum, LargestSeqNum], however there's no guarantee that there's
|
||||
@@ -1209,12 +1204,7 @@ func (v *Version) Unref() {
|
||||
l := v.list
|
||||
l.mu.Lock()
|
||||
l.Remove(v)
|
||||
obsolete := v.unrefFiles()
|
||||
fileBacking := make([]*FileBacking, len(obsolete))
|
||||
for i, f := range obsolete {
|
||||
fileBacking[i] = f.FileBacking
|
||||
}
|
||||
v.Deleted(fileBacking)
|
||||
v.Deleted(v.unrefFiles())
|
||||
l.mu.Unlock()
|
||||
}
|
||||
}
|
||||
@@ -1226,17 +1216,12 @@ func (v *Version) Unref() {
|
||||
func (v *Version) UnrefLocked() {
|
||||
if v.refs.Add(-1) == 0 {
|
||||
v.list.Remove(v)
|
||||
obsolete := v.unrefFiles()
|
||||
fileBacking := make([]*FileBacking, len(obsolete))
|
||||
for i, f := range obsolete {
|
||||
fileBacking[i] = f.FileBacking
|
||||
}
|
||||
v.Deleted(fileBacking)
|
||||
v.Deleted(v.unrefFiles())
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Version) unrefFiles() []*FileMetadata {
|
||||
var obsolete []*FileMetadata
|
||||
func (v *Version) unrefFiles() []*FileBacking {
|
||||
var obsolete []*FileBacking
|
||||
for _, lm := range v.Levels {
|
||||
obsolete = append(obsolete, lm.release()...)
|
||||
}
|
||||
@@ -1368,16 +1353,20 @@ func (v *Version) Overlaps(
|
||||
// CheckOrdering checks that the files are consistent with respect to
|
||||
// increasing file numbers (for level 0 files) and increasing and non-
|
||||
// overlapping internal key ranges (for level non-0 files).
|
||||
func (v *Version) CheckOrdering(cmp Compare, format base.FormatKey) error {
|
||||
func (v *Version) CheckOrdering(
|
||||
cmp Compare, format base.FormatKey, order OrderingInvariants,
|
||||
) error {
|
||||
for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- {
|
||||
sublevelIter := v.L0SublevelFiles[sublevel].Iter()
|
||||
if err := CheckOrdering(cmp, format, L0Sublevel(sublevel), sublevelIter); err != nil {
|
||||
// Sublevels have NEVER allowed split user keys, so we can pass
|
||||
// ProhibitSplitUserKeys.
|
||||
if err := CheckOrdering(cmp, format, L0Sublevel(sublevel), sublevelIter, ProhibitSplitUserKeys); err != nil {
|
||||
return base.CorruptionErrorf("%s\n%s", err, v.DebugString(format))
|
||||
}
|
||||
}
|
||||
|
||||
for level, lm := range v.Levels {
|
||||
if err := CheckOrdering(cmp, format, Level(level), lm.Iter()); err != nil {
|
||||
if err := CheckOrdering(cmp, format, Level(level), lm.Iter(), order); err != nil {
|
||||
return base.CorruptionErrorf("%s\n%s", err, v.DebugString(format))
|
||||
}
|
||||
}
|
||||
@@ -1446,10 +1435,34 @@ func (l *VersionList) Remove(v *Version) {
|
||||
v.list = nil // avoid memory leaks
|
||||
}
|
||||
|
||||
// OrderingInvariants dictates the file ordering invariants active.
|
||||
type OrderingInvariants int8
|
||||
|
||||
const (
|
||||
// ProhibitSplitUserKeys indicates that adjacent files within a level cannot
|
||||
// contain the same user key.
|
||||
ProhibitSplitUserKeys OrderingInvariants = iota
|
||||
// AllowSplitUserKeys indicates that adjacent files within a level may
|
||||
// contain the same user key. This is only allowed by historical format
|
||||
// major versions.
|
||||
//
|
||||
// TODO(jackson): Remove.
|
||||
AllowSplitUserKeys
|
||||
)
|
||||
|
||||
// CheckOrdering checks that the files are consistent with respect to
|
||||
// seqnums (for level 0 files -- see detailed comment below) and increasing and non-
|
||||
// overlapping internal key ranges (for non-level 0 files).
|
||||
func CheckOrdering(cmp Compare, format base.FormatKey, level Level, files LevelIterator) error {
|
||||
//
|
||||
// The ordering field may be passed AllowSplitUserKeys to allow adjacent files that are both
|
||||
// inclusive of the same user key. Pebble no longer creates version edits
|
||||
// installing such files, and Pebble databases with sufficiently high format
|
||||
// major version should no longer have any such files within their LSM.
|
||||
// TODO(jackson): Remove AllowSplitUserKeys when we remove support for the
|
||||
// earlier format major versions.
|
||||
func CheckOrdering(
|
||||
cmp Compare, format base.FormatKey, level Level, files LevelIterator, ordering OrderingInvariants,
|
||||
) error {
|
||||
// The invariants to check for L0 sublevels are the same as the ones to
|
||||
// check for all other levels. However, if L0 is not organized into
|
||||
// sublevels, or if all L0 files are being passed in, we do the legacy L0
|
||||
@@ -1527,11 +1540,29 @@ func CheckOrdering(cmp Compare, format base.FormatKey, level Level, files LevelI
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
}
|
||||
if base.InternalCompare(cmp, prev.Largest, f.Smallest) >= 0 {
|
||||
return base.CorruptionErrorf("%s files %s and %s have overlapping ranges: [%s-%s] vs [%s-%s]",
|
||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
|
||||
// What's considered "overlapping" is dependent on the format
|
||||
// major version. If ordering=ProhibitSplitUserKeys, then both
|
||||
// files cannot contain keys with the same user keys. If the
|
||||
// bounds have the same user key, the previous file's boundary
|
||||
// must have a Trailer indicating that it's exclusive.
|
||||
switch ordering {
|
||||
case AllowSplitUserKeys:
|
||||
if base.InternalCompare(cmp, prev.Largest, f.Smallest) >= 0 {
|
||||
return base.CorruptionErrorf("%s files %s and %s have overlapping ranges: [%s-%s] vs [%s-%s]",
|
||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
}
|
||||
case ProhibitSplitUserKeys:
|
||||
if v := cmp(prev.Largest.UserKey, f.Smallest.UserKey); v > 0 || (v == 0 && !prev.Largest.IsExclusiveSentinel()) {
|
||||
return base.CorruptionErrorf("%s files %s and %s have overlapping ranges: [%s-%s] vs [%s-%s]",
|
||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
21
vendor/github.com/cockroachdb/pebble/internal/manifest/version_edit.go
generated
vendored
21
vendor/github.com/cockroachdb/pebble/internal/manifest/version_edit.go
generated
vendored
@@ -783,6 +783,12 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
||||
b.AddedFileBacking = make(map[base.DiskFileNum]*FileBacking)
|
||||
}
|
||||
for _, fb := range ve.CreatedBackingTables {
|
||||
if _, ok := b.AddedFileBacking[fb.DiskFileNum]; ok {
|
||||
// There is already a FileBacking associated with fb.DiskFileNum.
|
||||
// This should never happen. There must always be only one FileBacking
|
||||
// associated with a backing sstable.
|
||||
panic(fmt.Sprintf("pebble: duplicate file backing %s", fb.DiskFileNum.String()))
|
||||
}
|
||||
b.AddedFileBacking[fb.DiskFileNum] = fb
|
||||
}
|
||||
|
||||
@@ -846,6 +852,9 @@ func AccumulateIncompleteAndApplySingleVE(
|
||||
flushSplitBytes int64,
|
||||
readCompactionRate int64,
|
||||
backingStateMap map[base.DiskFileNum]*FileBacking,
|
||||
addBackingFunc func(*FileBacking),
|
||||
removeBackingFunc func(base.DiskFileNum),
|
||||
orderingInvariants OrderingInvariants,
|
||||
) (_ *Version, zombies map[base.DiskFileNum]uint64, _ error) {
|
||||
if len(ve.RemovedBackingTables) != 0 {
|
||||
panic("pebble: invalid incomplete version edit")
|
||||
@@ -857,14 +866,14 @@ func AccumulateIncompleteAndApplySingleVE(
|
||||
}
|
||||
zombies = make(map[base.DiskFileNum]uint64)
|
||||
v, err := b.Apply(
|
||||
curr, cmp, formatKey, flushSplitBytes, readCompactionRate, zombies,
|
||||
curr, cmp, formatKey, flushSplitBytes, readCompactionRate, zombies, orderingInvariants,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, s := range b.AddedFileBacking {
|
||||
backingStateMap[s.DiskFileNum] = s
|
||||
addBackingFunc(s)
|
||||
}
|
||||
|
||||
for fileNum := range zombies {
|
||||
@@ -875,10 +884,9 @@ func AccumulateIncompleteAndApplySingleVE(
|
||||
ve.RemovedBackingTables = append(
|
||||
ve.RemovedBackingTables, fileNum,
|
||||
)
|
||||
delete(backingStateMap, fileNum)
|
||||
removeBackingFunc(fileNum)
|
||||
}
|
||||
}
|
||||
|
||||
return v, zombies, nil
|
||||
}
|
||||
|
||||
@@ -899,6 +907,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
flushSplitBytes int64,
|
||||
readCompactionRate int64,
|
||||
zombies map[base.DiskFileNum]uint64,
|
||||
orderingInvariants OrderingInvariants,
|
||||
) (*Version, error) {
|
||||
addZombie := func(state *FileBacking) {
|
||||
if zombies != nil {
|
||||
@@ -1082,7 +1091,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
} else if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
|
||||
return nil, errors.Wrap(err, "pebble: internal error")
|
||||
}
|
||||
if err := CheckOrdering(cmp, formatKey, Level(0), v.Levels[level].Iter()); err != nil {
|
||||
if err := CheckOrdering(cmp, formatKey, Level(0), v.Levels[level].Iter(), orderingInvariants); err != nil {
|
||||
return nil, errors.Wrap(err, "pebble: internal error")
|
||||
}
|
||||
continue
|
||||
@@ -1103,7 +1112,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
end.Prev()
|
||||
}
|
||||
})
|
||||
if err := CheckOrdering(cmp, formatKey, Level(level), check.Iter()); err != nil {
|
||||
if err := CheckOrdering(cmp, formatKey, Level(level), check.Iter(), orderingInvariants); err != nil {
|
||||
return nil, errors.Wrap(err, "pebble: internal error")
|
||||
}
|
||||
}
|
||||
|
||||
71
vendor/github.com/cockroachdb/pebble/internal/rangekey/coalesce.go
generated
vendored
71
vendor/github.com/cockroachdb/pebble/internal/rangekey/coalesce.go
generated
vendored
@@ -52,15 +52,15 @@ import (
|
||||
// │
|
||||
// ╰── <?>.Next
|
||||
type UserIteratorConfig struct {
|
||||
snapshot uint64
|
||||
comparer *base.Comparer
|
||||
miter keyspan.MergingIter
|
||||
biter keyspan.BoundedIter
|
||||
diter keyspan.DefragmentingIter
|
||||
liters [manifest.NumLevels]keyspan.LevelIter
|
||||
litersUsed int
|
||||
onlySets bool
|
||||
bufs *Buffers
|
||||
snapshot uint64
|
||||
comparer *base.Comparer
|
||||
miter keyspan.MergingIter
|
||||
biter keyspan.BoundedIter
|
||||
diter keyspan.DefragmentingIter
|
||||
liters [manifest.NumLevels]keyspan.LevelIter
|
||||
litersUsed int
|
||||
internalKeys bool
|
||||
bufs *Buffers
|
||||
}
|
||||
|
||||
// Buffers holds various buffers used for range key iteration. They're exposed
|
||||
@@ -79,9 +79,10 @@ func (bufs *Buffers) PrepareForReuse() {
|
||||
|
||||
// Init initializes the range key iterator stack for user iteration. The
|
||||
// resulting fragment iterator applies range key semantics, defragments spans
|
||||
// according to their user-observable state and, if onlySets = true, removes all
|
||||
// according to their user-observable state and, if !internalKeys, removes all
|
||||
// Keys other than RangeKeySets describing the current state of range keys. The
|
||||
// resulting spans contain Keys sorted by Suffix.
|
||||
// resulting spans contain Keys sorted by suffix (unless internalKeys is true,
|
||||
// in which case they remain sorted by trailer descending).
|
||||
//
|
||||
// The snapshot sequence number parameter determines which keys are visible. Any
|
||||
// keys not visible at the provided snapshot are ignored.
|
||||
@@ -91,16 +92,20 @@ func (ui *UserIteratorConfig) Init(
|
||||
lower, upper []byte,
|
||||
hasPrefix *bool,
|
||||
prefix *[]byte,
|
||||
onlySets bool,
|
||||
internalKeys bool,
|
||||
bufs *Buffers,
|
||||
iters ...keyspan.FragmentIterator,
|
||||
) keyspan.FragmentIterator {
|
||||
ui.snapshot = snapshot
|
||||
ui.comparer = comparer
|
||||
ui.onlySets = onlySets
|
||||
ui.internalKeys = internalKeys
|
||||
ui.miter.Init(comparer.Compare, ui, &bufs.merging, iters...)
|
||||
ui.biter.Init(comparer.Compare, comparer.Split, &ui.miter, lower, upper, hasPrefix, prefix)
|
||||
ui.diter.Init(comparer, &ui.biter, ui, keyspan.StaticDefragmentReducer, &bufs.defragmenting)
|
||||
if internalKeys {
|
||||
ui.diter.Init(comparer, &ui.biter, keyspan.DefragmentInternal, keyspan.StaticDefragmentReducer, &bufs.defragmenting)
|
||||
} else {
|
||||
ui.diter.Init(comparer, &ui.biter, ui, keyspan.StaticDefragmentReducer, &bufs.defragmenting)
|
||||
}
|
||||
ui.litersUsed = 0
|
||||
ui.bufs = bufs
|
||||
return &ui.diter
|
||||
@@ -136,7 +141,8 @@ func (ui *UserIteratorConfig) SetBounds(lower, upper []byte) {
|
||||
// of unset keys, removal of keys overwritten by a set at the same suffix, etc)
|
||||
// and then non-RangeKeySet keys are removed. The resulting transformed spans
|
||||
// only contain RangeKeySets describing the state visible at the provided
|
||||
// sequence number, and hold their Keys sorted by Suffix.
|
||||
// sequence number, and hold their Keys sorted by Suffix (except if internalKeys
|
||||
// is true, then keys remain sorted by trailer.
|
||||
func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error {
|
||||
// Apply shadowing of keys.
|
||||
dst.Start = s.Start
|
||||
@@ -148,9 +154,16 @@ func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *k
|
||||
if err := coalesce(ui.comparer.Equal, &ui.bufs.sortBuf, ui.snapshot, s.Keys); err != nil {
|
||||
return err
|
||||
}
|
||||
// During user iteration over range keys, unsets and deletes don't matter.
|
||||
// Remove them if onlySets = true. This step helps logical defragmentation
|
||||
// during iteration.
|
||||
if ui.internalKeys {
|
||||
if s.KeysOrder != keyspan.ByTrailerDesc {
|
||||
panic("unexpected key ordering in UserIteratorTransform with internalKeys = true")
|
||||
}
|
||||
dst.Keys = ui.bufs.sortBuf.Keys
|
||||
keyspan.SortKeysByTrailer(&dst.Keys)
|
||||
return nil
|
||||
}
|
||||
// During user iteration over range keys, unsets and deletes don't matter. This
|
||||
// step helps logical defragmentation during iteration.
|
||||
keys := ui.bufs.sortBuf.Keys
|
||||
dst.Keys = dst.Keys[:0]
|
||||
for i := range keys {
|
||||
@@ -164,17 +177,11 @@ func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *k
|
||||
if invariants.Enabled && len(dst.Keys) > 0 && cmp(dst.Keys[len(dst.Keys)-1].Suffix, keys[i].Suffix) > 0 {
|
||||
panic("pebble: keys unexpectedly not in ascending suffix order")
|
||||
}
|
||||
if ui.onlySets {
|
||||
// Skip.
|
||||
continue
|
||||
}
|
||||
dst.Keys = append(dst.Keys, keys[i])
|
||||
// Skip.
|
||||
continue
|
||||
case base.InternalKeyKindRangeKeyDelete:
|
||||
if ui.onlySets {
|
||||
// Skip.
|
||||
continue
|
||||
}
|
||||
dst.Keys = append(dst.Keys, keys[i])
|
||||
// Skip.
|
||||
continue
|
||||
default:
|
||||
return base.CorruptionErrorf("pebble: unrecognized range key kind %s", keys[i].Kind())
|
||||
}
|
||||
@@ -193,6 +200,10 @@ func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *k
|
||||
// sequence numbers). It's intended for use during user iteration, when the
|
||||
// wrapped keyspan iterator is merging spans across all levels of the LSM.
|
||||
func (ui *UserIteratorConfig) ShouldDefragment(equal base.Equal, a, b *keyspan.Span) bool {
|
||||
// This method is not called with internalKeys = true.
|
||||
if ui.internalKeys {
|
||||
panic("unexpected call to ShouldDefragment with internalKeys = true")
|
||||
}
|
||||
// This implementation must only be used on spans that have transformed by
|
||||
// ui.Transform. The transform applies shadowing, removes all keys besides
|
||||
// the resulting Sets and sorts the keys by suffix. Since shadowing has been
|
||||
@@ -208,8 +219,8 @@ func (ui *UserIteratorConfig) ShouldDefragment(equal base.Equal, a, b *keyspan.S
|
||||
ret := true
|
||||
for i := range a.Keys {
|
||||
if invariants.Enabled {
|
||||
if ui.onlySets && (a.Keys[i].Kind() != base.InternalKeyKindRangeKeySet ||
|
||||
b.Keys[i].Kind() != base.InternalKeyKindRangeKeySet) {
|
||||
if a.Keys[i].Kind() != base.InternalKeyKindRangeKeySet ||
|
||||
b.Keys[i].Kind() != base.InternalKeyKindRangeKeySet {
|
||||
panic("pebble: unexpected non-RangeKeySet during defragmentation")
|
||||
}
|
||||
if i > 0 && (ui.comparer.Compare(a.Keys[i].Suffix, a.Keys[i-1].Suffix) < 0 ||
|
||||
|
||||
62
vendor/github.com/cockroachdb/pebble/internal/testkeys/testkeys.go
generated
vendored
62
vendor/github.com/cockroachdb/pebble/internal/testkeys/testkeys.go
generated
vendored
@@ -384,14 +384,18 @@ func computeAlphabetKeyIndex(key []byte, alphabet map[byte]int64, n int) int64 {
|
||||
return ret
|
||||
}
|
||||
|
||||
func abs(a int64) int64 {
|
||||
if a < 0 {
|
||||
return -a
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// RandomSeparator returns a random alphabetic key k such that a < k < b,
|
||||
// pulling randomness from the provided random number generator. If dst is
|
||||
// provided and the generated key fits within dst's capacity, the returned slice
|
||||
// will use dst's memory.
|
||||
//
|
||||
// The keys a and b must be purely alphabetic, consisting only of characters
|
||||
// a-z.
|
||||
//
|
||||
// If a prefix P exists such that Prefix(a) < P < Prefix(b), the generated key
|
||||
// will consist of the prefix P appended with the provided suffix. A zero suffix
|
||||
// generates an unsuffixed key. If no such prefix P exists, RandomSeparator will
|
||||
@@ -405,6 +409,10 @@ func computeAlphabetKeyIndex(key []byte, alphabet map[byte]int64, n int) int64 {
|
||||
//
|
||||
// RandomSeparator panics if a or b fails to decode.
|
||||
func RandomSeparator(dst, a, b []byte, suffix int64, maxLength int, rng *rand.Rand) []byte {
|
||||
if Comparer.Compare(a, b) >= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine both keys' logical prefixes and suffixes.
|
||||
ai := Comparer.Split(a)
|
||||
bi := Comparer.Split(b)
|
||||
@@ -431,31 +439,56 @@ func RandomSeparator(dst, a, b []byte, suffix int64, maxLength int, rng *rand.Ra
|
||||
diff := bpIdx - apIdx
|
||||
generatedIdx := bpIdx
|
||||
if diff > 0 {
|
||||
var add int64 = diff + 1
|
||||
var start int64 = apIdx
|
||||
if as == 1 {
|
||||
// There's no expressible key with suffix `a` greater than a@1, so
|
||||
// There's no expressible key with prefix a greater than a@1. So,
|
||||
// exclude ap.
|
||||
generatedIdx = rng.Int63n(diff) + apIdx + 1
|
||||
} else {
|
||||
generatedIdx = rng.Int63n(diff+1) + apIdx
|
||||
start = apIdx + 1
|
||||
add = diff
|
||||
}
|
||||
if bs == 0 {
|
||||
// No key with prefix b can sort before b@0. We don't want to pick b.
|
||||
add--
|
||||
}
|
||||
// We're allowing generated id to be in the range [start, start + add - 1].
|
||||
if start > start+add-1 {
|
||||
return nil
|
||||
}
|
||||
// If we can generate a key which is actually in the middle of apIdx
|
||||
// and bpIdx use it so that we don't have to bother about timestamps.
|
||||
generatedIdx = rng.Int63n(add) + start
|
||||
for diff > 1 && generatedIdx == apIdx || generatedIdx == bpIdx {
|
||||
generatedIdx = rng.Int63n(add) + start
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case generatedIdx == apIdx && generatedIdx == bpIdx:
|
||||
if bs-as <= 1 {
|
||||
if abs(bs-as) <= 1 {
|
||||
// There's no expressible suffix between the two, and there's no
|
||||
// possible separator key.
|
||||
return nil
|
||||
}
|
||||
// The key b is >= key a, but has the same prefix, so b must have the
|
||||
// smaller timestamp, unless a has timestamp of 0.
|
||||
//
|
||||
// NB: The zero suffix (suffix-less) sorts before all other suffixes, so
|
||||
// any suffix we generate will be greater than it.
|
||||
if (as != 0 && suffix >= as) || suffix <= bs {
|
||||
suffix = rng.Int63n(bs-as-1) + as + 1
|
||||
if as == 0 {
|
||||
// bs > as
|
||||
suffix = bs + rng.Int63n(10) + 1
|
||||
} else {
|
||||
// bs < as.
|
||||
// Generate suffix in range [bs + 1, as - 1]
|
||||
suffix = bs + 1 + rng.Int63n(as-bs-1)
|
||||
}
|
||||
case generatedIdx == apIdx:
|
||||
// NB: The zero suffix (suffix-less) sorts before all other suffixes, so
|
||||
// any suffix we generate will be greater than it.
|
||||
if as != 0 && suffix >= as {
|
||||
if as == 0 && suffix == 0 {
|
||||
suffix++
|
||||
} else if as != 0 && suffix >= as {
|
||||
suffix = rng.Int63n(as)
|
||||
}
|
||||
case generatedIdx == bpIdx:
|
||||
@@ -468,7 +501,12 @@ func RandomSeparator(dst, a, b []byte, suffix int64, maxLength int, rng *rand.Ra
|
||||
} else {
|
||||
dst = dst[:cap(dst)]
|
||||
}
|
||||
w := WriteKeyAt(dst, Alpha(maxLength), generatedIdx, suffix)
|
||||
var w int
|
||||
if suffix == 0 {
|
||||
w = WriteKey(dst, Alpha(maxLength), generatedIdx)
|
||||
} else {
|
||||
w = WriteKeyAt(dst, Alpha(maxLength), generatedIdx, suffix)
|
||||
}
|
||||
return dst[:w]
|
||||
}
|
||||
|
||||
|
||||
303
vendor/github.com/cockroachdb/pebble/iterator.go
generated
vendored
303
vendor/github.com/cockroachdb/pebble/iterator.go
generated
vendored
@@ -447,6 +447,11 @@ const (
|
||||
seekPrefixGELastPositioningOp
|
||||
seekGELastPositioningOp
|
||||
seekLTLastPositioningOp
|
||||
// internalNextOp is a special internal iterator positioning operation used
|
||||
// by CanDeterministicallySingleDelete. It exists for enforcing requirements
|
||||
// around calling CanDeterministicallySingleDelete at most once per external
|
||||
// iterator position.
|
||||
internalNextOp
|
||||
// invalidatedLastPositionOp is similar to unknownLastPositionOp and the
|
||||
// only reason to distinguish this is for the wider set of SeekGE
|
||||
// optimizations we permit for the external iterator Iterator.forwardOnly
|
||||
@@ -550,6 +555,21 @@ func (i *Iterator) findNextEntry(limit []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
// If the user has configured a SkipPoint function, invoke it to see
|
||||
// whether we should skip over the current user key.
|
||||
if i.opts.SkipPoint != nil && key.Kind() != InternalKeyKindRangeKeySet && i.opts.SkipPoint(i.iterKey.UserKey) {
|
||||
// NB: We could call nextUserKey, but in some cases the SkipPoint
|
||||
// predicate function might be cheaper than nextUserKey's key copy
|
||||
// and key comparison. This should be the case for MVCC suffix
|
||||
// comparisons, for example. In the future, we could expand the
|
||||
// SkipPoint interface to give the implementor more control over
|
||||
// whether we skip over just the internal key, the user key, or even
|
||||
// the key prefix.
|
||||
i.stats.ForwardStepCount[InternalIterCall]++
|
||||
i.iterKey, i.iterValue = i.iter.Next()
|
||||
continue
|
||||
}
|
||||
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindRangeKeySet:
|
||||
// Save the current key.
|
||||
@@ -619,6 +639,13 @@ func (i *Iterator) findNextEntry(limit []byte) {
|
||||
}
|
||||
|
||||
func (i *Iterator) nextPointCurrentUserKey() bool {
|
||||
// If the user has configured a SkipPoint function and the current user key
|
||||
// would be skipped by it, there's no need to step forward looking for a
|
||||
// point key. If we were to find one, it should be skipped anyways.
|
||||
if i.opts.SkipPoint != nil && i.opts.SkipPoint(i.key) {
|
||||
return false
|
||||
}
|
||||
|
||||
i.pos = iterPosCurForward
|
||||
|
||||
i.iterKey, i.iterValue = i.iter.Next()
|
||||
@@ -780,39 +807,41 @@ func (i *Iterator) maybeSampleRead() {
|
||||
func (i *Iterator) sampleRead() {
|
||||
var topFile *manifest.FileMetadata
|
||||
topLevel, numOverlappingLevels := numLevels, 0
|
||||
if mi, ok := i.iter.(*mergingIter); ok {
|
||||
if len(mi.levels) > 1 {
|
||||
mi.ForEachLevelIter(func(li *levelIter) bool {
|
||||
l := manifest.LevelToInt(li.level)
|
||||
if f := li.iterFile; f != nil {
|
||||
var containsKey bool
|
||||
if i.pos == iterPosNext || i.pos == iterPosCurForward ||
|
||||
i.pos == iterPosCurForwardPaused {
|
||||
containsKey = i.cmp(f.SmallestPointKey.UserKey, i.key) <= 0
|
||||
} else if i.pos == iterPosPrev || i.pos == iterPosCurReverse ||
|
||||
i.pos == iterPosCurReversePaused {
|
||||
containsKey = i.cmp(f.LargestPointKey.UserKey, i.key) >= 0
|
||||
}
|
||||
// Do nothing if the current key is not contained in f's
|
||||
// bounds. We could seek the LevelIterator at this level
|
||||
// to find the right file, but the performance impacts of
|
||||
// doing that are significant enough to negate the benefits
|
||||
// of read sampling in the first place. See the discussion
|
||||
// at:
|
||||
// https://github.com/cockroachdb/pebble/pull/1041#issuecomment-763226492
|
||||
if containsKey {
|
||||
numOverlappingLevels++
|
||||
if numOverlappingLevels >= 2 {
|
||||
// Terminate the loop early if at least 2 overlapping levels are found.
|
||||
return true
|
||||
}
|
||||
topLevel = l
|
||||
topFile = f
|
||||
}
|
||||
mi := i.merging
|
||||
if mi == nil {
|
||||
return
|
||||
}
|
||||
if len(mi.levels) > 1 {
|
||||
mi.ForEachLevelIter(func(li *levelIter) bool {
|
||||
l := manifest.LevelToInt(li.level)
|
||||
if f := li.iterFile; f != nil {
|
||||
var containsKey bool
|
||||
if i.pos == iterPosNext || i.pos == iterPosCurForward ||
|
||||
i.pos == iterPosCurForwardPaused {
|
||||
containsKey = i.cmp(f.SmallestPointKey.UserKey, i.key) <= 0
|
||||
} else if i.pos == iterPosPrev || i.pos == iterPosCurReverse ||
|
||||
i.pos == iterPosCurReversePaused {
|
||||
containsKey = i.cmp(f.LargestPointKey.UserKey, i.key) >= 0
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
// Do nothing if the current key is not contained in f's
|
||||
// bounds. We could seek the LevelIterator at this level
|
||||
// to find the right file, but the performance impacts of
|
||||
// doing that are significant enough to negate the benefits
|
||||
// of read sampling in the first place. See the discussion
|
||||
// at:
|
||||
// https://github.com/cockroachdb/pebble/pull/1041#issuecomment-763226492
|
||||
if containsKey {
|
||||
numOverlappingLevels++
|
||||
if numOverlappingLevels >= 2 {
|
||||
// Terminate the loop early if at least 2 overlapping levels are found.
|
||||
return true
|
||||
}
|
||||
topLevel = l
|
||||
topFile = f
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
if topFile == nil || topLevel >= numLevels {
|
||||
return
|
||||
@@ -909,6 +938,26 @@ func (i *Iterator) findPrevEntry(limit []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// If the user has configured a SkipPoint function, invoke it to see
|
||||
// whether we should skip over the current user key.
|
||||
if i.opts.SkipPoint != nil && key.Kind() != InternalKeyKindRangeKeySet && i.opts.SkipPoint(key.UserKey) {
|
||||
// NB: We could call prevUserKey, but in some cases the SkipPoint
|
||||
// predicate function might be cheaper than prevUserKey's key copy
|
||||
// and key comparison. This should be the case for MVCC suffix
|
||||
// comparisons, for example. In the future, we could expand the
|
||||
// SkipPoint interface to give the implementor more control over
|
||||
// whether we skip over just the internal key, the user key, or even
|
||||
// the key prefix.
|
||||
i.stats.ReverseStepCount[InternalIterCall]++
|
||||
i.iterKey, i.iterValue = i.iter.Prev()
|
||||
if limit != nil && i.iterKey != nil && i.cmp(limit, i.iterKey.UserKey) > 0 && !i.rangeKeyWithinLimit(limit) {
|
||||
i.iterValidityState = IterAtLimit
|
||||
i.pos = iterPosCurReversePaused
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindRangeKeySet:
|
||||
// Range key start boundary markers are interleaved with the maximum
|
||||
@@ -946,12 +995,12 @@ func (i *Iterator) findPrevEntry(limit []byte) {
|
||||
// Compare with the limit. We could optimize by only checking when
|
||||
// we step to the previous user key, but detecting that requires a
|
||||
// comparison too. Note that this position may already passed a
|
||||
// number of versions of this user key, but they are all deleted,
|
||||
// so the fact that a subsequent Prev*() call will not see them is
|
||||
// number of versions of this user key, but they are all deleted, so
|
||||
// the fact that a subsequent Prev*() call will not see them is
|
||||
// harmless. Also note that this is the only place in the loop,
|
||||
// other than the firstLoopIter case above, where we could step
|
||||
// to a different user key and start processing it for returning
|
||||
// to the caller.
|
||||
// other than the firstLoopIter and SkipPoint cases above, where we
|
||||
// could step to a different user key and start processing it for
|
||||
// returning to the caller.
|
||||
if limit != nil && i.iterKey != nil && i.cmp(limit, i.iterKey.UserKey) > 0 && !i.rangeKeyWithinLimit(limit) {
|
||||
i.iterValidityState = IterAtLimit
|
||||
i.pos = iterPosCurReversePaused
|
||||
@@ -2132,6 +2181,9 @@ func (i *Iterator) Value() []byte {
|
||||
|
||||
// ValueAndErr returns the value, and any error encountered in extracting the value.
|
||||
// REQUIRES: i.Error()==nil and HasPointAndRange() returns true for hasPoint.
|
||||
//
|
||||
// The caller should not modify the contents of the returned slice, and its
|
||||
// contents may change on the next call to Next.
|
||||
func (i *Iterator) ValueAndErr() ([]byte, error) {
|
||||
val, callerOwned, err := i.value.Value(i.lazyValueBuf)
|
||||
if err != nil {
|
||||
@@ -2162,7 +2214,13 @@ func (i *Iterator) RangeKeys() []RangeKeyData {
|
||||
// Valid returns true if the iterator is positioned at a valid key/value pair
|
||||
// and false otherwise.
|
||||
func (i *Iterator) Valid() bool {
|
||||
return i.iterValidityState == IterValid && !i.requiresReposition
|
||||
valid := i.iterValidityState == IterValid && !i.requiresReposition
|
||||
if invariants.Enabled {
|
||||
if err := i.Error(); valid && err != nil {
|
||||
panic(errors.WithSecondaryError(errors.AssertionFailedf("pebble: iterator is valid with non-nil Error"), err))
|
||||
}
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
// Error returns any accumulated error.
|
||||
@@ -2423,7 +2481,8 @@ func (i *Iterator) SetOptions(o *IterOptions) {
|
||||
// If either options specify block property filters for an iterator stack,
|
||||
// reconstruct it.
|
||||
if i.pointIter != nil && (closeBoth || len(o.PointKeyFilters) > 0 || len(i.opts.PointKeyFilters) > 0 ||
|
||||
o.RangeKeyMasking.Filter != nil || i.opts.RangeKeyMasking.Filter != nil) {
|
||||
o.RangeKeyMasking.Filter != nil || i.opts.RangeKeyMasking.Filter != nil || o.SkipPoint != nil ||
|
||||
i.opts.SkipPoint != nil) {
|
||||
i.err = firstError(i.err, i.pointIter.Close())
|
||||
i.pointIter = nil
|
||||
}
|
||||
@@ -2769,3 +2828,169 @@ func (stats *IteratorStats) SafeFormat(s redact.SafePrinter, verb rune) {
|
||||
stats.RangeKeyStats.SkippedPoints)
|
||||
}
|
||||
}
|
||||
|
||||
// CanDeterministicallySingleDelete takes a valid iterator and examines internal
|
||||
// state to determine if a SingleDelete deleting Iterator.Key() would
|
||||
// deterministically delete the key. CanDeterministicallySingleDelete requires
|
||||
// the iterator to be oriented in the forward direction (eg, the last
|
||||
// positioning operation must've been a First, a Seek[Prefix]GE, or a
|
||||
// Next[Prefix][WithLimit]).
|
||||
//
|
||||
// This function does not change the external position of the iterator, and all
|
||||
// positioning methods should behave the same as if it was never called. This
|
||||
// function will only return a meaningful result the first time it's invoked at
|
||||
// an iterator position. This function invalidates the iterator Value's memory,
|
||||
// and the caller must not rely on the memory safety of the previous Iterator
|
||||
// position.
|
||||
//
|
||||
// If CanDeterministicallySingleDelete returns true AND the key at the iterator
|
||||
// position is not modified between the creation of the Iterator and the commit
|
||||
// of a batch containing a SingleDelete over the key, then the caller can be
|
||||
// assured that SingleDelete is equivalent to Delete on the local engine, but it
|
||||
// may not be true on another engine that received the same writes and with
|
||||
// logically equivalent state since this engine may have collapsed multiple SETs
|
||||
// into one.
|
||||
func CanDeterministicallySingleDelete(it *Iterator) (bool, error) {
|
||||
// This function may only be called once per external iterator position. We
|
||||
// can validate this by checking the last positioning operation.
|
||||
if it.lastPositioningOp == internalNextOp {
|
||||
return false, errors.New("pebble: CanDeterministicallySingleDelete called twice")
|
||||
}
|
||||
validity, kind := it.internalNext()
|
||||
var shadowedBySingleDelete bool
|
||||
for validity == internalNextValid {
|
||||
switch kind {
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized:
|
||||
// A DEL or DELSIZED tombstone is okay. An internal key
|
||||
// sequence like SINGLEDEL; SET; DEL; SET can be handled
|
||||
// deterministically. If there are SETs further down, we
|
||||
// don't care about them.
|
||||
return true, nil
|
||||
case InternalKeyKindSingleDelete:
|
||||
// A SingleDelete is okay as long as when that SingleDelete was
|
||||
// written, it was written deterministically (eg, with its own
|
||||
// CanDeterministicallySingleDelete check). Validate that it was
|
||||
// written deterministically. We'll allow one set to appear after
|
||||
// the SingleDelete.
|
||||
shadowedBySingleDelete = true
|
||||
validity, kind = it.internalNext()
|
||||
continue
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete, InternalKeyKindMerge:
|
||||
// If we observed a single delete, it's allowed to delete 1 key.
|
||||
// We'll keep looping to validate that the internal keys beneath the
|
||||
// already-written single delete are copacetic.
|
||||
if shadowedBySingleDelete {
|
||||
shadowedBySingleDelete = false
|
||||
validity, kind = it.internalNext()
|
||||
continue
|
||||
}
|
||||
// We encountered a shadowed SET, SETWITHDEL, MERGE. A SINGLEDEL
|
||||
// that deleted the KV at the original iterator position could
|
||||
// result in this key becoming visible.
|
||||
return false, nil
|
||||
case InternalKeyKindRangeDelete:
|
||||
// RangeDeletes are handled by the merging iterator and should never
|
||||
// be observed by the top-level Iterator.
|
||||
panic(errors.AssertionFailedf("pebble: unexpected range delete"))
|
||||
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
// Range keys are interleaved at the maximal sequence number and
|
||||
// should never be observed within a user key.
|
||||
panic(errors.AssertionFailedf("pebble: unexpected range key"))
|
||||
default:
|
||||
panic(errors.AssertionFailedf("pebble: unexpected key kind: %s", errors.Safe(kind)))
|
||||
}
|
||||
}
|
||||
if validity == internalNextError {
|
||||
return false, it.Error()
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// internalNextValidity enumerates the potential outcomes of a call to
|
||||
// internalNext.
|
||||
type internalNextValidity int8
|
||||
|
||||
const (
|
||||
// internalNextError is returned by internalNext when an error occurred and
|
||||
// the caller is responsible for checking iter.Error().
|
||||
internalNextError internalNextValidity = iota
|
||||
// internalNextExhausted is returned by internalNext when the next internal
|
||||
// key is an internal key with a different user key than Iterator.Key().
|
||||
internalNextExhausted
|
||||
// internalNextValid is returned by internalNext when the internal next
|
||||
// found a shadowed internal key with a user key equal to Iterator.Key().
|
||||
internalNextValid
|
||||
)
|
||||
|
||||
// internalNext advances internal Iterator state forward to expose the
|
||||
// InternalKeyKind of the next internal key with a user key equal to Key().
|
||||
//
|
||||
// internalNext is a highly specialized operation and is unlikely to be
|
||||
// generally useful. See Iterator.Next for how to reposition the iterator to the
|
||||
// next key. internalNext requires the Iterator to be at a valid position in the
|
||||
// forward direction (the last positioning operation must've been a First, a
|
||||
// Seek[Prefix]GE, or a Next[Prefix][WithLimit] and Valid() must return true).
|
||||
//
|
||||
// internalNext, unlike all other Iterator methods, exposes internal LSM state.
|
||||
// internalNext advances the Iterator's internal iterator to the next shadowed
|
||||
// key with a user key equal to Key(). When a key is overwritten or deleted, its
|
||||
// removal from the LSM occurs lazily as a part of compactions. internalNext
|
||||
// allows the caller to see whether an obsolete internal key exists with the
|
||||
// current Key(), and what it's key kind is. Note that the existence of an
|
||||
// internal key is nondeterministic and dependent on internal LSM state. These
|
||||
// semantics are unlikely to be applicable to almost all use cases.
|
||||
//
|
||||
// If internalNext finds a key that shares the same user key as Key(), it
|
||||
// returns internalNextValid and the internal key's kind. If internalNext
|
||||
// encounters an error, it returns internalNextError and the caller is expected
|
||||
// to call Iterator.Error() to retrieve it. In all other circumstances,
|
||||
// internalNext returns internalNextExhausted, indicating that there are no more
|
||||
// additional internal keys with the user key Key().
|
||||
//
|
||||
// internalNext does not change the external position of the iterator, and a
|
||||
// Next operation should behave the same as if internalNext was never called.
|
||||
// internalNext does invalidate the iterator Value's memory, and the caller must
|
||||
// not rely on the memory safety of the previous Iterator position.
|
||||
func (i *Iterator) internalNext() (internalNextValidity, base.InternalKeyKind) {
|
||||
i.stats.ForwardStepCount[InterfaceCall]++
|
||||
if i.err != nil {
|
||||
return internalNextError, base.InternalKeyKindInvalid
|
||||
} else if i.iterValidityState != IterValid {
|
||||
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||
}
|
||||
i.lastPositioningOp = internalNextOp
|
||||
|
||||
switch i.pos {
|
||||
case iterPosCurForward:
|
||||
i.iterKey, i.iterValue = i.iter.Next()
|
||||
if i.iterKey == nil {
|
||||
// We check i.iter.Error() here and return an internalNextError enum
|
||||
// variant so that the caller does not need to check i.iter.Error()
|
||||
// in the common case that the next internal key has a new user key.
|
||||
if i.err = i.iter.Error(); i.err != nil {
|
||||
return internalNextError, base.InternalKeyKindInvalid
|
||||
}
|
||||
i.pos = iterPosNext
|
||||
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||
} else if i.comparer.Equal(i.iterKey.UserKey, i.key) {
|
||||
return internalNextValid, i.iterKey.Kind()
|
||||
}
|
||||
i.pos = iterPosNext
|
||||
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||
case iterPosCurReverse, iterPosCurReversePaused, iterPosPrev:
|
||||
i.err = errors.New("switching from reverse to forward via internalNext is prohibited")
|
||||
i.iterValidityState = IterExhausted
|
||||
return internalNextError, base.InternalKeyKindInvalid
|
||||
case iterPosNext, iterPosCurForwardPaused:
|
||||
// The previous method already moved onto the next user key. This is
|
||||
// only possible if
|
||||
// - the last positioning method was a call to internalNext, and we
|
||||
// advanced to a new user key.
|
||||
// - the previous non-internalNext iterator operation encountered a
|
||||
// range key or merge, forcing an internal Next that found a new
|
||||
// user key that's not equal to i.Iterator.Key().
|
||||
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
11
vendor/github.com/cockroachdb/pebble/level_checker.go
generated
vendored
11
vendor/github.com/cockroachdb/pebble/level_checker.go
generated
vendored
@@ -345,7 +345,7 @@ func iterateAndCheckTombstones(
|
||||
|
||||
type checkConfig struct {
|
||||
logger Logger
|
||||
cmp Compare
|
||||
comparer *Comparer
|
||||
readState *readState
|
||||
newIters tableNewIters
|
||||
seqNum uint64
|
||||
@@ -354,6 +354,9 @@ type checkConfig struct {
|
||||
formatKey base.FormatKey
|
||||
}
|
||||
|
||||
// cmp is shorthand for comparer.Compare.
|
||||
func (c *checkConfig) cmp(a, b []byte) int { return c.comparer.Compare(a, b) }
|
||||
|
||||
func checkRangeTombstones(c *checkConfig) error {
|
||||
var level int
|
||||
var tombstones []tombstoneWithLevel
|
||||
@@ -571,7 +574,7 @@ func (d *DB) CheckLevels(stats *CheckLevelsStats) error {
|
||||
|
||||
checkConfig := &checkConfig{
|
||||
logger: d.opts.Logger,
|
||||
cmp: d.cmp,
|
||||
comparer: d.opts.Comparer,
|
||||
readState: readState,
|
||||
newIters: d.newIters,
|
||||
seqNum: seqNum,
|
||||
@@ -639,7 +642,7 @@ func checkLevelsInternal(c *checkConfig) (err error) {
|
||||
manifestIter := current.L0SublevelFiles[sublevel].Iter()
|
||||
iterOpts := IterOptions{logger: c.logger}
|
||||
li := &levelIter{}
|
||||
li.init(context.Background(), iterOpts, c.cmp, nil /* split */, c.newIters, manifestIter,
|
||||
li.init(context.Background(), iterOpts, c.comparer, c.newIters, manifestIter,
|
||||
manifest.L0Sublevel(sublevel), internalIterOpts{})
|
||||
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
|
||||
li.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
|
||||
@@ -653,7 +656,7 @@ func checkLevelsInternal(c *checkConfig) (err error) {
|
||||
|
||||
iterOpts := IterOptions{logger: c.logger}
|
||||
li := &levelIter{}
|
||||
li.init(context.Background(), iterOpts, c.cmp, nil /* split */, c.newIters,
|
||||
li.init(context.Background(), iterOpts, c.comparer, c.newIters,
|
||||
current.Levels[level].Iter(), manifest.Level(level), internalIterOpts{})
|
||||
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
|
||||
li.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
|
||||
|
||||
22
vendor/github.com/cockroachdb/pebble/level_iter.go
generated
vendored
22
vendor/github.com/cockroachdb/pebble/level_iter.go
generated
vendored
@@ -82,10 +82,11 @@ type levelIter struct {
|
||||
// short-lived (since they pin sstables), (b) plumbing a context into every
|
||||
// method is very painful, (c) they do not (yet) respect context
|
||||
// cancellation and are only used for tracing.
|
||||
ctx context.Context
|
||||
logger Logger
|
||||
cmp Compare
|
||||
split Split
|
||||
ctx context.Context
|
||||
logger Logger
|
||||
comparer *Comparer
|
||||
cmp Compare
|
||||
split Split
|
||||
// The lower/upper bounds for iteration as specified at creation or the most
|
||||
// recent call to SetBounds.
|
||||
lower []byte
|
||||
@@ -241,15 +242,14 @@ var _ base.InternalIterator = (*levelIter)(nil)
|
||||
// parameter if the caller is never going to call SeekPrefixGE.
|
||||
func newLevelIter(
|
||||
opts IterOptions,
|
||||
cmp Compare,
|
||||
split Split,
|
||||
comparer *Comparer,
|
||||
newIters tableNewIters,
|
||||
files manifest.LevelIterator,
|
||||
level manifest.Level,
|
||||
internalOpts internalIterOpts,
|
||||
) *levelIter {
|
||||
l := &levelIter{}
|
||||
l.init(context.Background(), opts, cmp, split, newIters, files, level,
|
||||
l.init(context.Background(), opts, comparer, newIters, files, level,
|
||||
internalOpts)
|
||||
return l
|
||||
}
|
||||
@@ -257,8 +257,7 @@ func newLevelIter(
|
||||
func (l *levelIter) init(
|
||||
ctx context.Context,
|
||||
opts IterOptions,
|
||||
cmp Compare,
|
||||
split Split,
|
||||
comparer *Comparer,
|
||||
newIters tableNewIters,
|
||||
files manifest.LevelIterator,
|
||||
level manifest.Level,
|
||||
@@ -278,8 +277,9 @@ func (l *levelIter) init(
|
||||
l.tableOpts.UseL6Filters = opts.UseL6Filters
|
||||
l.tableOpts.level = l.level
|
||||
l.tableOpts.snapshotForHideObsoletePoints = opts.snapshotForHideObsoletePoints
|
||||
l.cmp = cmp
|
||||
l.split = split
|
||||
l.comparer = comparer
|
||||
l.cmp = comparer.Compare
|
||||
l.split = comparer.Split
|
||||
l.iterFile = nil
|
||||
l.newIters = newIters
|
||||
l.files = files
|
||||
|
||||
6
vendor/github.com/cockroachdb/pebble/mem_table.go
generated
vendored
6
vendor/github.com/cockroachdb/pebble/mem_table.go
generated
vendored
@@ -211,11 +211,13 @@ func (m *memTable) apply(batch *Batch, seqNum uint64) error {
|
||||
var tombstoneCount, rangeKeyCount uint32
|
||||
startSeqNum := seqNum
|
||||
for r := batch.Reader(); ; seqNum++ {
|
||||
kind, ukey, value, ok := r.Next()
|
||||
kind, ukey, value, ok, err := r.Next()
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
var err error
|
||||
ikey := base.MakeInternalKey(ukey, seqNum, kind)
|
||||
switch kind {
|
||||
case InternalKeyKindRangeDelete:
|
||||
|
||||
10
vendor/github.com/cockroachdb/pebble/merging_iter.go
generated
vendored
10
vendor/github.com/cockroachdb/pebble/merging_iter.go
generated
vendored
@@ -27,6 +27,10 @@ type mergingIterLevel struct {
|
||||
// iterKey and iterValue cache the current key and value iter are pointed at.
|
||||
iterKey *InternalKey
|
||||
iterValue base.LazyValue
|
||||
// levelIter is non-nil if this level's iter is ultimately backed by a
|
||||
// *levelIter. The handle in iter may have wrapped the levelIter with
|
||||
// intermediary internalIterator implementations.
|
||||
levelIter *levelIter
|
||||
|
||||
// levelIterBoundaryContext's fields are set when using levelIter, in order
|
||||
// to surface sstable boundary keys and file-level context. See levelIter
|
||||
@@ -1371,9 +1375,9 @@ func (m *mergingIter) DebugString() string {
|
||||
}
|
||||
|
||||
func (m *mergingIter) ForEachLevelIter(fn func(li *levelIter) bool) {
|
||||
for _, iter := range m.levels {
|
||||
if li, ok := iter.iter.(*levelIter); ok {
|
||||
if done := fn(li); done {
|
||||
for _, ml := range m.levels {
|
||||
if ml.levelIter != nil {
|
||||
if done := fn(ml.levelIter); done {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
143
vendor/github.com/cockroachdb/pebble/metrics.go
generated
vendored
143
vendor/github.com/cockroachdb/pebble/metrics.go
generated
vendored
@@ -44,9 +44,14 @@ type LevelMetrics struct {
|
||||
Sublevels int32
|
||||
// The total number of files in the level.
|
||||
NumFiles int64
|
||||
// The total number of virtual sstables in the level.
|
||||
NumVirtualFiles uint64
|
||||
// The total size in bytes of the files in the level.
|
||||
Size int64
|
||||
// The level's compaction score.
|
||||
// The total size of the virtual sstables in the level.
|
||||
VirtualSize uint64
|
||||
// The level's compaction score. This is the compensatedScoreRatio in the
|
||||
// candidateLevelInfo.
|
||||
Score float64
|
||||
// The number of incoming bytes from other levels read during
|
||||
// compactions. This excludes bytes moved and bytes ingested. For L0 this is
|
||||
@@ -77,6 +82,18 @@ type LevelMetrics struct {
|
||||
TablesIngested uint64
|
||||
// The number of sstables moved to this level by a "move" compaction.
|
||||
TablesMoved uint64
|
||||
|
||||
MultiLevel struct {
|
||||
// BytesInTop are the total bytes in a multilevel compaction coming from the top level.
|
||||
BytesInTop uint64
|
||||
|
||||
// BytesIn, exclusively for multiLevel compactions.
|
||||
BytesIn uint64
|
||||
|
||||
// BytesRead, exclusively for multilevel compactions.
|
||||
BytesRead uint64
|
||||
}
|
||||
|
||||
// Additional contains misc additional metrics that are not always printed.
|
||||
Additional struct {
|
||||
// The sum of Properties.ValueBlocksSize for all the sstables in this
|
||||
@@ -94,6 +111,8 @@ type LevelMetrics struct {
|
||||
// Add updates the counter metrics for the level.
|
||||
func (m *LevelMetrics) Add(u *LevelMetrics) {
|
||||
m.NumFiles += u.NumFiles
|
||||
m.NumVirtualFiles += u.NumVirtualFiles
|
||||
m.VirtualSize += u.VirtualSize
|
||||
m.Size += u.Size
|
||||
m.BytesIn += u.BytesIn
|
||||
m.BytesIngested += u.BytesIngested
|
||||
@@ -105,6 +124,9 @@ func (m *LevelMetrics) Add(u *LevelMetrics) {
|
||||
m.TablesFlushed += u.TablesFlushed
|
||||
m.TablesIngested += u.TablesIngested
|
||||
m.TablesMoved += u.TablesMoved
|
||||
m.MultiLevel.BytesInTop += u.MultiLevel.BytesInTop
|
||||
m.MultiLevel.BytesRead += u.MultiLevel.BytesRead
|
||||
m.MultiLevel.BytesIn += u.MultiLevel.BytesIn
|
||||
m.Additional.BytesWrittenDataBlocks += u.Additional.BytesWrittenDataBlocks
|
||||
m.Additional.BytesWrittenValueBlocks += u.Additional.BytesWrittenValueBlocks
|
||||
m.Additional.ValueBlocksSize += u.Additional.ValueBlocksSize
|
||||
@@ -130,14 +152,15 @@ type Metrics struct {
|
||||
|
||||
Compact struct {
|
||||
// The total number of compactions, and per-compaction type counts.
|
||||
Count int64
|
||||
DefaultCount int64
|
||||
DeleteOnlyCount int64
|
||||
ElisionOnlyCount int64
|
||||
MoveCount int64
|
||||
ReadCount int64
|
||||
RewriteCount int64
|
||||
MultiLevelCount int64
|
||||
Count int64
|
||||
DefaultCount int64
|
||||
DeleteOnlyCount int64
|
||||
ElisionOnlyCount int64
|
||||
MoveCount int64
|
||||
ReadCount int64
|
||||
RewriteCount int64
|
||||
MultiLevelCount int64
|
||||
CounterLevelCount int64
|
||||
// An estimate of the number of bytes that need to be compacted for the LSM
|
||||
// to reach a stable state.
|
||||
EstimatedDebt uint64
|
||||
@@ -236,6 +259,10 @@ type Metrics struct {
|
||||
ZombieSize uint64
|
||||
// The count of zombie tables.
|
||||
ZombieCount int64
|
||||
// The count of the backing sstables.
|
||||
BackingTableCount uint64
|
||||
// The sum of the sizes of the all of the backing sstables.
|
||||
BackingTableSize uint64
|
||||
}
|
||||
|
||||
TableCache CacheMetrics
|
||||
@@ -284,6 +311,13 @@ var (
|
||||
prometheus.LinearBuckets(0.0, float64(time.Microsecond*100), 50),
|
||||
prometheus.ExponentialBucketsRange(float64(time.Millisecond*5), float64(10*time.Second), 50)...,
|
||||
)
|
||||
|
||||
// SecondaryCacheIOBuckets exported to enable exporting from package pebble to
|
||||
// enable exporting metrics with below buckets in CRDB.
|
||||
SecondaryCacheIOBuckets = sharedcache.IOBuckets
|
||||
// SecondaryCacheChannelWriteBuckets exported to enable exporting from package
|
||||
// pebble to enable exporting metrics with below buckets in CRDB.
|
||||
SecondaryCacheChannelWriteBuckets = sharedcache.ChannelWriteBuckets
|
||||
)
|
||||
|
||||
// DiskSpaceUsage returns the total disk space used by the database in bytes,
|
||||
@@ -303,6 +337,27 @@ func (m *Metrics) DiskSpaceUsage() uint64 {
|
||||
return usageBytes
|
||||
}
|
||||
|
||||
// NumVirtual is the number of virtual sstables in the latest version
|
||||
// summed over every level in the lsm.
|
||||
func (m *Metrics) NumVirtual() uint64 {
|
||||
var n uint64
|
||||
for _, level := range m.Levels {
|
||||
n += level.NumVirtualFiles
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// VirtualSize is the sum of the sizes of the virtual sstables in the
|
||||
// latest version. BackingTableSize - VirtualSize gives an estimate for
|
||||
// the space amplification caused by not compacting virtual sstables.
|
||||
func (m *Metrics) VirtualSize() uint64 {
|
||||
var size uint64
|
||||
for _, level := range m.Levels {
|
||||
size += level.VirtualSize
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// ReadAmp returns the current read amplification of the database.
|
||||
// It's computed as the number of sublevels in L0 + the number of non-empty
|
||||
// levels below L0.
|
||||
@@ -333,24 +388,25 @@ func (m *Metrics) Total() LevelMetrics {
|
||||
|
||||
// String pretty-prints the metrics as below:
|
||||
//
|
||||
// | | | | ingested | moved | written | | amp
|
||||
// level | tables size val-bl | score | in | tables size | tables size | tables size | read | r w
|
||||
// ------+---------------------+-------+-------+--------------+--------------+--------------+-------+---------
|
||||
// 0 | 101 102B 0B | 103.0 | 104B | 112 104B | 113 106B | 221 217B | 107B | 1 2.1
|
||||
// 1 | 201 202B 0B | 203.0 | 204B | 212 204B | 213 206B | 421 417B | 207B | 2 2.0
|
||||
// 2 | 301 302B 0B | 303.0 | 304B | 312 304B | 313 306B | 621 617B | 307B | 3 2.0
|
||||
// 3 | 401 402B 0B | 403.0 | 404B | 412 404B | 413 406B | 821 817B | 407B | 4 2.0
|
||||
// 4 | 501 502B 0B | 503.0 | 504B | 512 504B | 513 506B | 1.0K 1017B | 507B | 5 2.0
|
||||
// 5 | 601 602B 0B | 603.0 | 604B | 612 604B | 613 606B | 1.2K 1.2KB | 607B | 6 2.0
|
||||
// 6 | 701 702B 0B | - | 704B | 712 704B | 713 706B | 1.4K 1.4KB | 707B | 7 2.0
|
||||
// total | 2.8K 2.7KB 0B | - | 2.8KB | 2.9K 2.8KB | 2.9K 2.8KB | 5.7K 8.4KB | 2.8KB | 28 3.0
|
||||
// -----------------------------------------------------------------------------------------------------------
|
||||
// | | | | ingested | moved | written | | amp
|
||||
// level | tables size val-bl vtables | score | in | tables size | tables size | tables size | read | r w
|
||||
// ------+-----------------------------+-------+-------+--------------+--------------+--------------+-------+---------
|
||||
// 0 | 101 102B 0B 0 | 103.0 | 104B | 112 104B | 113 106B | 221 217B | 107B | 1 2.1
|
||||
// 1 | 201 202B 0B 0 | 203.0 | 204B | 212 204B | 213 206B | 421 417B | 207B | 2 2.0
|
||||
// 2 | 301 302B 0B 0 | 303.0 | 304B | 312 304B | 313 306B | 621 617B | 307B | 3 2.0
|
||||
// 3 | 401 402B 0B 0 | 403.0 | 404B | 412 404B | 413 406B | 821 817B | 407B | 4 2.0
|
||||
// 4 | 501 502B 0B 0 | 503.0 | 504B | 512 504B | 513 506B | 1.0K 1017B | 507B | 5 2.0
|
||||
// 5 | 601 602B 0B 0 | 603.0 | 604B | 612 604B | 613 606B | 1.2K 1.2KB | 607B | 6 2.0
|
||||
// 6 | 701 702B 0B 0 | - | 704B | 712 704B | 713 706B | 1.4K 1.4KB | 707B | 7 2.0
|
||||
// total | 2.8K 2.7KB 0B 0 | - | 2.8KB | 2.9K 2.8KB | 2.9K 2.8KB | 5.7K 8.4KB | 2.8KB | 28 3.0
|
||||
// -------------------------------------------------------------------------------------------------------------------
|
||||
// WAL: 22 files (24B) in: 25B written: 26B (4% overhead)
|
||||
// Flushes: 8
|
||||
// Compactions: 5 estimated debt: 6B in progress: 2 (7B)
|
||||
// default: 27 delete: 28 elision: 29 move: 30 read: 31 rewrite: 32 multi-level: 33
|
||||
// MemTables: 12 (11B) zombie: 14 (13B)
|
||||
// Zombie tables: 16 (15B)
|
||||
// Backing tables: 0 (0B)
|
||||
// Block cache: 2 entries (1B) hit rate: 42.9%
|
||||
// Table cache: 18 entries (17B) hit rate: 48.7%
|
||||
// Secondary cache: 40 entries (40B) hit rate: 49.9%
|
||||
@@ -378,9 +434,25 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
// width specifiers. When the issue is fixed, we can convert these to
|
||||
// RedactableStrings. https://github.com/cockroachdb/redact/issues/17
|
||||
|
||||
w.SafeString(" | | | | ingested | moved | written | | amp\n")
|
||||
w.SafeString("level | tables size val-bl | score | in | tables size | tables size | tables size | read | r w\n")
|
||||
w.SafeString("------+---------------------+-------+-------+--------------+--------------+--------------+-------+---------\n")
|
||||
multiExists := m.Compact.MultiLevelCount > 0
|
||||
appendIfMulti := func(line redact.SafeString) {
|
||||
if multiExists {
|
||||
w.SafeString(line)
|
||||
}
|
||||
}
|
||||
newline := func() {
|
||||
w.SafeString("\n")
|
||||
}
|
||||
|
||||
w.SafeString(" | | | | ingested | moved | written | | amp")
|
||||
appendIfMulti(" | multilevel")
|
||||
newline()
|
||||
w.SafeString("level | tables size val-bl vtables | score | in | tables size | tables size | tables size | read | r w")
|
||||
appendIfMulti(" | top in read")
|
||||
newline()
|
||||
w.SafeString("------+-----------------------------+-------+-------+--------------+--------------+--------------+-------+---------")
|
||||
appendIfMulti("-+------------------")
|
||||
newline()
|
||||
|
||||
// formatRow prints out a row of the table.
|
||||
formatRow := func(m *LevelMetrics, score float64) {
|
||||
@@ -403,10 +475,11 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
wampStr = fmt.Sprintf("%.1f", wamp)
|
||||
}
|
||||
|
||||
w.Printf("| %5s %6s %6s | %5s | %5s | %5s %6s | %5s %6s | %5s %6s | %5s | %3d %4s\n",
|
||||
w.Printf("| %5s %6s %6s %7s | %5s | %5s | %5s %6s | %5s %6s | %5s %6s | %5s | %3d %4s",
|
||||
humanize.Count.Int64(m.NumFiles),
|
||||
humanize.Bytes.Int64(m.Size),
|
||||
humanize.Bytes.Uint64(m.Additional.ValueBlocksSize),
|
||||
humanize.Count.Uint64(m.NumVirtualFiles),
|
||||
redact.Safe(scoreStr),
|
||||
humanize.Bytes.Uint64(m.BytesIn),
|
||||
humanize.Count.Uint64(m.TablesIngested),
|
||||
@@ -418,6 +491,14 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
humanize.Bytes.Uint64(m.BytesRead),
|
||||
redact.Safe(m.Sublevels),
|
||||
redact.Safe(wampStr))
|
||||
|
||||
if multiExists {
|
||||
w.Printf(" | %5s %5s %5s",
|
||||
humanize.Bytes.Uint64(m.MultiLevel.BytesInTop),
|
||||
humanize.Bytes.Uint64(m.MultiLevel.BytesIn),
|
||||
humanize.Bytes.Uint64(m.MultiLevel.BytesRead))
|
||||
}
|
||||
newline()
|
||||
}
|
||||
|
||||
var total LevelMetrics
|
||||
@@ -443,8 +524,9 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.SafeString("total ")
|
||||
formatRow(&total, math.NaN())
|
||||
|
||||
w.SafeString("-----------------------------------------------------------------------------------------------------------\n")
|
||||
|
||||
w.SafeString("-------------------------------------------------------------------------------------------------------------------")
|
||||
appendIfMulti("--------------------")
|
||||
newline()
|
||||
w.Printf("WAL: %d files (%s) in: %s written: %s (%.0f%% overhead)\n",
|
||||
redact.Safe(m.WAL.Files),
|
||||
humanize.Bytes.Uint64(m.WAL.Size),
|
||||
@@ -479,6 +561,13 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
redact.Safe(m.Table.ZombieCount),
|
||||
humanize.Bytes.Uint64(m.Table.ZombieSize))
|
||||
|
||||
w.Printf("Backing tables: %d (%s)\n",
|
||||
redact.Safe(m.Table.BackingTableCount),
|
||||
humanize.Bytes.Uint64(m.Table.BackingTableSize))
|
||||
w.Printf("Virtual tables: %d (%s)\n",
|
||||
redact.Safe(m.NumVirtual()),
|
||||
humanize.Bytes.Uint64(m.VirtualSize()))
|
||||
|
||||
formatCacheMetrics := func(m *CacheMetrics, name redact.SafeString) {
|
||||
w.Printf("%s: %s entries (%s) hit rate: %.1f%%\n",
|
||||
name,
|
||||
|
||||
8
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/provider.go
generated
vendored
8
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/provider.go
generated
vendored
@@ -97,10 +97,10 @@ type Settings struct {
|
||||
Remote struct {
|
||||
StorageFactory remote.StorageFactory
|
||||
|
||||
// If CreateOnShared is true, sstables are created on remote storage using
|
||||
// If CreateOnShared is non-zero, sstables are created on remote storage using
|
||||
// the CreateOnSharedLocator (when the PreferSharedStorage create option is
|
||||
// true).
|
||||
CreateOnShared bool
|
||||
CreateOnShared remote.CreateOnSharedStrategy
|
||||
CreateOnSharedLocator remote.Locator
|
||||
|
||||
// CacheSizeBytes is the size of the on-disk block cache for objects
|
||||
@@ -249,7 +249,7 @@ func (p *provider) Create(
|
||||
fileNum base.DiskFileNum,
|
||||
opts objstorage.CreateOptions,
|
||||
) (w objstorage.Writable, meta objstorage.ObjectMetadata, err error) {
|
||||
if opts.PreferSharedStorage && p.st.Remote.CreateOnShared {
|
||||
if opts.PreferSharedStorage && p.st.Remote.CreateOnShared != remote.CreateOnSharedNone {
|
||||
w, meta, err = p.sharedCreate(ctx, fileType, fileNum, p.st.Remote.CreateOnSharedLocator, opts)
|
||||
} else {
|
||||
w, meta, err = p.vfsCreate(ctx, fileType, fileNum)
|
||||
@@ -338,7 +338,7 @@ func (p *provider) LinkOrCopyFromLocal(
|
||||
dstFileNum base.DiskFileNum,
|
||||
opts objstorage.CreateOptions,
|
||||
) (objstorage.ObjectMetadata, error) {
|
||||
shared := opts.PreferSharedStorage && p.st.Remote.CreateOnShared
|
||||
shared := opts.PreferSharedStorage && p.st.Remote.CreateOnShared != remote.CreateOnSharedNone
|
||||
if !shared && srcFS == p.st.FS {
|
||||
// Wrap the normal filesystem with one which wraps newly created files with
|
||||
// vfs.NewSyncingFile.
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
@@ -234,14 +235,18 @@ func (v *VersionEdit) Apply(
|
||||
*creatorID = v.CreatorID
|
||||
}
|
||||
for _, meta := range v.NewObjects {
|
||||
if _, exists := objects[meta.FileNum]; exists {
|
||||
return errors.AssertionFailedf("version edit adds existing object %s", meta.FileNum)
|
||||
if invariants.Enabled {
|
||||
if _, exists := objects[meta.FileNum]; exists {
|
||||
return errors.AssertionFailedf("version edit adds existing object %s", meta.FileNum)
|
||||
}
|
||||
}
|
||||
objects[meta.FileNum] = meta
|
||||
}
|
||||
for _, fileNum := range v.DeletedObjects {
|
||||
if _, exists := objects[fileNum]; !exists {
|
||||
return errors.AssertionFailedf("version edit deletes non-existent object %s", fileNum)
|
||||
if invariants.Enabled {
|
||||
if _, exists := objects[fileNum]; !exists {
|
||||
return errors.AssertionFailedf("version edit deletes non-existent object %s", fileNum)
|
||||
}
|
||||
}
|
||||
delete(objects, fileNum)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,13 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// Exported to enable exporting from package pebble to enable
|
||||
// exporting metrics with below buckets in CRDB.
|
||||
var (
|
||||
IOBuckets = prometheus.ExponentialBucketsRange(float64(time.Millisecond*1), float64(10*time.Second), 50)
|
||||
ChannelWriteBuckets = prometheus.ExponentialBucketsRange(float64(time.Microsecond*1), float64(10*time.Second), 50)
|
||||
)
|
||||
|
||||
// Cache is a persistent cache backed by a local filesystem. It is intended
|
||||
// to cache data that is in slower shared storage (e.g. S3), hence the
|
||||
// package name 'sharedcache'.
|
||||
@@ -123,9 +130,10 @@ func Open(
|
||||
sizeBytes int64,
|
||||
numShards int,
|
||||
) (*Cache, error) {
|
||||
min := shardingBlockSize * int64(numShards)
|
||||
if sizeBytes < min {
|
||||
return nil, errors.Errorf("cache size %d lower than min %d", sizeBytes, min)
|
||||
if minSize := shardingBlockSize * int64(numShards); sizeBytes < minSize {
|
||||
// Up the size so that we have one block per shard. In practice, this should
|
||||
// only happen in tests.
|
||||
sizeBytes = minSize
|
||||
}
|
||||
|
||||
c := &Cache{
|
||||
@@ -143,15 +151,13 @@ func Open(
|
||||
|
||||
c.writeWorkers.Start(c, numShards*writeWorkersPerShard)
|
||||
|
||||
buckets := prometheus.ExponentialBucketsRange(float64(time.Millisecond*1), float64(10*time.Second), 50)
|
||||
c.metrics.getLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: buckets})
|
||||
c.metrics.diskReadLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: buckets})
|
||||
c.metrics.putLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: buckets})
|
||||
c.metrics.diskWriteLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: buckets})
|
||||
c.metrics.getLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||
c.metrics.diskReadLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||
c.metrics.putLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||
c.metrics.diskWriteLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||
|
||||
// Measures a channel write, so lower min.
|
||||
buckets = prometheus.ExponentialBucketsRange(float64(time.Microsecond*1), float64(10*time.Second), 50)
|
||||
c.metrics.queuePutLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: buckets})
|
||||
c.metrics.queuePutLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: ChannelWriteBuckets})
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
36
vendor/github.com/cockroachdb/pebble/objstorage/remote/storage.go
generated
vendored
36
vendor/github.com/cockroachdb/pebble/objstorage/remote/storage.go
generated
vendored
@@ -22,6 +22,42 @@ type StorageFactory interface {
|
||||
CreateStorage(locator Locator) (Storage, error)
|
||||
}
|
||||
|
||||
// SharedLevelsStart denotes the highest (i.e. lowest numbered) level that will
|
||||
// have sstables shared across Pebble instances when doing skip-shared
|
||||
// iteration (see db.ScanInternal) or shared file ingestion (see
|
||||
// db.IngestAndExcise).
|
||||
const SharedLevelsStart = 5
|
||||
|
||||
// CreateOnSharedStrategy specifies what table files should be created on shared
|
||||
// storage. For use with CreateOnShared in options.
|
||||
type CreateOnSharedStrategy int
|
||||
|
||||
const (
|
||||
// CreateOnSharedNone denotes no files being created on shared storage.
|
||||
CreateOnSharedNone CreateOnSharedStrategy = iota
|
||||
// CreateOnSharedLower denotes the creation of files in lower levels of the
|
||||
// LSM (specifically, L5 and L6 as they're below SharedLevelsStart) on
|
||||
// shared storage, and higher levels on local storage.
|
||||
CreateOnSharedLower
|
||||
// CreateOnSharedAll denotes the creation of all sstables on shared storage.
|
||||
CreateOnSharedAll
|
||||
)
|
||||
|
||||
// ShouldCreateShared returns whether new table files at the specified level
|
||||
// should be created on shared storage.
|
||||
func ShouldCreateShared(strategy CreateOnSharedStrategy, level int) bool {
|
||||
switch strategy {
|
||||
case CreateOnSharedAll:
|
||||
return true
|
||||
case CreateOnSharedNone:
|
||||
return false
|
||||
case CreateOnSharedLower:
|
||||
return level >= SharedLevelsStart
|
||||
default:
|
||||
panic("unexpected CreateOnSharedStrategy value")
|
||||
}
|
||||
}
|
||||
|
||||
// Storage is an interface for a blob storage driver. This is lower-level
|
||||
// than an FS-like interface, however FS/File-like abstractions can be built on
|
||||
// top of these methods.
|
||||
|
||||
52
vendor/github.com/cockroachdb/pebble/open.go
generated
vendored
52
vendor/github.com/cockroachdb/pebble/open.go
generated
vendored
@@ -89,7 +89,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
||||
// Open the database and WAL directories first.
|
||||
walDirname, dataDir, walDir, err := prepareAndOpenDirs(dirname, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrapf(err, "error opening database at %q", dirname)
|
||||
}
|
||||
defer func() {
|
||||
if db == nil {
|
||||
@@ -260,7 +260,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
||||
}
|
||||
|
||||
// Create the DB.
|
||||
if err := d.mu.versions.create(jobID, dirname, opts, manifestMarker, setCurrent, &d.mu.Mutex); err != nil {
|
||||
if err := d.mu.versions.create(jobID, dirname, opts, manifestMarker, setCurrent, d.FormatMajorVersion, &d.mu.Mutex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
@@ -268,7 +268,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
||||
return nil, errors.Wrapf(ErrDBAlreadyExists, "dirname=%q", dirname)
|
||||
}
|
||||
// Load the version set.
|
||||
if err := d.mu.versions.load(dirname, opts, manifestFileNum.FileNum(), manifestMarker, setCurrent, &d.mu.Mutex); err != nil {
|
||||
if err := d.mu.versions.load(dirname, opts, manifestFileNum.FileNum(), manifestMarker, setCurrent, d.FormatMajorVersion, &d.mu.Mutex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.ErrorIfNotPristine {
|
||||
@@ -381,6 +381,15 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Ratchet d.mu.versions.nextFileNum ahead of all known objects in the
|
||||
// objProvider. This avoids FileNum collisions with obsolete sstables.
|
||||
objects := d.objProvider.List()
|
||||
for _, obj := range objects {
|
||||
if d.mu.versions.nextFileNum <= obj.DiskFileNum.FileNum() {
|
||||
d.mu.versions.nextFileNum = obj.DiskFileNum.FileNum() + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the most-recent OPTIONS file, if there is one.
|
||||
var strictWALTail bool
|
||||
if previousOptionsFilename != "" {
|
||||
@@ -700,6 +709,11 @@ func (d *DB) replayWAL(
|
||||
batchesReplayed int64 // number of batches replayed
|
||||
)
|
||||
|
||||
// TODO(jackson): This function is interspersed with panics, in addition to
|
||||
// corruption error propagation. Audit them to ensure we're truly only
|
||||
// panicking where the error points to Pebble bug and not user or
|
||||
// hardware-induced corruption.
|
||||
|
||||
if d.opts.ReadOnly {
|
||||
// In read-only mode, we replay directly into the mutable memtable which will
|
||||
// never be flushed.
|
||||
@@ -754,6 +768,11 @@ func (d *DB) replayWAL(
|
||||
ve.NewFiles = append(ve.NewFiles, newVE.NewFiles...)
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = errors.WithDetailf(err, "replaying log %s, offset %d", logNum, offset)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
offset = rr.Offset()
|
||||
@@ -786,7 +805,8 @@ func (d *DB) replayWAL(
|
||||
|
||||
// Specify Batch.db so that Batch.SetRepr will compute Batch.memTableSize
|
||||
// which is used below.
|
||||
b = Batch{db: d}
|
||||
b = Batch{}
|
||||
b.db = d
|
||||
b.SetRepr(buf.Bytes())
|
||||
seqNum := b.SeqNum()
|
||||
maxSeqNum = seqNum + uint64(b.Count())
|
||||
@@ -794,7 +814,9 @@ func (d *DB) replayWAL(
|
||||
batchesReplayed++
|
||||
{
|
||||
br := b.Reader()
|
||||
if kind, encodedFileNum, _, _ := br.Next(); kind == InternalKeyKindIngestSST {
|
||||
if kind, encodedFileNum, _, ok, err := br.Next(); err != nil {
|
||||
return nil, 0, err
|
||||
} else if ok && kind == InternalKeyKindIngestSST {
|
||||
fileNums := make([]base.DiskFileNum, 0, b.Count())
|
||||
addFileNum := func(encodedFileNum []byte) {
|
||||
fileNum, n := binary.Uvarint(encodedFileNum)
|
||||
@@ -806,7 +828,10 @@ func (d *DB) replayWAL(
|
||||
addFileNum(encodedFileNum)
|
||||
|
||||
for i := 1; i < int(b.Count()); i++ {
|
||||
kind, encodedFileNum, _, ok := br.Next()
|
||||
kind, encodedFileNum, _, ok, err := br.Next()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if kind != InternalKeyKindIngestSST {
|
||||
panic("pebble: invalid batch key kind.")
|
||||
}
|
||||
@@ -816,7 +841,9 @@ func (d *DB) replayWAL(
|
||||
addFileNum(encodedFileNum)
|
||||
}
|
||||
|
||||
if _, _, _, ok := br.Next(); ok {
|
||||
if _, _, _, ok, err := br.Next(); err != nil {
|
||||
return nil, 0, err
|
||||
} else if ok {
|
||||
panic("pebble: invalid number of entries in batch.")
|
||||
}
|
||||
|
||||
@@ -909,7 +936,10 @@ func (d *DB) replayWAL(
|
||||
// Make a copy of the data slice since it is currently owned by buf and will
|
||||
// be reused in the next iteration.
|
||||
b.data = append([]byte(nil), b.data...)
|
||||
b.flushable = newFlushableBatch(&b, d.opts.Comparer)
|
||||
b.flushable, err = newFlushableBatch(&b, d.opts.Comparer)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
entry := d.newFlushableEntry(b.flushable, logNum, b.SeqNum())
|
||||
// Disable memory accounting by adding a reader ref that will never be
|
||||
// removed.
|
||||
@@ -1106,6 +1136,12 @@ var ErrDBAlreadyExists = errors.New("pebble: database already exists")
|
||||
// Note that errors can be wrapped with more details; use errors.Is().
|
||||
var ErrDBNotPristine = errors.New("pebble: database already exists and is not pristine")
|
||||
|
||||
// IsCorruptionError returns true if the given error indicates database
|
||||
// corruption.
|
||||
func IsCorruptionError(err error) bool {
|
||||
return errors.Is(err, base.ErrCorruption)
|
||||
}
|
||||
|
||||
func checkConsistency(v *manifest.Version, dirname string, objProvider objstorage.Provider) error {
|
||||
var buf bytes.Buffer
|
||||
var args []interface{}
|
||||
|
||||
89
vendor/github.com/cockroachdb/pebble/options.go
generated
vendored
89
vendor/github.com/cockroachdb/pebble/options.go
generated
vendored
@@ -119,6 +119,16 @@ type IterOptions struct {
|
||||
// false to skip scanning. This function must be thread-safe since the same
|
||||
// function can be used by multiple iterators, if the iterator is cloned.
|
||||
TableFilter func(userProps map[string]string) bool
|
||||
// SkipPoint may be used to skip over point keys that don't match an
|
||||
// arbitrary predicate during iteration. If set, the Iterator invokes
|
||||
// SkipPoint for keys encountered. If SkipPoint returns true, the iterator
|
||||
// will skip the key without yielding it to the iterator operation in
|
||||
// progress.
|
||||
//
|
||||
// SkipPoint must be a pure function and always return the same result when
|
||||
// provided the same arguments. The iterator may call SkipPoint multiple
|
||||
// times for the same user key.
|
||||
SkipPoint func(userKey []byte) bool
|
||||
// PointKeyFilters can be used to avoid scanning tables and blocks in tables
|
||||
// when iterating over point keys. This slice represents an intersection
|
||||
// across all filters, i.e., all filters must indicate that the block is
|
||||
@@ -540,6 +550,11 @@ type Options struct {
|
||||
// concurrency slots as determined by the two options is chosen.
|
||||
CompactionDebtConcurrency uint64
|
||||
|
||||
// IngestSplit, if it returns true, allows for ingest-time splitting of
|
||||
// existing sstables into two virtual sstables to allow ingestion sstables to
|
||||
// slot into a lower level than they otherwise would have.
|
||||
IngestSplit func() bool
|
||||
|
||||
// ReadCompactionRate controls the frequency of read triggered
|
||||
// compactions by adjusting `AllowedSeeks` in manifest.FileMetadata:
|
||||
//
|
||||
@@ -603,10 +618,10 @@ type Options struct {
|
||||
// desired size of each level of the LSM. Defaults to 10.
|
||||
LevelMultiplier int
|
||||
|
||||
// MultiLevelCompactionHueristic determines whether to add an additional
|
||||
// MultiLevelCompactionHeuristic determines whether to add an additional
|
||||
// level to a conventional two level compaction. If nil, a multilevel
|
||||
// compaction will never get triggered.
|
||||
MultiLevelCompactionHueristic MultiLevelHeuristic
|
||||
MultiLevelCompactionHeuristic MultiLevelHeuristic
|
||||
|
||||
// MaxWriterConcurrency is used to indicate the maximum number of
|
||||
// compression workers the compression queue is allowed to use. If
|
||||
@@ -670,19 +685,44 @@ type Options struct {
|
||||
// allows ingestion of external files.
|
||||
RemoteStorage remote.StorageFactory
|
||||
|
||||
// If CreateOnShared is true, any new sstables are created on remote storage
|
||||
// (using CreateOnSharedLocator). These sstables can be shared between
|
||||
// different Pebble instances; the lifecycle of such objects is managed by
|
||||
// the cluster.
|
||||
// If CreateOnShared is non-zero, new sstables are created on remote storage
|
||||
// (using CreateOnSharedLocator and with the appropriate
|
||||
// CreateOnSharedStrategy). These sstables can be shared between different
|
||||
// Pebble instances; the lifecycle of such objects is managed by the
|
||||
// remote.Storage constructed by options.RemoteStorage.
|
||||
//
|
||||
// Can only be used when RemoteStorage is set (and recognizes
|
||||
// CreateOnSharedLocator).
|
||||
CreateOnShared bool
|
||||
CreateOnShared remote.CreateOnSharedStrategy
|
||||
CreateOnSharedLocator remote.Locator
|
||||
|
||||
// CacheSizeBytesBytes is the size of the on-disk block cache for objects
|
||||
// on shared storage in bytes. If it is 0, no cache is used.
|
||||
SecondaryCacheSizeBytes int64
|
||||
|
||||
// IneffectualPointDeleteCallback is called in compactions/flushes if any
|
||||
// single delete is being elided without deleting a point set/merge.
|
||||
IneffectualSingleDeleteCallback func(userKey []byte)
|
||||
|
||||
// SingleDeleteInvariantViolationCallback is called in compactions/flushes if any
|
||||
// single delete has consumed a Set/Merge, and there is another immediately older
|
||||
// Set/SetWithDelete/Merge. The user of Pebble has violated the invariant under
|
||||
// which SingleDelete can be used correctly.
|
||||
//
|
||||
// Consider the sequence SingleDelete#3, Set#2, Set#1. There are three
|
||||
// ways some of these keys can first meet in a compaction.
|
||||
//
|
||||
// - All 3 keys in the same compaction: this callback will detect the
|
||||
// violation.
|
||||
//
|
||||
// - SingleDelete#3, Set#2 meet in a compaction first: Both keys will
|
||||
// disappear. The violation will not be detected, and the DB will have
|
||||
// Set#1 which is likely incorrect (from the user's perspective).
|
||||
//
|
||||
// - Set#2, Set#1 meet in a compaction first: The output will be Set#2,
|
||||
// which will later be consumed by SingleDelete#3. The violation will
|
||||
// not be detected and the DB will be correct.
|
||||
SingleDeleteInvariantViolationCallback func(userKey []byte)
|
||||
}
|
||||
|
||||
// Filters is a map from filter policy name to filter policy. It is used for
|
||||
@@ -797,12 +837,18 @@ type Options struct {
|
||||
// writing the contents of the old one in the
|
||||
// background. MemTableStopWritesThreshold places a hard limit on the size of
|
||||
// the queued MemTables.
|
||||
//
|
||||
// The default value is 4MB.
|
||||
MemTableSize uint64
|
||||
|
||||
// Hard limit on the size of queued of MemTables. Writes are stopped when the
|
||||
// sum of the queued memtable sizes exceeds
|
||||
// MemTableStopWritesThreshold*MemTableSize. This value should be at least 2
|
||||
// or writes will stop whenever a MemTable is being flushed.
|
||||
// Hard limit on the number of queued of MemTables. Writes are stopped when
|
||||
// the sum of the queued memtable sizes exceeds:
|
||||
// MemTableStopWritesThreshold * MemTableSize.
|
||||
//
|
||||
// This value should be at least 2 or writes will stop whenever a MemTable is
|
||||
// being flushed.
|
||||
//
|
||||
// The default value is 2.
|
||||
MemTableStopWritesThreshold int
|
||||
|
||||
// Merger defines the associative merge operation to use for merging values
|
||||
@@ -934,6 +980,10 @@ type Options struct {
|
||||
// A private option to disable stats collection.
|
||||
disableTableStats bool
|
||||
|
||||
// testingAlwaysWaitForCleanup is set by some tests to force waiting for
|
||||
// obsolete file deletion (to make events deterministic).
|
||||
testingAlwaysWaitForCleanup bool
|
||||
|
||||
// fsCloser holds a closer that should be invoked after a DB using these
|
||||
// Options is closed. This is used to automatically stop the
|
||||
// long-running goroutine associated with the disk-health-checking FS.
|
||||
@@ -1045,7 +1095,7 @@ func (o *Options) EnsureDefaults() *Options {
|
||||
o.MaxOpenFiles = 1000
|
||||
}
|
||||
if o.MemTableSize <= 0 {
|
||||
o.MemTableSize = 4 << 20
|
||||
o.MemTableSize = 4 << 20 // 4 MB
|
||||
}
|
||||
if o.MemTableStopWritesThreshold <= 0 {
|
||||
o.MemTableStopWritesThreshold = 2
|
||||
@@ -1086,8 +1136,8 @@ func (o *Options) EnsureDefaults() *Options {
|
||||
if o.Experimental.CPUWorkPermissionGranter == nil {
|
||||
o.Experimental.CPUWorkPermissionGranter = defaultCPUWorkGranter{}
|
||||
}
|
||||
if o.Experimental.MultiLevelCompactionHueristic == nil {
|
||||
o.Experimental.MultiLevelCompactionHueristic = NoMultiLevel{}
|
||||
if o.Experimental.MultiLevelCompactionHeuristic == nil {
|
||||
o.Experimental.MultiLevelCompactionHeuristic = WriteAmpHeuristic{}
|
||||
}
|
||||
|
||||
o.initMaps()
|
||||
@@ -1228,6 +1278,7 @@ func (o *Options) String() string {
|
||||
fmt.Fprintf(&buf, " max_writer_concurrency=%d\n", o.Experimental.MaxWriterConcurrency)
|
||||
fmt.Fprintf(&buf, " force_writer_parallelism=%t\n", o.Experimental.ForceWriterParallelism)
|
||||
fmt.Fprintf(&buf, " secondary_cache_size_bytes=%d\n", o.Experimental.SecondaryCacheSizeBytes)
|
||||
fmt.Fprintf(&buf, " create_on_shared=%d\n", o.Experimental.CreateOnShared)
|
||||
|
||||
// Private options.
|
||||
//
|
||||
@@ -1283,7 +1334,11 @@ func parseOptions(s string, fn func(section, key, value string) error) error {
|
||||
|
||||
pos := strings.Index(line, "=")
|
||||
if pos < 0 {
|
||||
return errors.Errorf("pebble: invalid key=value syntax: %s", errors.Safe(line))
|
||||
const maxLen = 50
|
||||
if len(line) > maxLen {
|
||||
line = line[:maxLen-3] + "..."
|
||||
}
|
||||
return base.CorruptionErrorf("invalid key=value syntax: %q", errors.Safe(line))
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(line[:pos])
|
||||
@@ -1499,6 +1554,10 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
|
||||
o.Experimental.ForceWriterParallelism, err = strconv.ParseBool(value)
|
||||
case "secondary_cache_size_bytes":
|
||||
o.Experimental.SecondaryCacheSizeBytes, err = strconv.ParseInt(value, 10, 64)
|
||||
case "create_on_shared":
|
||||
var createOnSharedInt int64
|
||||
createOnSharedInt, err = strconv.ParseInt(value, 10, 64)
|
||||
o.Experimental.CreateOnShared = remote.CreateOnSharedStrategy(createOnSharedInt)
|
||||
default:
|
||||
if hooks != nil && hooks.SkipUnknown != nil && hooks.SkipUnknown(section+"."+key, value) {
|
||||
return nil
|
||||
|
||||
2
vendor/github.com/cockroachdb/pebble/range_keys.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/range_keys.go
generated
vendored
@@ -17,7 +17,7 @@ import (
|
||||
func (i *Iterator) constructRangeKeyIter() {
|
||||
i.rangeKey.rangeKeyIter = i.rangeKey.iterConfig.Init(
|
||||
&i.comparer, i.seqNum, i.opts.LowerBound, i.opts.UpperBound,
|
||||
&i.hasPrefix, &i.prefixOrFullSeekKey, true /* onlySets */, &i.rangeKey.rangeKeyBuffers.internal)
|
||||
&i.hasPrefix, &i.prefixOrFullSeekKey, false /* internalKeys */, &i.rangeKey.rangeKeyBuffers.internal)
|
||||
|
||||
// If there's an indexed batch with range keys, include it.
|
||||
if i.batch != nil {
|
||||
|
||||
19
vendor/github.com/cockroachdb/pebble/scan_internal.go
generated
vendored
19
vendor/github.com/cockroachdb/pebble/scan_internal.go
generated
vendored
@@ -14,12 +14,13 @@ import (
|
||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||
"github.com/cockroachdb/pebble/internal/manifest"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
|
||||
const (
|
||||
// In skip-shared iteration mode, keys in levels sharedLevelsStart and greater
|
||||
// (i.e. lower in the LSM) are skipped.
|
||||
sharedLevelsStart = 5
|
||||
sharedLevelsStart = remote.SharedLevelsStart
|
||||
)
|
||||
|
||||
// ErrInvalidSkipSharedIteration is returned by ScanInternal if it was called
|
||||
@@ -661,7 +662,17 @@ func scanInternalImpl(
|
||||
case InternalKeyKindRangeKeyDelete, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeySet:
|
||||
if opts.visitRangeKey != nil {
|
||||
span := iter.unsafeSpan()
|
||||
if err := opts.visitRangeKey(span.Start, span.End, span.Keys); err != nil {
|
||||
// NB: The caller isn't interested in the sequence numbers of these
|
||||
// range keys. Rather, the caller wants them to be in trailer order
|
||||
// _after_ zeroing of sequence numbers. Copy span.Keys, sort it, and then
|
||||
// call visitRangeKey.
|
||||
keysCopy := make([]keyspan.Key, len(span.Keys))
|
||||
for i := range span.Keys {
|
||||
keysCopy[i] = span.Keys[i]
|
||||
keysCopy[i].Trailer = base.MakeTrailer(0, span.Keys[i].Kind())
|
||||
}
|
||||
keyspan.SortKeysByTrailer(&keysCopy)
|
||||
if err := opts.visitRangeKey(span.Start, span.End, keysCopy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -763,7 +774,7 @@ func (i *scanInternalIterator) constructPointIter(memtables flushableList, buf *
|
||||
rli := &rangeDelLevels[levelsIndex]
|
||||
|
||||
li.init(
|
||||
context.Background(), i.opts.IterOptions, i.comparer.Compare, i.comparer.Split, i.newIters, files, level,
|
||||
context.Background(), i.opts.IterOptions, i.comparer, i.newIters, files, level,
|
||||
internalIterOpts{})
|
||||
li.initBoundaryContext(&mlevels[mlevelsIndex].levelIterBoundaryContext)
|
||||
mlevels[mlevelsIndex].iter = li
|
||||
@@ -832,7 +843,7 @@ func (i *scanInternalIterator) constructRangeKeyIter() {
|
||||
// RangeKeyUnsets and RangeKeyDels.
|
||||
i.rangeKey.rangeKeyIter = i.rangeKey.iterConfig.Init(
|
||||
i.comparer, i.seqNum, i.opts.LowerBound, i.opts.UpperBound,
|
||||
nil /* hasPrefix */, nil /* prefix */, false, /* onlySets */
|
||||
nil /* hasPrefix */, nil /* prefix */, true, /* internalKeys */
|
||||
&i.rangeKey.rangeKeyBuffers.internal)
|
||||
|
||||
// Next are the flushables: memtables and large batches.
|
||||
|
||||
73
vendor/github.com/cockroachdb/pebble/snapshot.go
generated
vendored
73
vendor/github.com/cockroachdb/pebble/snapshot.go
generated
vendored
@@ -205,7 +205,7 @@ func (l *snapshotList) remove(s *Snapshot) {
|
||||
}
|
||||
|
||||
// EventuallyFileOnlySnapshot (aka EFOS) provides a read-only point-in-time view
|
||||
// of the database state, similar to Snapshot. A EventuallyFileOnlySnapshot
|
||||
// of the database state, similar to Snapshot. An EventuallyFileOnlySnapshot
|
||||
// induces less write amplification than Snapshot, at the cost of increased space
|
||||
// amplification. While a Snapshot may increase write amplification across all
|
||||
// flushes and compactions for the duration of its lifetime, an
|
||||
@@ -225,28 +225,21 @@ func (l *snapshotList) remove(s *Snapshot) {
|
||||
// with zero write-amp and zero pinning of memtables in memory.
|
||||
//
|
||||
// EventuallyFileOnlySnapshots interact with the IngestAndExcise operation in
|
||||
// subtle ways. Unlike Snapshots, EFOS guarantees that their read-only
|
||||
// point-in-time view is unaltered by the excision. However, if a concurrent
|
||||
// excise were to happen on one of the protectedRanges, WaitForFileOnlySnapshot()
|
||||
// would return ErrSnapshotExcised and the EFOS would maintain a reference to the
|
||||
// underlying readState (and by extension, zombie memtables) for its lifetime.
|
||||
// This could lead to increased memory utilization, which is why callers should
|
||||
// call WaitForFileOnlySnapshot() if they expect an EFOS to be long-lived.
|
||||
// subtle ways. No new iterators can be created once
|
||||
// EventuallyFileOnlySnapshot.excised is set to true.
|
||||
type EventuallyFileOnlySnapshot struct {
|
||||
mu struct {
|
||||
// NB: If both this mutex and db.mu are being grabbed, db.mu should be
|
||||
// grabbed _before_ grabbing this one.
|
||||
sync.Mutex
|
||||
|
||||
// Either the {snap,readState} fields are set below, or the version is set at
|
||||
// any given point of time. If a snapshot is referenced, this is not a
|
||||
// file-only snapshot yet, and if a version is set (and ref'd) this is a
|
||||
// file-only snapshot.
|
||||
// Either the snap field is set below, or the version is set at any given
|
||||
// point of time. If a snapshot is referenced, this is not a file-only
|
||||
// snapshot yet, and if a version is set (and ref'd) this is a file-only
|
||||
// snapshot.
|
||||
|
||||
// The wrapped regular snapshot, if not a file-only snapshot yet. The
|
||||
// readState has already been ref()d once if it's set.
|
||||
snap *Snapshot
|
||||
readState *readState
|
||||
// The wrapped regular snapshot, if not a file-only snapshot yet.
|
||||
snap *Snapshot
|
||||
// The wrapped version reference, if a file-only snapshot.
|
||||
vers *version
|
||||
}
|
||||
@@ -296,7 +289,6 @@ func (d *DB) makeEventuallyFileOnlySnapshot(
|
||||
}
|
||||
s.efos = es
|
||||
es.mu.snap = s
|
||||
es.mu.readState = d.loadReadState()
|
||||
d.mu.snapshots.pushBack(s)
|
||||
}
|
||||
return es
|
||||
@@ -328,33 +320,11 @@ func (es *EventuallyFileOnlySnapshot) transitionToFileOnlySnapshot(vers *version
|
||||
es.mu.vers = vers
|
||||
// NB: The callers should have already done a check of es.excised.
|
||||
oldSnap := es.mu.snap
|
||||
oldReadState := es.mu.readState
|
||||
es.mu.snap = nil
|
||||
es.mu.readState = nil
|
||||
es.mu.Unlock()
|
||||
// It's okay to close a snapshot even if iterators are already open on it.
|
||||
oldReadState.unrefLocked()
|
||||
return oldSnap.closeLocked()
|
||||
}
|
||||
|
||||
// releaseReadState is called to release reference to a readState when
|
||||
// es.excised == true. This is to free up memory as quickly as possible; all
|
||||
// other snapshot resources are kept around until Close() is called. Safe for
|
||||
// idempotent calls.
|
||||
//
|
||||
// d.mu must be held when calling this method.
|
||||
func (es *EventuallyFileOnlySnapshot) releaseReadState() {
|
||||
if !es.excised.Load() {
|
||||
panic("pebble: releasing read state of eventually-file-only-snapshot but was not excised")
|
||||
}
|
||||
es.mu.Lock()
|
||||
defer es.mu.Unlock()
|
||||
if es.mu.readState != nil {
|
||||
es.mu.readState.unrefLocked()
|
||||
es.db.maybeScheduleObsoleteTableDeletionLocked()
|
||||
}
|
||||
}
|
||||
|
||||
// hasTransitioned returns true if this EFOS has transitioned to a file-only
|
||||
// snapshot.
|
||||
func (es *EventuallyFileOnlySnapshot) hasTransitioned() bool {
|
||||
@@ -448,10 +418,6 @@ func (es *EventuallyFileOnlySnapshot) Close() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if es.mu.readState != nil {
|
||||
es.mu.readState.unrefLocked()
|
||||
es.db.maybeScheduleObsoleteTableDeletionLocked()
|
||||
}
|
||||
if es.mu.vers != nil {
|
||||
es.mu.vers.UnrefLocked()
|
||||
}
|
||||
@@ -511,8 +477,16 @@ func (es *EventuallyFileOnlySnapshot) NewIterWithContext(
|
||||
if es.excised.Load() {
|
||||
return nil, ErrSnapshotExcised
|
||||
}
|
||||
sOpts := snapshotIterOpts{seqNum: es.seqNum, readState: es.mu.readState}
|
||||
return es.db.newIter(ctx, nil /* batch */, sOpts, o), nil
|
||||
sOpts := snapshotIterOpts{seqNum: es.seqNum}
|
||||
iter := es.db.newIter(ctx, nil /* batch */, sOpts, o)
|
||||
|
||||
// If excised is true, then keys relevant to the snapshot might not be
|
||||
// present in the readState being used by the iterator. Error out.
|
||||
if es.excised.Load() {
|
||||
iter.Close()
|
||||
return nil, ErrSnapshotExcised
|
||||
}
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
// ScanInternal scans all internal keys within the specified bounds, truncating
|
||||
@@ -544,8 +518,7 @@ func (es *EventuallyFileOnlySnapshot) ScanInternal(
|
||||
}
|
||||
} else {
|
||||
sOpts = snapshotIterOpts{
|
||||
seqNum: es.seqNum,
|
||||
readState: es.mu.readState,
|
||||
seqNum: es.seqNum,
|
||||
}
|
||||
}
|
||||
es.mu.Unlock()
|
||||
@@ -564,5 +537,11 @@ func (es *EventuallyFileOnlySnapshot) ScanInternal(
|
||||
iter := es.db.newInternalIter(sOpts, opts)
|
||||
defer iter.close()
|
||||
|
||||
// If excised is true, then keys relevant to the snapshot might not be
|
||||
// present in the readState being used by the iterator. Error out.
|
||||
if es.excised.Load() {
|
||||
return ErrSnapshotExcised
|
||||
}
|
||||
|
||||
return scanInternalImpl(ctx, lower, upper, iter, opts)
|
||||
}
|
||||
|
||||
24
vendor/github.com/cockroachdb/pebble/sstable/block_property.go
generated
vendored
24
vendor/github.com/cockroachdb/pebble/sstable/block_property.go
generated
vendored
@@ -9,6 +9,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/rangekey"
|
||||
@@ -643,15 +644,21 @@ func (f *BlockPropertiesFilterer) intersectsUserPropsAndFinishInit(
|
||||
// considered intersecting.
|
||||
continue
|
||||
}
|
||||
byteProps := []byte(props)
|
||||
if len(byteProps) < 1 {
|
||||
if len(props) < 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
|
||||
shortID := shortID(props[0])
|
||||
{
|
||||
// Use an unsafe conversion to avoid allocating. Intersects() is not
|
||||
// supposed to modify the given slice.
|
||||
// Note that unsafe.StringData only works if the string is not empty
|
||||
// (which we already checked).
|
||||
byteProps := unsafe.Slice(unsafe.StringData(props), len(props))
|
||||
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.
|
||||
@@ -684,12 +691,11 @@ func (f *BlockPropertiesFilterer) intersectsUserPropsAndFinishInit(
|
||||
// be unused within this file.
|
||||
return true, nil
|
||||
}
|
||||
byteProps := []byte(props)
|
||||
if len(byteProps) < 1 {
|
||||
if len(props) < 1 {
|
||||
return false, base.CorruptionErrorf(
|
||||
"block properties for %s is corrupted", f.boundLimitedFilter.Name())
|
||||
}
|
||||
f.boundLimitedShortID = int(byteProps[0])
|
||||
f.boundLimitedShortID = int(props[0])
|
||||
|
||||
// We don't check for table-level intersection for the bound-limited filter.
|
||||
// The bound-limited filter is treated as vacuously intersecting.
|
||||
|
||||
139
vendor/github.com/cockroachdb/pebble/sstable/properties.go
generated
vendored
139
vendor/github.com/cockroachdb/pebble/sstable/properties.go
generated
vendored
@@ -25,10 +25,20 @@ var propBoolFalse = []byte{'0'}
|
||||
|
||||
var propOffsetTagMap = make(map[uintptr]string)
|
||||
|
||||
func init() {
|
||||
t := reflect.TypeOf(Properties{})
|
||||
func generateTagMaps(t reflect.Type, indexPrefix []int) {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.Type.Kind() == reflect.Struct {
|
||||
if tag := f.Tag.Get("prop"); i == 0 && tag == "pebble.embbeded_common_properties" {
|
||||
// CommonProperties struct embedded in Properties. Note that since
|
||||
// CommonProperties is placed at the top of properties we can use
|
||||
// the offsets of the fields within CommonProperties to determine
|
||||
// the offsets of those fields within Properties.
|
||||
generateTagMaps(f.Type, []int{i})
|
||||
continue
|
||||
}
|
||||
panic("pebble: unknown struct type in Properties")
|
||||
}
|
||||
if tag := f.Tag.Get("prop"); tag != "" {
|
||||
switch f.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
@@ -38,16 +48,84 @@ func init() {
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported property field type: %s %s", f.Name, f.Type))
|
||||
}
|
||||
if len(indexPrefix) > 0 {
|
||||
// Prepend the index prefix so that we can use FieldByIndex on the top-level struct.
|
||||
f.Index = append(indexPrefix[:len(indexPrefix):len(indexPrefix)], f.Index...)
|
||||
}
|
||||
propTagMap[tag] = f
|
||||
propOffsetTagMap[f.Offset] = tag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
generateTagMaps(reflect.TypeOf(Properties{}), nil)
|
||||
}
|
||||
|
||||
// CommonProperties holds properties for either a virtual or a physical sstable. This
|
||||
// can be used by code which doesn't care to make the distinction between physical
|
||||
// and virtual sstables properties.
|
||||
//
|
||||
// For virtual sstables, fields are constructed through extrapolation upon virtual
|
||||
// reader construction. See MakeVirtualReader for implementation details.
|
||||
//
|
||||
// NB: The values of these properties can affect correctness. For example,
|
||||
// if NumRangeKeySets == 0, but the sstable actually contains range keys, then
|
||||
// the iterators will behave incorrectly.
|
||||
type CommonProperties struct {
|
||||
// The number of entries in this table.
|
||||
NumEntries uint64 `prop:"rocksdb.num.entries"`
|
||||
// Total raw key size.
|
||||
RawKeySize uint64 `prop:"rocksdb.raw.key.size"`
|
||||
// Total raw value size.
|
||||
RawValueSize uint64 `prop:"rocksdb.raw.value.size"`
|
||||
// Total raw key size of point deletion tombstones. This value is comparable
|
||||
// to RawKeySize.
|
||||
RawPointTombstoneKeySize uint64 `prop:"pebble.raw.point-tombstone.key.size"`
|
||||
// Sum of the raw value sizes carried by point deletion tombstones
|
||||
// containing size estimates. See the DeleteSized key kind. This value is
|
||||
// comparable to Raw{Key,Value}Size.
|
||||
RawPointTombstoneValueSize uint64 `prop:"pebble.raw.point-tombstone.value.size"`
|
||||
// The number of point deletion entries ("tombstones") in this table that
|
||||
// carry a size hint indicating the size of the value the tombstone deletes.
|
||||
NumSizedDeletions uint64 `prop:"pebble.num.deletions.sized"`
|
||||
// The number of deletion entries in this table, including both point and
|
||||
// range deletions.
|
||||
NumDeletions uint64 `prop:"rocksdb.deleted.keys"`
|
||||
// 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"`
|
||||
// Total size of value blocks and value index block. Only serialized if > 0.
|
||||
ValueBlocksSize uint64 `prop:"pebble.value-blocks.size"`
|
||||
}
|
||||
|
||||
// String is only used for testing purposes.
|
||||
func (c *CommonProperties) String() string {
|
||||
var buf bytes.Buffer
|
||||
v := reflect.ValueOf(*c)
|
||||
loaded := make(map[uintptr]struct{})
|
||||
writeProperties(loaded, v, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// NumPointDeletions is the number of point deletions in the sstable. For virtual
|
||||
// sstables, this is an estimate.
|
||||
func (c *CommonProperties) NumPointDeletions() uint64 {
|
||||
return c.NumDeletions - c.NumRangeDeletions
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// CommonProperties needs to be at the top of the Properties struct so that the
|
||||
// offsets of the fields in CommonProperties match the offsets of the embedded
|
||||
// fields of CommonProperties in Properties.
|
||||
CommonProperties `prop:"pebble.embbeded_common_properties"`
|
||||
|
||||
// The name of the comparer used in this table.
|
||||
ComparerName string `prop:"rocksdb.comparator"`
|
||||
// The compression algorithm used to compress blocks.
|
||||
@@ -81,22 +159,8 @@ type Properties struct {
|
||||
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 point deletion entries ("tombstones") in this table that
|
||||
// carry a size hint indicating the size of the value the tombstone deletes.
|
||||
NumSizedDeletions uint64 `prop:"pebble.num.deletions.sized"`
|
||||
// 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"`
|
||||
// The number of value blocks in this table. Only serialized if > 0.
|
||||
@@ -111,21 +175,10 @@ type Properties struct {
|
||||
// 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 key size of point deletion tombstones. This value is comparable
|
||||
// to RawKeySize.
|
||||
RawPointTombstoneKeySize uint64 `prop:"pebble.raw.point-tombstone.key.size"`
|
||||
// Sum of the raw value sizes carried by point deletion tombstones
|
||||
// containing size estimates. See the DeleteSized key kind. This value is
|
||||
// comparable to Raw{Key,Value}Size.
|
||||
RawPointTombstoneValueSize uint64 `prop:"pebble.raw.point-tombstone.value.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"`
|
||||
// The total number of keys in this table that were pinned by open snapshots.
|
||||
SnapshotPinnedKeys uint64 `prop:"pebble.num.snapshot-pinned-keys"`
|
||||
// The cumulative bytes of keys in this table that were pinned by
|
||||
@@ -138,8 +191,6 @@ type Properties struct {
|
||||
TopLevelIndexSize uint64 `prop:"rocksdb.top-level.index.size"`
|
||||
// User collected properties.
|
||||
UserProperties map[string]string
|
||||
// Total size of value blocks and value index block. Only serialized if > 0.
|
||||
ValueBlocksSize uint64 `prop:"pebble.value-blocks.size"`
|
||||
// If filtering is enabled, was the filter created on the whole key.
|
||||
WholeKeyFiltering bool `prop:"rocksdb.block.based.table.whole.key.filtering"`
|
||||
|
||||
@@ -160,12 +211,15 @@ 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)
|
||||
func writeProperties(loaded map[uintptr]struct{}, v reflect.Value, buf *bytes.Buffer) {
|
||||
vt := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
ft := vt.Field(i)
|
||||
if ft.Type.Kind() == reflect.Struct {
|
||||
// Embedded struct within the properties.
|
||||
writeProperties(loaded, v.Field(i), buf)
|
||||
continue
|
||||
}
|
||||
tag := ft.Tag.Get("prop")
|
||||
if tag == "" {
|
||||
continue
|
||||
@@ -175,25 +229,33 @@ func (p *Properties) String() string {
|
||||
// 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 {
|
||||
if _, ok := loaded[ft.Offset]; !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(&buf, "%s: ", tag)
|
||||
fmt.Fprintf(buf, "%s: ", tag)
|
||||
switch ft.Type.Kind() {
|
||||
case reflect.Bool:
|
||||
fmt.Fprintf(&buf, "%t\n", f.Bool())
|
||||
fmt.Fprintf(buf, "%t\n", f.Bool())
|
||||
case reflect.Uint32:
|
||||
fmt.Fprintf(&buf, "%d\n", f.Uint())
|
||||
fmt.Fprintf(buf, "%d\n", f.Uint())
|
||||
case reflect.Uint64:
|
||||
fmt.Fprintf(&buf, "%d\n", f.Uint())
|
||||
fmt.Fprintf(buf, "%d\n", f.Uint())
|
||||
case reflect.String:
|
||||
fmt.Fprintf(&buf, "%s\n", f.String())
|
||||
fmt.Fprintf(buf, "%s\n", f.String())
|
||||
default:
|
||||
panic("not reached")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Properties) String() string {
|
||||
var buf bytes.Buffer
|
||||
v := reflect.ValueOf(*p)
|
||||
writeProperties(p.Loaded, v, &buf)
|
||||
|
||||
// Write the UserProperties.
|
||||
keys := make([]string, 0, len(p.UserProperties))
|
||||
for key := range p.UserProperties {
|
||||
keys = append(keys, key)
|
||||
@@ -214,6 +276,7 @@ func (p *Properties) load(
|
||||
}
|
||||
p.Loaded = make(map[uintptr]struct{})
|
||||
v := reflect.ValueOf(p).Elem()
|
||||
|
||||
for valid := i.First(); valid; valid = i.Next() {
|
||||
if f, ok := propTagMap[string(i.Key().UserKey)]; ok {
|
||||
p.Loaded[f.Offset] = struct{}{}
|
||||
|
||||
31
vendor/github.com/cockroachdb/pebble/sstable/reader.go
generated
vendored
31
vendor/github.com/cockroachdb/pebble/sstable/reader.go
generated
vendored
@@ -174,6 +174,28 @@ func init() {
|
||||
private.SSTableRawTombstonesOpt = rawTombstonesOpt{}
|
||||
}
|
||||
|
||||
// CommonReader abstracts functionality over a Reader or a VirtualReader. This
|
||||
// can be used by code which doesn't care to distinguish between a reader and a
|
||||
// virtual reader.
|
||||
type CommonReader interface {
|
||||
NewRawRangeKeyIter() (keyspan.FragmentIterator, error)
|
||||
NewRawRangeDelIter() (keyspan.FragmentIterator, error)
|
||||
NewIterWithBlockPropertyFiltersAndContextEtc(
|
||||
ctx context.Context, lower, upper []byte,
|
||||
filterer *BlockPropertiesFilterer,
|
||||
hideObsoletePoints, useFilterBlock bool,
|
||||
stats *base.InternalIteratorStats,
|
||||
rp ReaderProvider,
|
||||
) (Iterator, error)
|
||||
NewCompactionIter(
|
||||
bytesIterated *uint64,
|
||||
rp ReaderProvider,
|
||||
bufferPool *BufferPool,
|
||||
) (Iterator, error)
|
||||
EstimateDiskUsage(start, end []byte) (uint64, error)
|
||||
CommonProperties() *CommonProperties
|
||||
}
|
||||
|
||||
// Reader is a table reader.
|
||||
type Reader struct {
|
||||
readable objstorage.Readable
|
||||
@@ -337,7 +359,7 @@ func (r *Reader) newCompactionIter(
|
||||
err := i.init(
|
||||
context.Background(),
|
||||
r, v, nil /* lower */, nil /* upper */, nil,
|
||||
false /* useFilter */, false, /* hideObsoletePoints */
|
||||
false /* useFilter */, v != nil && v.isForeign, /* hideObsoletePoints */
|
||||
nil /* stats */, rp, bufferPool,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -352,7 +374,7 @@ func (r *Reader) newCompactionIter(
|
||||
i := singleLevelIterPool.Get().(*singleLevelIterator)
|
||||
err := i.init(
|
||||
context.Background(), r, v, nil /* lower */, nil, /* upper */
|
||||
nil, false /* useFilter */, false, /* hideObsoletePoints */
|
||||
nil, false /* useFilter */, v != nil && v.isForeign, /* hideObsoletePoints */
|
||||
nil /* stats */, rp, bufferPool,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -927,6 +949,11 @@ func (r *Reader) ValidateBlockChecksums() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommonProperties implemented the CommonReader interface.
|
||||
func (r *Reader) CommonProperties() *CommonProperties {
|
||||
return &r.Properties.CommonProperties
|
||||
}
|
||||
|
||||
// EstimateDiskUsage returns the total size of data blocks overlapping the range
|
||||
// `[start, end]`. Even if a data block partially overlaps, or we cannot
|
||||
// determine overlap due to abbreviated index keys, the full data block size is
|
||||
|
||||
5
vendor/github.com/cockroachdb/pebble/sstable/reader_iter_two_lvl.go
generated
vendored
5
vendor/github.com/cockroachdb/pebble/sstable/reader_iter_two_lvl.go
generated
vendored
@@ -810,6 +810,11 @@ func (i *twoLevelIterator) NextPrefix(succKey []byte) (*InternalKey, base.LazyVa
|
||||
if key, val := i.singleLevelIterator.NextPrefix(succKey); key != nil {
|
||||
return key, val
|
||||
}
|
||||
// key == nil
|
||||
if i.err != nil {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
|
||||
// Did not find prefix in the existing second-level index block. This is the
|
||||
// slow-path where we seek the iterator.
|
||||
var ikey *InternalKey
|
||||
|
||||
63
vendor/github.com/cockroachdb/pebble/sstable/reader_virtual.go
generated
vendored
63
vendor/github.com/cockroachdb/pebble/sstable/reader_virtual.go
generated
vendored
@@ -22,46 +22,58 @@ import (
|
||||
type VirtualReader struct {
|
||||
vState virtualState
|
||||
reader *Reader
|
||||
Properties struct {
|
||||
// RawKeySize, RawValueSize are set upon construction of a
|
||||
// VirtualReader. The values of the fields is extrapolated. See
|
||||
// MakeVirtualReader for implementation details.
|
||||
RawKeySize uint64
|
||||
RawValueSize uint64
|
||||
}
|
||||
Properties CommonProperties
|
||||
}
|
||||
|
||||
// Lightweight virtual sstable state which can be passed to sstable iterators.
|
||||
type virtualState struct {
|
||||
lower InternalKey
|
||||
upper InternalKey
|
||||
fileNum base.FileNum
|
||||
Compare Compare
|
||||
lower InternalKey
|
||||
upper InternalKey
|
||||
fileNum base.FileNum
|
||||
Compare Compare
|
||||
isForeign bool
|
||||
}
|
||||
|
||||
func ceilDiv(a, b uint64) uint64 {
|
||||
return (a + b - 1) / b
|
||||
}
|
||||
|
||||
// MakeVirtualReader is used to contruct a reader which can read from virtual
|
||||
// sstables.
|
||||
func MakeVirtualReader(reader *Reader, meta manifest.VirtualFileMeta) VirtualReader {
|
||||
func MakeVirtualReader(
|
||||
reader *Reader, meta manifest.VirtualFileMeta, isForeign bool,
|
||||
) VirtualReader {
|
||||
if reader.fileNum != meta.FileBacking.DiskFileNum {
|
||||
panic("pebble: invalid call to MakeVirtualReader")
|
||||
}
|
||||
|
||||
vState := virtualState{
|
||||
lower: meta.Smallest,
|
||||
upper: meta.Largest,
|
||||
fileNum: meta.FileNum,
|
||||
Compare: reader.Compare,
|
||||
lower: meta.Smallest,
|
||||
upper: meta.Largest,
|
||||
fileNum: meta.FileNum,
|
||||
Compare: reader.Compare,
|
||||
isForeign: isForeign,
|
||||
}
|
||||
v := VirtualReader{
|
||||
vState: vState,
|
||||
reader: reader,
|
||||
}
|
||||
|
||||
v.Properties.RawKeySize =
|
||||
(reader.Properties.RawKeySize * meta.Size) / meta.FileBacking.Size
|
||||
v.Properties.RawValueSize =
|
||||
(reader.Properties.RawValueSize * meta.Size) / meta.FileBacking.Size
|
||||
v.Properties.RawKeySize = ceilDiv(reader.Properties.RawKeySize*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.RawValueSize = ceilDiv(reader.Properties.RawValueSize*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumEntries = ceilDiv(reader.Properties.NumEntries*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumDeletions = ceilDiv(reader.Properties.NumDeletions*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumRangeDeletions = ceilDiv(reader.Properties.NumRangeDeletions*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumRangeKeyDels = ceilDiv(reader.Properties.NumRangeKeyDels*meta.Size, meta.FileBacking.Size)
|
||||
|
||||
// Note that we rely on NumRangeKeySets for correctness. If the sstable may
|
||||
// contain range keys, then NumRangeKeySets must be > 0. ceilDiv works because
|
||||
// meta.Size will not be 0 for virtual sstables.
|
||||
v.Properties.NumRangeKeySets = ceilDiv(reader.Properties.NumRangeKeySets*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.ValueBlocksSize = ceilDiv(reader.Properties.ValueBlocksSize*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.NumSizedDeletions = ceilDiv(reader.Properties.NumSizedDeletions*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.RawPointTombstoneKeySize = ceilDiv(reader.Properties.RawPointTombstoneKeySize*meta.Size, meta.FileBacking.Size)
|
||||
v.Properties.RawPointTombstoneValueSize = ceilDiv(reader.Properties.RawPointTombstoneValueSize*meta.Size, meta.FileBacking.Size)
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -89,6 +101,12 @@ func (v *VirtualReader) NewIterWithBlockPropertyFiltersAndContextEtc(
|
||||
)
|
||||
}
|
||||
|
||||
// ValidateBlockChecksumsOnBacking will call ValidateBlockChecksumsOnBacking on the underlying reader.
|
||||
// Note that block checksum validation is NOT restricted to virtual sstable bounds.
|
||||
func (v *VirtualReader) ValidateBlockChecksumsOnBacking() error {
|
||||
return v.reader.ValidateBlockChecksums()
|
||||
}
|
||||
|
||||
// NewRawRangeDelIter wraps Reader.NewRawRangeDelIter.
|
||||
func (v *VirtualReader) NewRawRangeDelIter() (keyspan.FragmentIterator, error) {
|
||||
iter, err := v.reader.NewRawRangeDelIter()
|
||||
@@ -181,3 +199,8 @@ func (v *VirtualReader) EstimateDiskUsage(start, end []byte) (uint64, error) {
|
||||
_, f, l := v.vState.constrainBounds(start, end, true /* endInclusive */)
|
||||
return v.reader.EstimateDiskUsage(f, l)
|
||||
}
|
||||
|
||||
// CommonProperties implements the CommonReader interface.
|
||||
func (v *VirtualReader) CommonProperties() *CommonProperties {
|
||||
return &v.Properties
|
||||
}
|
||||
|
||||
115
vendor/github.com/cockroachdb/pebble/table_cache.go
generated
vendored
115
vendor/github.com/cockroachdb/pebble/table_cache.go
generated
vendored
@@ -194,6 +194,40 @@ func (c *tableCacheContainer) estimateSize(
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// createCommonReader creates a Reader for this file. isForeign, if true for
|
||||
// virtual sstables, is passed into the vSSTable reader so its iterators can
|
||||
// collapse obsolete points accordingly.
|
||||
func createCommonReader(
|
||||
v *tableCacheValue, file *fileMetadata, isForeign bool,
|
||||
) sstable.CommonReader {
|
||||
// TODO(bananabrick): We suffer an allocation if file is a virtual sstable.
|
||||
var cr sstable.CommonReader = v.reader
|
||||
if file.Virtual {
|
||||
virtualReader := sstable.MakeVirtualReader(
|
||||
v.reader, file.VirtualMeta(), isForeign,
|
||||
)
|
||||
cr = &virtualReader
|
||||
}
|
||||
return cr
|
||||
}
|
||||
|
||||
func (c *tableCacheContainer) withCommonReader(
|
||||
meta *fileMetadata, fn func(sstable.CommonReader) error,
|
||||
) error {
|
||||
s := c.tableCache.getShard(meta.FileBacking.DiskFileNum)
|
||||
v := s.findNode(meta, &c.dbOpts)
|
||||
defer s.unrefValue(v)
|
||||
if v.err != nil {
|
||||
return v.err
|
||||
}
|
||||
provider := c.dbOpts.objProvider
|
||||
objMeta, err := provider.Lookup(fileTypeTable, meta.FileBacking.DiskFileNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(createCommonReader(v, meta, provider.IsSharedForeign(objMeta)))
|
||||
}
|
||||
|
||||
func (c *tableCacheContainer) withReader(meta physicalMeta, fn func(*sstable.Reader) error) error {
|
||||
s := c.tableCache.getShard(meta.FileBacking.DiskFileNum)
|
||||
v := s.findNode(meta.FileMetadata, &c.dbOpts)
|
||||
@@ -214,7 +248,12 @@ func (c *tableCacheContainer) withVirtualReader(
|
||||
if v.err != nil {
|
||||
return v.err
|
||||
}
|
||||
return fn(sstable.MakeVirtualReader(v.reader, meta))
|
||||
provider := c.dbOpts.objProvider
|
||||
objMeta, err := provider.Lookup(fileTypeTable, meta.FileBacking.DiskFileNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(sstable.MakeVirtualReader(v.reader, meta, provider.IsSharedForeign(objMeta)))
|
||||
}
|
||||
|
||||
func (c *tableCacheContainer) iterCount() int64 {
|
||||
@@ -432,25 +471,6 @@ func (c *tableCacheShard) newIters(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
type iterCreator interface {
|
||||
NewRawRangeDelIter() (keyspan.FragmentIterator, error)
|
||||
NewIterWithBlockPropertyFiltersAndContextEtc(ctx context.Context, lower, upper []byte, filterer *sstable.BlockPropertiesFilterer, hideObsoletePoints, useFilterBlock bool, stats *base.InternalIteratorStats, rp sstable.ReaderProvider) (sstable.Iterator, error)
|
||||
NewCompactionIter(
|
||||
bytesIterated *uint64,
|
||||
rp sstable.ReaderProvider,
|
||||
bufferPool *sstable.BufferPool,
|
||||
) (sstable.Iterator, error)
|
||||
}
|
||||
|
||||
// TODO(bananabrick): We suffer an allocation if file is a virtual sstable.
|
||||
var ic iterCreator = v.reader
|
||||
if file.Virtual {
|
||||
virtualReader := sstable.MakeVirtualReader(
|
||||
v.reader, file.VirtualMeta(),
|
||||
)
|
||||
ic = &virtualReader
|
||||
}
|
||||
|
||||
provider := dbOpts.objProvider
|
||||
// Check if this file is a foreign file.
|
||||
objMeta, err := provider.Lookup(fileTypeTable, file.FileBacking.DiskFileNum)
|
||||
@@ -458,9 +478,12 @@ func (c *tableCacheShard) newIters(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Note: This suffers an allocation for virtual sstables.
|
||||
cr := createCommonReader(v, file, provider.IsSharedForeign(objMeta))
|
||||
|
||||
// NB: range-del iterator does not maintain a reference to the table, nor
|
||||
// does it need to read from it after creation.
|
||||
rangeDelIter, err := ic.NewRawRangeDelIter()
|
||||
rangeDelIter, err := cr.NewRawRangeDelIter()
|
||||
if err != nil {
|
||||
c.unrefValue(v)
|
||||
return nil, nil, err
|
||||
@@ -504,9 +527,9 @@ func (c *tableCacheShard) newIters(
|
||||
hideObsoletePoints = true
|
||||
}
|
||||
if internalOpts.bytesIterated != nil {
|
||||
iter, err = ic.NewCompactionIter(internalOpts.bytesIterated, rp, internalOpts.bufferPool)
|
||||
iter, err = cr.NewCompactionIter(internalOpts.bytesIterated, rp, internalOpts.bufferPool)
|
||||
} else {
|
||||
iter, err = ic.NewIterWithBlockPropertyFiltersAndContextEtc(
|
||||
iter, err = cr.NewIterWithBlockPropertyFiltersAndContextEtc(
|
||||
ctx, opts.GetLowerBound(), opts.GetUpperBound(), filterer, hideObsoletePoints, useFilter,
|
||||
internalOpts.stats, rp)
|
||||
}
|
||||
@@ -567,10 +590,15 @@ func (c *tableCacheShard) newRangeKeyIter(
|
||||
|
||||
var iter keyspan.FragmentIterator
|
||||
if file.Virtual {
|
||||
virtualReader := sstable.MakeVirtualReader(
|
||||
v.reader, file.VirtualMeta(),
|
||||
)
|
||||
iter, err = virtualReader.NewRawRangeKeyIter()
|
||||
provider := dbOpts.objProvider
|
||||
var objMeta objstorage.ObjectMetadata
|
||||
objMeta, err = provider.Lookup(fileTypeTable, file.FileBacking.DiskFileNum)
|
||||
if err == nil {
|
||||
virtualReader := sstable.MakeVirtualReader(
|
||||
v.reader, file.VirtualMeta(), provider.IsSharedForeign(objMeta),
|
||||
)
|
||||
iter, err = virtualReader.NewRawRangeKeyIter()
|
||||
}
|
||||
} else {
|
||||
iter, err = v.reader.NewRawRangeKeyIter()
|
||||
}
|
||||
@@ -714,23 +742,30 @@ func (c *tableCacheShard) unrefValue(v *tableCacheValue) {
|
||||
// findNode returns the node for the table with the given file number, creating
|
||||
// that node if it didn't already exist. The caller is responsible for
|
||||
// decrementing the returned node's refCount.
|
||||
func (c *tableCacheShard) findNode(
|
||||
meta *fileMetadata, dbOpts *tableCacheOpts,
|
||||
) (v *tableCacheValue) {
|
||||
func (c *tableCacheShard) findNode(meta *fileMetadata, dbOpts *tableCacheOpts) *tableCacheValue {
|
||||
v := c.findNodeInternal(meta, dbOpts)
|
||||
|
||||
// Loading a file before its global sequence number is known (eg,
|
||||
// during ingest before entering the commit pipeline) can pollute
|
||||
// the cache with incorrect state. In invariant builds, verify
|
||||
// that the global sequence number of the returned reader matches.
|
||||
if invariants.Enabled {
|
||||
defer func() {
|
||||
if v.reader != nil && meta.LargestSeqNum == meta.SmallestSeqNum &&
|
||||
v.reader.Properties.GlobalSeqNum != meta.SmallestSeqNum {
|
||||
panic(errors.AssertionFailedf("file %s loaded from table cache with the wrong global sequence number %d",
|
||||
meta, v.reader.Properties.GlobalSeqNum))
|
||||
}
|
||||
}()
|
||||
if v.reader != nil && meta.LargestSeqNum == meta.SmallestSeqNum &&
|
||||
v.reader.Properties.GlobalSeqNum != meta.SmallestSeqNum {
|
||||
panic(errors.AssertionFailedf("file %s loaded from table cache with the wrong global sequence number %d",
|
||||
meta, v.reader.Properties.GlobalSeqNum))
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (c *tableCacheShard) findNodeInternal(
|
||||
meta *fileMetadata, dbOpts *tableCacheOpts,
|
||||
) *tableCacheValue {
|
||||
if refs := meta.Refs(); refs <= 0 {
|
||||
panic(errors.AssertionFailedf("attempting to load file %s with refs=%d from table cache",
|
||||
meta, refs))
|
||||
}
|
||||
// Fast-path for a hit in the cache.
|
||||
c.mu.RLock()
|
||||
key := tableCacheKey{dbOpts.cacheID, meta.FileBacking.DiskFileNum}
|
||||
@@ -738,7 +773,7 @@ func (c *tableCacheShard) findNode(
|
||||
// Fast-path hit.
|
||||
//
|
||||
// The caller is responsible for decrementing the refCount.
|
||||
v = n.value
|
||||
v := n.value
|
||||
v.refCount.Add(1)
|
||||
c.mu.RUnlock()
|
||||
n.referenced.Store(true)
|
||||
@@ -765,7 +800,7 @@ func (c *tableCacheShard) findNode(
|
||||
// Slow-path hit of a hot or cold node.
|
||||
//
|
||||
// The caller is responsible for decrementing the refCount.
|
||||
v = n.value
|
||||
v := n.value
|
||||
v.refCount.Add(1)
|
||||
n.referenced.Store(true)
|
||||
c.hits.Add(1)
|
||||
@@ -789,7 +824,7 @@ func (c *tableCacheShard) findNode(
|
||||
|
||||
c.misses.Add(1)
|
||||
|
||||
v = &tableCacheValue{
|
||||
v := &tableCacheValue{
|
||||
loaded: make(chan struct{}),
|
||||
}
|
||||
v.refCount.Store(2)
|
||||
|
||||
102
vendor/github.com/cockroachdb/pebble/table_stats.go
generated
vendored
102
vendor/github.com/cockroachdb/pebble/table_stats.go
generated
vendored
@@ -186,14 +186,9 @@ func (d *DB) loadNewFileStats(
|
||||
continue
|
||||
}
|
||||
|
||||
if nf.Meta.Virtual {
|
||||
// cannot load virtual table stats
|
||||
continue
|
||||
}
|
||||
|
||||
stats, newHints, err := d.loadTableStats(
|
||||
rs.current, nf.Level,
|
||||
nf.Meta.PhysicalMeta(),
|
||||
nf.Meta,
|
||||
)
|
||||
if err != nil {
|
||||
d.opts.EventListener.BackgroundError(err)
|
||||
@@ -222,12 +217,6 @@ func (d *DB) scanReadStateTableStats(
|
||||
for l, levelMetadata := range rs.current.Levels {
|
||||
iter := levelMetadata.Iter()
|
||||
for f := iter.First(); f != nil; f = iter.Next() {
|
||||
if f.Virtual {
|
||||
// TODO(bananabrick): Support stats collection for virtual
|
||||
// sstables.
|
||||
continue
|
||||
}
|
||||
|
||||
// NB: We're not holding d.mu which protects f.Stats, but only the
|
||||
// active stats collection job updates f.Stats for active files,
|
||||
// and we ensure only one goroutine runs it at a time through
|
||||
@@ -236,11 +225,6 @@ func (d *DB) scanReadStateTableStats(
|
||||
if f.StatsValid() {
|
||||
continue
|
||||
}
|
||||
// TODO(bilal): Remove this guard when table stats collection is
|
||||
// implemented for virtual sstables.
|
||||
if f.Virtual {
|
||||
continue
|
||||
}
|
||||
|
||||
// Limit how much work we do per read state. The older the read
|
||||
// state is, the higher the likelihood files are no longer being
|
||||
@@ -253,7 +237,7 @@ func (d *DB) scanReadStateTableStats(
|
||||
}
|
||||
|
||||
stats, newHints, err := d.loadTableStats(
|
||||
rs.current, l, f.PhysicalMeta(),
|
||||
rs.current, l, f,
|
||||
)
|
||||
if err != nil {
|
||||
// Set `moreRemain` so we'll try again.
|
||||
@@ -271,25 +255,22 @@ func (d *DB) scanReadStateTableStats(
|
||||
return fill, hints, moreRemain
|
||||
}
|
||||
|
||||
// loadTableStats currently only supports stats collection for physical
|
||||
// sstables.
|
||||
//
|
||||
// TODO(bananabrick): Support stats collection for virtual sstables.
|
||||
func (d *DB) loadTableStats(
|
||||
v *version, level int, meta physicalMeta,
|
||||
v *version, level int, meta *fileMetadata,
|
||||
) (manifest.TableStats, []deleteCompactionHint, error) {
|
||||
var stats manifest.TableStats
|
||||
var compactionHints []deleteCompactionHint
|
||||
err := d.tableCache.withReader(
|
||||
meta, func(r *sstable.Reader) (err error) {
|
||||
stats.NumEntries = r.Properties.NumEntries
|
||||
stats.NumDeletions = r.Properties.NumDeletions
|
||||
if r.Properties.NumPointDeletions() > 0 {
|
||||
if err = d.loadTablePointKeyStats(r, v, level, meta, &stats); err != nil {
|
||||
err := d.tableCache.withCommonReader(
|
||||
meta, func(r sstable.CommonReader) (err error) {
|
||||
props := r.CommonProperties()
|
||||
stats.NumEntries = props.NumEntries
|
||||
stats.NumDeletions = props.NumDeletions
|
||||
if props.NumPointDeletions() > 0 {
|
||||
if err = d.loadTablePointKeyStats(props, v, level, meta, &stats); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if r.Properties.NumRangeDeletions > 0 || r.Properties.NumRangeKeyDels > 0 {
|
||||
if props.NumRangeDeletions > 0 || props.NumRangeKeyDels > 0 {
|
||||
if compactionHints, err = d.loadTableRangeDelStats(
|
||||
r, v, level, meta, &stats,
|
||||
); err != nil {
|
||||
@@ -299,8 +280,8 @@ func (d *DB) loadTableStats(
|
||||
// TODO(travers): Once we have real-world data, consider collecting
|
||||
// additional stats that may provide improved heuristics for compaction
|
||||
// picking.
|
||||
stats.NumRangeKeySets = r.Properties.NumRangeKeySets
|
||||
stats.ValueBlocksSize = r.Properties.ValueBlocksSize
|
||||
stats.NumRangeKeySets = props.NumRangeKeySets
|
||||
stats.ValueBlocksSize = props.ValueBlocksSize
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
@@ -312,7 +293,11 @@ func (d *DB) loadTableStats(
|
||||
// loadTablePointKeyStats calculates the point key statistics for the given
|
||||
// table. The provided manifest.TableStats are updated.
|
||||
func (d *DB) loadTablePointKeyStats(
|
||||
r *sstable.Reader, v *version, level int, meta physicalMeta, stats *manifest.TableStats,
|
||||
props *sstable.CommonProperties,
|
||||
v *version,
|
||||
level int,
|
||||
meta *fileMetadata,
|
||||
stats *manifest.TableStats,
|
||||
) error {
|
||||
// TODO(jackson): If the file has a wide keyspace, the average
|
||||
// value size beneath the entire file might not be representative
|
||||
@@ -320,21 +305,21 @@ func (d *DB) loadTablePointKeyStats(
|
||||
// We could write the ranges of 'clusters' of point tombstones to
|
||||
// a sstable property and call averageValueSizeBeneath for each of
|
||||
// these narrower ranges to improve the estimate.
|
||||
avgValLogicalSize, compressionRatio, err := d.estimateSizesBeneath(v, level, meta)
|
||||
avgValLogicalSize, compressionRatio, err := d.estimateSizesBeneath(v, level, meta, props)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stats.PointDeletionsBytesEstimate =
|
||||
pointDeletionsBytesEstimate(meta.Size, &r.Properties, avgValLogicalSize, compressionRatio)
|
||||
pointDeletionsBytesEstimate(meta.Size, props, avgValLogicalSize, compressionRatio)
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadTableRangeDelStats calculates the range deletion and range key deletion
|
||||
// statistics for the given table.
|
||||
func (d *DB) loadTableRangeDelStats(
|
||||
r *sstable.Reader, v *version, level int, meta physicalMeta, stats *manifest.TableStats,
|
||||
r sstable.CommonReader, v *version, level int, meta *fileMetadata, stats *manifest.TableStats,
|
||||
) ([]deleteCompactionHint, error) {
|
||||
iter, err := newCombinedDeletionKeyspanIter(d.opts.Comparer, r, meta.FileMetadata)
|
||||
iter, err := newCombinedDeletionKeyspanIter(d.opts.Comparer, r, meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -423,7 +408,7 @@ func (d *DB) loadTableRangeDelStats(
|
||||
hintType: hintType,
|
||||
start: make([]byte, len(start)),
|
||||
end: make([]byte, len(end)),
|
||||
tombstoneFile: meta.FileMetadata,
|
||||
tombstoneFile: meta,
|
||||
tombstoneLevel: level,
|
||||
tombstoneLargestSeqNum: s.LargestSeqNum(),
|
||||
tombstoneSmallestSeqNum: s.SmallestSeqNum(),
|
||||
@@ -437,12 +422,21 @@ func (d *DB) loadTableRangeDelStats(
|
||||
}
|
||||
|
||||
func (d *DB) estimateSizesBeneath(
|
||||
v *version, level int, meta physicalMeta,
|
||||
v *version, level int, meta *fileMetadata, fileProps *sstable.CommonProperties,
|
||||
) (avgValueLogicalSize, compressionRatio float64, err error) {
|
||||
// Find all files in lower levels that overlap with meta,
|
||||
// summing their value sizes and entry counts.
|
||||
file := meta.FileMetadata
|
||||
file := meta
|
||||
var fileSum, keySum, valSum, entryCount uint64
|
||||
// Include the file itself. This is important because in some instances, the
|
||||
// computed compression ratio is applied to the tombstones contained within
|
||||
// `meta` itself. If there are no files beneath `meta` in the LSM, we would
|
||||
// calculate a compression ratio of 0 which is not accurate for the file's
|
||||
// own tombstones.
|
||||
fileSum += file.Size
|
||||
entryCount += fileProps.NumEntries
|
||||
keySum += fileProps.RawKeySize
|
||||
valSum += fileProps.RawValueSize
|
||||
|
||||
addPhysicalTableStats := func(r *sstable.Reader) (err error) {
|
||||
fileSum += file.Size
|
||||
@@ -459,15 +453,6 @@ func (d *DB) estimateSizesBeneath(
|
||||
return nil
|
||||
}
|
||||
|
||||
// Include the file itself. This is important because in some instances, the
|
||||
// computed compression ratio is applied to the tombstones contained within
|
||||
// `meta` itself. If there are no files beneath `meta` in the LSM, we would
|
||||
// calculate a compression ratio of 0 which is not accurate for the file's
|
||||
// own tombstones.
|
||||
if err = d.tableCache.withReader(meta, addPhysicalTableStats); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
for l := level + 1; l < numLevels; l++ {
|
||||
overlaps := v.Overlaps(l, d.cmp, meta.Smallest.UserKey,
|
||||
meta.Largest.UserKey, meta.Largest.IsExclusiveSentinel())
|
||||
@@ -637,8 +622,9 @@ func maybeSetStatsFromProperties(meta physicalMeta, props *sstable.Properties) b
|
||||
// doesn't require any additional IO and since the number of point
|
||||
// deletions in the file is low, the error introduced by this crude
|
||||
// estimate is expected to be small.
|
||||
avgValSize, compressionRatio := estimatePhysicalSizes(meta.Size, props)
|
||||
pointEstimate = pointDeletionsBytesEstimate(meta.Size, props, avgValSize, compressionRatio)
|
||||
commonProps := &props.CommonProperties
|
||||
avgValSize, compressionRatio := estimatePhysicalSizes(meta.Size, commonProps)
|
||||
pointEstimate = pointDeletionsBytesEstimate(meta.Size, commonProps, avgValSize, compressionRatio)
|
||||
}
|
||||
|
||||
meta.Stats.NumEntries = props.NumEntries
|
||||
@@ -652,7 +638,7 @@ func maybeSetStatsFromProperties(meta physicalMeta, props *sstable.Properties) b
|
||||
}
|
||||
|
||||
func pointDeletionsBytesEstimate(
|
||||
fileSize uint64, props *sstable.Properties, avgValLogicalSize, compressionRatio float64,
|
||||
fileSize uint64, props *sstable.CommonProperties, avgValLogicalSize, compressionRatio float64,
|
||||
) (estimate uint64) {
|
||||
if props.NumEntries == 0 {
|
||||
return 0
|
||||
@@ -675,7 +661,7 @@ func pointDeletionsBytesEstimate(
|
||||
// tombstones' encoded values.
|
||||
//
|
||||
// For un-sized point tombstones (DELs), we estimate assuming that each
|
||||
// point tombstone on average covers 1 key and using average vaue sizes.
|
||||
// point tombstone on average covers 1 key and using average value sizes.
|
||||
// This is almost certainly an overestimate, but that's probably okay
|
||||
// because point tombstones can slow range iterations even when they don't
|
||||
// cover a key.
|
||||
@@ -739,7 +725,7 @@ func pointDeletionsBytesEstimate(
|
||||
}
|
||||
|
||||
func estimatePhysicalSizes(
|
||||
fileSize uint64, props *sstable.Properties,
|
||||
fileSize uint64, props *sstable.CommonProperties,
|
||||
) (avgValLogicalSize, compressionRatio float64) {
|
||||
// RawKeySize and RawValueSize are uncompressed totals. Scale according to
|
||||
// the data size to account for compression, index blocks and metadata
|
||||
@@ -812,7 +798,7 @@ func estimatePhysicalSizes(
|
||||
// corresponding to the largest and smallest sequence numbers encountered across
|
||||
// the range deletes and range keys deletes that comprised the merged spans.
|
||||
func newCombinedDeletionKeyspanIter(
|
||||
comparer *base.Comparer, r *sstable.Reader, m *fileMetadata,
|
||||
comparer *base.Comparer, cr sstable.CommonReader, m *fileMetadata,
|
||||
) (keyspan.FragmentIterator, error) {
|
||||
// The range del iter and range key iter are each wrapped in their own
|
||||
// defragmenting iter. For each iter, abutting spans can always be merged.
|
||||
@@ -874,7 +860,7 @@ func newCombinedDeletionKeyspanIter(
|
||||
})
|
||||
mIter.Init(comparer.Compare, transform, new(keyspan.MergingBuffers))
|
||||
|
||||
iter, err := r.NewRawRangeDelIter()
|
||||
iter, err := cr.NewRawRangeDelIter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -891,7 +877,7 @@ func newCombinedDeletionKeyspanIter(
|
||||
mIter.AddLevel(iter)
|
||||
}
|
||||
|
||||
iter, err = r.NewRawRangeKeyIter()
|
||||
iter, err = cr.NewRawRangeKeyIter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -908,7 +894,7 @@ func newCombinedDeletionKeyspanIter(
|
||||
out.Keys = append(out.Keys, k)
|
||||
}
|
||||
return len(out.Keys) > 0
|
||||
})
|
||||
}, comparer.Compare)
|
||||
dIter := &keyspan.DefragmentingIter{}
|
||||
dIter.Init(comparer, iter, equal, reducer, new(keyspan.DefragmentingBuffers))
|
||||
iter = dIter
|
||||
|
||||
93
vendor/github.com/cockroachdb/pebble/version_set.go
generated
vendored
93
vendor/github.com/cockroachdb/pebble/version_set.go
generated
vendored
@@ -86,17 +86,21 @@ type versionSet struct {
|
||||
// still referenced by an inuse iterator.
|
||||
zombieTables map[base.DiskFileNum]uint64 // filenum -> size
|
||||
|
||||
// fileBackingMap is a map for the FileBacking which is supporting virtual
|
||||
// sstables in the latest version. Once the file backing is backing no
|
||||
// virtual sstables in the latest version, it is removed from this map and
|
||||
// the corresponding state is added to the zombieTables map. Note that we
|
||||
// don't keep track of file backing which supports a virtual sstable
|
||||
// which is not in the latest version.
|
||||
//
|
||||
// fileBackingMap is protected by the versionSet.logLock. It's populated
|
||||
// backingState is protected by the versionSet.logLock. It's populated
|
||||
// during Open in versionSet.load, but it's not used concurrently during
|
||||
// load.
|
||||
fileBackingMap map[base.DiskFileNum]*fileBacking
|
||||
backingState struct {
|
||||
// fileBackingMap is a map for the FileBacking which is supporting virtual
|
||||
// sstables in the latest version. Once the file backing is backing no
|
||||
// virtual sstables in the latest version, it is removed from this map and
|
||||
// the corresponding state is added to the zombieTables map. Note that we
|
||||
// don't keep track of file backing which supports a virtual sstable
|
||||
// which is not in the latest version.
|
||||
fileBackingMap map[base.DiskFileNum]*fileBacking
|
||||
// fileBackingSize is the sum of the sizes of the fileBackings in the
|
||||
// fileBackingMap.
|
||||
fileBackingSize uint64
|
||||
}
|
||||
|
||||
// minUnflushedLogNum is the smallest WAL log file number corresponding to
|
||||
// mutations that have not been flushed to an sstable.
|
||||
@@ -110,9 +114,10 @@ type versionSet struct {
|
||||
manifestFileNum FileNum
|
||||
manifestMarker *atomicfs.Marker
|
||||
|
||||
manifestFile vfs.File
|
||||
manifest *record.Writer
|
||||
setCurrent func(FileNum) error
|
||||
manifestFile vfs.File
|
||||
manifest *record.Writer
|
||||
setCurrent func(FileNum) error
|
||||
getFormatMajorVersion func() FormatMajorVersion
|
||||
|
||||
writing bool
|
||||
writerCond sync.Cond
|
||||
@@ -125,6 +130,7 @@ func (vs *versionSet) init(
|
||||
opts *Options,
|
||||
marker *atomicfs.Marker,
|
||||
setCurrent func(FileNum) error,
|
||||
getFMV func() FormatMajorVersion,
|
||||
mu *sync.Mutex,
|
||||
) {
|
||||
vs.dirname = dirname
|
||||
@@ -138,10 +144,12 @@ func (vs *versionSet) init(
|
||||
vs.versions.Init(mu)
|
||||
vs.obsoleteFn = vs.addObsoleteLocked
|
||||
vs.zombieTables = make(map[base.DiskFileNum]uint64)
|
||||
vs.fileBackingMap = make(map[base.DiskFileNum]*fileBacking)
|
||||
vs.backingState.fileBackingMap = make(map[base.DiskFileNum]*fileBacking)
|
||||
vs.backingState.fileBackingSize = 0
|
||||
vs.nextFileNum = 1
|
||||
vs.manifestMarker = marker
|
||||
vs.setCurrent = setCurrent
|
||||
vs.getFormatMajorVersion = getFMV
|
||||
}
|
||||
|
||||
// create creates a version set for a fresh DB.
|
||||
@@ -151,9 +159,10 @@ func (vs *versionSet) create(
|
||||
opts *Options,
|
||||
marker *atomicfs.Marker,
|
||||
setCurrent func(FileNum) error,
|
||||
getFormatMajorVersion func() FormatMajorVersion,
|
||||
mu *sync.Mutex,
|
||||
) error {
|
||||
vs.init(dirname, opts, marker, setCurrent, mu)
|
||||
vs.init(dirname, opts, marker, setCurrent, getFormatMajorVersion, mu)
|
||||
newVersion := &version{}
|
||||
vs.append(newVersion)
|
||||
var err error
|
||||
@@ -199,9 +208,10 @@ func (vs *versionSet) load(
|
||||
manifestFileNum FileNum,
|
||||
marker *atomicfs.Marker,
|
||||
setCurrent func(FileNum) error,
|
||||
getFormatMajorVersion func() FormatMajorVersion,
|
||||
mu *sync.Mutex,
|
||||
) error {
|
||||
vs.init(dirname, opts, marker, setCurrent, mu)
|
||||
vs.init(dirname, opts, marker, setCurrent, getFormatMajorVersion, mu)
|
||||
|
||||
vs.manifestFileNum = manifestFileNum
|
||||
manifestPath := base.MakeFilepath(opts.FS, dirname, fileTypeManifest, vs.manifestFileNum.DiskFileNum())
|
||||
@@ -290,16 +300,17 @@ func (vs *versionSet) load(
|
||||
// Populate the fileBackingMap and the FileBacking for virtual sstables since
|
||||
// we have finished version edit accumulation.
|
||||
for _, s := range bve.AddedFileBacking {
|
||||
vs.fileBackingMap[s.DiskFileNum] = s
|
||||
vs.addFileBacking(s)
|
||||
}
|
||||
|
||||
for _, fileNum := range bve.RemovedFileBacking {
|
||||
delete(vs.fileBackingMap, fileNum)
|
||||
vs.removeFileBacking(fileNum)
|
||||
}
|
||||
|
||||
newVersion, err := bve.Apply(
|
||||
nil, vs.cmp, opts.Comparer.FormatKey, opts.FlushSplitBytes,
|
||||
opts.Experimental.ReadCompactionRate, nil, /* zombies */
|
||||
getFormatMajorVersion().orderingInvariants(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -357,6 +368,26 @@ func (vs *versionSet) logUnlock() {
|
||||
vs.writerCond.Signal()
|
||||
}
|
||||
|
||||
// Only call if the DiskFileNum doesn't exist in the fileBackingMap.
|
||||
func (vs *versionSet) addFileBacking(backing *manifest.FileBacking) {
|
||||
_, ok := vs.backingState.fileBackingMap[backing.DiskFileNum]
|
||||
if ok {
|
||||
panic("pebble: trying to add an existing file backing")
|
||||
}
|
||||
vs.backingState.fileBackingMap[backing.DiskFileNum] = backing
|
||||
vs.backingState.fileBackingSize += backing.Size
|
||||
}
|
||||
|
||||
// Only call if the the DiskFileNum exists in the fileBackingMap.
|
||||
func (vs *versionSet) removeFileBacking(dfn base.DiskFileNum) {
|
||||
backing, ok := vs.backingState.fileBackingMap[dfn]
|
||||
if !ok {
|
||||
panic("pebble: trying to remove an unknown file backing")
|
||||
}
|
||||
delete(vs.backingState.fileBackingMap, dfn)
|
||||
vs.backingState.fileBackingSize -= backing.Size
|
||||
}
|
||||
|
||||
// logAndApply logs the version edit to the manifest, applies the version edit
|
||||
// to the current version, and installs the new version.
|
||||
//
|
||||
@@ -414,6 +445,8 @@ func (vs *versionSet) logAndApply(
|
||||
}
|
||||
|
||||
currentVersion := vs.currentVersion()
|
||||
fmv := vs.getFormatMajorVersion()
|
||||
orderingInvariants := fmv.orderingInvariants()
|
||||
var newVersion *version
|
||||
|
||||
// Generate a new manifest if we don't currently have one, or forceRotation
|
||||
@@ -484,13 +517,14 @@ func (vs *versionSet) logAndApply(
|
||||
defer vs.mu.Lock()
|
||||
|
||||
var err error
|
||||
if vs.opts.FormatMajorVersion < ExperimentalFormatVirtualSSTables && len(ve.CreatedBackingTables) > 0 {
|
||||
if vs.getFormatMajorVersion() < FormatVirtualSSTables && len(ve.CreatedBackingTables) > 0 {
|
||||
return errors.AssertionFailedf("MANIFEST cannot contain virtual sstable records due to format major version")
|
||||
}
|
||||
newVersion, zombies, err = manifest.AccumulateIncompleteAndApplySingleVE(
|
||||
ve, currentVersion, vs.cmp, vs.opts.Comparer.FormatKey,
|
||||
vs.opts.FlushSplitBytes, vs.opts.Experimental.ReadCompactionRate,
|
||||
vs.fileBackingMap,
|
||||
vs.backingState.fileBackingMap, vs.addFileBacking, vs.removeFileBacking,
|
||||
orderingInvariants,
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "MANIFEST apply failed")
|
||||
@@ -588,6 +622,8 @@ func (vs *versionSet) logAndApply(
|
||||
for i := range vs.metrics.Levels {
|
||||
l := &vs.metrics.Levels[i]
|
||||
l.NumFiles = int64(newVersion.Levels[i].Len())
|
||||
l.NumVirtualFiles = newVersion.Levels[i].NumVirtual
|
||||
l.VirtualSize = newVersion.Levels[i].VirtualSize
|
||||
l.Size = int64(newVersion.Levels[i].Size())
|
||||
|
||||
l.Sublevels = 0
|
||||
@@ -595,13 +631,22 @@ func (vs *versionSet) logAndApply(
|
||||
l.Sublevels = 1
|
||||
}
|
||||
if invariants.Enabled {
|
||||
if count := int64(newVersion.Levels[i].Len()); l.NumFiles != count {
|
||||
vs.opts.Logger.Fatalf("versionSet metrics L%d NumFiles = %d, actual count = %d", i, l.NumFiles, count)
|
||||
}
|
||||
levelFiles := newVersion.Levels[i].Slice()
|
||||
if size := int64(levelFiles.SizeSum()); l.Size != size {
|
||||
vs.opts.Logger.Fatalf("versionSet metrics L%d Size = %d, actual size = %d", i, l.Size, size)
|
||||
}
|
||||
if nVirtual := levelFiles.NumVirtual(); nVirtual != l.NumVirtualFiles {
|
||||
vs.opts.Logger.Fatalf(
|
||||
"versionSet metrics L%d NumVirtual = %d, actual NumVirtual = %d",
|
||||
i, l.NumVirtualFiles, nVirtual,
|
||||
)
|
||||
}
|
||||
if vSize := levelFiles.VirtualSizeSum(); vSize != l.VirtualSize {
|
||||
vs.opts.Logger.Fatalf(
|
||||
"versionSet metrics L%d Virtual size = %d, actual size = %d",
|
||||
i, l.VirtualSize, vSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
vs.metrics.Levels[0].Sublevels = int32(len(newVersion.L0SublevelFiles))
|
||||
@@ -613,7 +658,9 @@ func (vs *versionSet) logAndApply(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vs *versionSet) incrementCompactions(kind compactionKind, extraLevels []*compactionLevel) {
|
||||
func (vs *versionSet) incrementCompactions(
|
||||
kind compactionKind, extraLevels []*compactionLevel, pickerMetrics compactionPickerMetrics,
|
||||
) {
|
||||
switch kind {
|
||||
case compactionKindDefault:
|
||||
vs.metrics.Compact.Count++
|
||||
|
||||
49
vendor/github.com/cockroachdb/pebble/vfs/mem_fs.go
generated
vendored
49
vendor/github.com/cockroachdb/pebble/vfs/mem_fs.go
generated
vendored
@@ -14,6 +14,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
@@ -78,6 +79,11 @@ type MemFS struct {
|
||||
mu sync.Mutex
|
||||
root *memNode
|
||||
|
||||
// lockFiles holds a map of open file locks. Presence in this map indicates
|
||||
// a file lock is currently held. Keys are strings holding the path of the
|
||||
// locked file. The stored value is untyped and unused; only presence of
|
||||
// the key within the map is significant.
|
||||
lockedFiles sync.Map
|
||||
strict bool
|
||||
ignoreSyncs bool
|
||||
// Windows has peculiar semantics with respect to hard links and deleting
|
||||
@@ -457,9 +463,31 @@ func (y *MemFS) MkdirAll(dirname string, perm os.FileMode) error {
|
||||
// Lock implements FS.Lock.
|
||||
func (y *MemFS) Lock(fullname string) (io.Closer, error) {
|
||||
// FS.Lock excludes other processes, but other processes cannot see this
|
||||
// process' memory. We translate Lock into Create so that have the normal
|
||||
// detection of non-existent directory paths.
|
||||
return y.Create(fullname)
|
||||
// process' memory. However some uses (eg, Cockroach tests) may open and
|
||||
// close the same MemFS-backed database multiple times. We want mutual
|
||||
// exclusion in this case too. See cockroachdb/cockroach#110645.
|
||||
_, loaded := y.lockedFiles.Swap(fullname, nil /* the value itself is insignificant */)
|
||||
if loaded {
|
||||
// This file lock has already been acquired. On unix, this results in
|
||||
// either EACCES or EAGAIN so we mimic.
|
||||
return nil, syscall.EAGAIN
|
||||
}
|
||||
// Otherwise, we successfully acquired the lock. Locks are visible in the
|
||||
// parent directory listing, and they also must be created under an existent
|
||||
// directory. Create the path so that we have the normal detection of
|
||||
// non-existent directory paths, and make the lock visible when listing
|
||||
// directory entries.
|
||||
f, err := y.Create(fullname)
|
||||
if err != nil {
|
||||
// "Release" the lock since we failed.
|
||||
y.lockedFiles.Delete(fullname)
|
||||
return nil, err
|
||||
}
|
||||
return &memFileLock{
|
||||
y: y,
|
||||
f: f,
|
||||
fullname: fullname,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// List implements FS.List.
|
||||
@@ -787,3 +815,18 @@ func (f *memFile) Fd() uintptr {
|
||||
func (f *memFile) Flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type memFileLock struct {
|
||||
y *MemFS
|
||||
f File
|
||||
fullname string
|
||||
}
|
||||
|
||||
func (l *memFileLock) Close() error {
|
||||
if l.y == nil {
|
||||
return nil
|
||||
}
|
||||
l.y.lockedFiles.Delete(l.fullname)
|
||||
l.y = nil
|
||||
return l.f.Close()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user