Upgrade versions of vendored libs

This commit is contained in:
eleksir
2022-11-04 16:29:14 +03:00
parent fba732cc24
commit a7ae372459
941 changed files with 219674 additions and 23611 deletions

View File

@@ -4,3 +4,4 @@ go:
- 1.13.x
- 1.14.x
- 1.15.x
- 1.16.x

View File

@@ -6,13 +6,16 @@
# go get github.com/gogo/protobuf/types
# go get github.com/gogo/protobuf/protoc-gen-gogo
#
# Note: as of 2020-11-01, it is not (yet) possible to use gogoproto
# v1.3.x to generate .pb.go files so that they are compatible with
# CockroachDB. This is because CockroachDB hardcodes gogoproto at
# version v1.2, and thus does not provide v1.3's
# proto.GoGoProtoPackageIsVersion3.
# Note: as of 2021-04-13, we like to use a custom protoc-gen-gogo
# with additional options, to stabilize the marshalled
# encoding of objects (so that they are deterministic
# across marshal/unmarshal cycles) and reduce the memory footprint
# of objects:
#
# For details, see https://github.com/cockroachdb/cockroach/issues/56378
# vanity.TurnOnStable_MarshalerAll,
# vanity.TurnOffGoUnrecognizedAll,
# vanity.TurnOffGoUnkeyedAll,
# vanity.TurnOffGoSizecacheAll,
#
# Until this is resolved, the "go get" commands above are not
# adequate; instead:
@@ -23,21 +26,33 @@
#
# 2. run `make -f Makefile.update-protos` with this PATH active.
PROTOS := $(wildcard errorspb/*.proto)
export SHELL := env PWD=$(CURDIR) bash
PROTOS := $(wildcard \
errbase/internal/*.proto \
errorspb/*.proto \
extgrpc/*.proto \
exthttp/*.proto \
grpc/*.proto \
markers/internal/*.proto \
)
GO_SOURCES = $(PROTOS:.proto=.pb.go)
SED = sed
SED_INPLACE := $(shell $(SED) --version 2>&1 | grep -q GNU && echo -i || echo "-i ''")
all: $(PROTOS)
protoc \
-I$$GOPATH/src/ \
-I$$GOPATH/src/github.com \
-I$$GOPATH/src/github.com/gogo/protobuf \
-I$$GOPATH/src/github.com/gogo/protobuf/protobuf \
--gogoroach_out=Mgoogle/protobuf/any.proto=github.com/gogo/protobuf/types,plugins=grpc,import_prefix=:. \
$(PROTOS:%=$(GOPATH)/src/github.com/cockroachdb/errors/%)
mv -f github.com/cockroachdb/errors/errorspb/*.pb.go errorspb/
set -e; for dir in $(sort $(dir $(PROTOS))); do \
protoc \
-I. \
-I$$GOPATH/src/ \
-I$$GOPATH/src/github.com \
-I$$GOPATH/src/github.com/cockroachdb/errors \
-I$$GOPATH/src/github.com/gogo/protobuf \
-I$$GOPATH/src/github.com/gogo/protobuf/protobuf \
--gogoroach_out=Mgoogle/protobuf/any.proto=github.com/gogo/protobuf/types,plugins=grpc,import_prefix=:. \
$$dir/*.proto; \
done
$(SED) $(SED_INPLACE) -E \
-e '/import _ /d' \
-e 's!import (fmt|math) "github.com/(fmt|math)"! !g' \

View File

@@ -11,7 +11,8 @@ automatically formats error details and strips them of PII.
See also [the design RFC](https://github.com/cockroachdb/cockroach/blob/master/docs/RFCS/20190318_error_handling.md).
[![Build Status](https://travis-ci.org/cockroachdb/errors.svg?branch=master)](https://travis-ci.org/cockroachdb/errors)
![Build Status](https://github.com/cockroachdb/errors/actions/workflows/ci.yaml/badge.svg?branch=master)
[![Go Reference](https://pkg.go.dev/badge/github.com/cockroachdb/errors.svg)](https://pkg.go.dev/github.com/cockroachdb/errors)
Table of contents:
@@ -422,10 +423,10 @@ func myLeafDecoder(_ string, details []string, _ proto.Message) error {
(For an example, see the `withTelemetry` type in [`telemetry/with_telemetry.go`](telemetry/with_telemetry.go).)
**The only case where you need a custom encoder is when your error
__The only case where you need a custom encoder is when your error
type contains some fields that are not reflected in the error message
(so you can't extract them back from there), and are not PII-free and
thus cannot be reported as "safe details".**
thus cannot be reported as "safe details".__
To take inspiration from examples, see the following types in the
library that need a custom encoder:
@@ -538,6 +539,9 @@ Example use:
## API (not constructing error objects)
The following is a summary of the non-constructor API functions, grouped by category.
Detailed documentation can be found at: https://pkg.go.dev/github.com/cockroachdb/errors
```go
// Access causes.
func UnwrapAll(err error) error

View File

@@ -19,6 +19,7 @@ import (
"fmt"
"github.com/cockroachdb/errors/errbase"
"github.com/cockroachdb/redact"
"github.com/gogo/protobuf/proto"
)
@@ -36,7 +37,7 @@ func Handled(err error) error {
if err == nil {
return nil
}
return HandledWithMessage(err, err.Error())
return HandledWithSafeMessage(err, redact.Sprint(err))
}
// HandledWithMessage is like Handled except the message is overridden.
@@ -46,7 +47,14 @@ func HandledWithMessage(err error, msg string) error {
if err == nil {
return nil
}
return &barrierError{maskedErr: err, msg: msg}
return HandledWithSafeMessage(err, redact.Sprint(msg))
}
// HandledWithSafeMessage is like Handled except the message is overridden.
// This can be used e.g. to hide message details or to prevent
// downstream code to make assertions on the message's contents.
func HandledWithSafeMessage(err error, msg redact.RedactableString) error {
return &barrierErr{maskedErr: err, smsg: msg}
}
// HandledWithMessagef is like HandledWithMessagef except the message
@@ -55,47 +63,48 @@ func HandledWithMessagef(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &barrierError{maskedErr: err, msg: fmt.Sprintf(format, args...)}
return &barrierErr{maskedErr: err, smsg: redact.Sprintf(format, args...)}
}
// barrierError is a leaf error type. It encapsulates a chain of
// barrierErr is a leaf error type. It encapsulates a chain of
// original causes, but these causes are hidden so that they inhibit
// matching via Is() and the Cause()/Unwrap() recursions.
type barrierError struct {
type barrierErr struct {
// Message for the barrier itself.
// In the common case, the message from the masked error
// is used as-is (see Handled() above) however it is
// useful to cache it here since the masked error may
// have a long chain of wrappers and its Error() call
// may be expensive.
msg string
smsg redact.RedactableString
// Masked error chain.
maskedErr error
}
var _ error = (*barrierError)(nil)
var _ errbase.SafeDetailer = (*barrierError)(nil)
var _ errbase.SafeFormatter = (*barrierError)(nil)
var _ fmt.Formatter = (*barrierError)(nil)
var _ error = (*barrierErr)(nil)
var _ errbase.SafeDetailer = (*barrierErr)(nil)
var _ errbase.SafeFormatter = (*barrierErr)(nil)
var _ fmt.Formatter = (*barrierErr)(nil)
// barrierError is an error.
func (e *barrierError) Error() string { return e.msg }
// barrierErr is an error.
func (e *barrierErr) Error() string { return e.smsg.StripMarkers() }
// SafeDetails reports the PII-free details from the masked error.
func (e *barrierError) SafeDetails() []string {
func (e *barrierErr) SafeDetails() []string {
var details []string
for err := e.maskedErr; err != nil; err = errbase.UnwrapOnce(err) {
sd := errbase.GetSafeDetails(err)
details = sd.Fill(details)
}
details = append(details, redact.Sprintf("masked error: %+v", e.maskedErr).Redact().StripMarkers())
return details
}
// Printing a barrier reveals the details.
func (e *barrierError) Format(s fmt.State, verb rune) { errbase.FormatError(e, s, verb) }
func (e *barrierErr) Format(s fmt.State, verb rune) { errbase.FormatError(e, s, verb) }
func (e *barrierError) SafeFormatError(p errbase.Printer) (next error) {
p.Print(e.msg)
func (e *barrierErr) SafeFormatError(p errbase.Printer) (next error) {
p.Print(e.smsg)
if p.Detail() {
p.Printf("-- cause hidden behind barrier\n%+v", e.maskedErr)
}
@@ -106,19 +115,37 @@ func (e *barrierError) SafeFormatError(p errbase.Printer) (next error) {
func encodeBarrier(
ctx context.Context, err error,
) (msg string, details []string, payload proto.Message) {
e := err.(*barrierError)
e := err.(*barrierErr)
enc := errbase.EncodeError(ctx, e.maskedErr)
return e.msg, e.SafeDetails(), &enc
return string(e.smsg), e.SafeDetails(), &enc
}
// A barrier error is decoded exactly.
func decodeBarrier(ctx context.Context, msg string, _ []string, payload proto.Message) error {
enc := payload.(*errbase.EncodedError)
return &barrierError{msg: msg, maskedErr: errbase.DecodeError(ctx, *enc)}
return &barrierErr{smsg: redact.RedactableString(msg), maskedErr: errbase.DecodeError(ctx, *enc)}
}
// Previous versions of barrier errors.
func decodeBarrierPrev(ctx context.Context, msg string, _ []string, payload proto.Message) error {
enc := payload.(*errbase.EncodedError)
return &barrierErr{smsg: redact.Sprint(msg), maskedErr: errbase.DecodeError(ctx, *enc)}
}
// barrierError is the "old" type name of barrierErr. We use a new
// name now to ensure a different decode function is used when
// importing barriers from the previous structure, where the
// message is not redactable.
type barrierError struct {
msg string
maskedErr error
}
func (b *barrierError) Error() string { return "" }
func init() {
tn := errbase.GetTypeKey((*barrierError)(nil))
errbase.RegisterLeafDecoder(errbase.GetTypeKey((*barrierError)(nil)), decodeBarrierPrev)
tn := errbase.GetTypeKey((*barrierErr)(nil))
errbase.RegisterLeafDecoder(tn, decodeBarrier)
errbase.RegisterLeafEncoder(tn, encodeBarrier)
}

View File

@@ -37,6 +37,11 @@ func decodeErrorString(_ context.Context, msg string, _ []string, _ proto.Messag
return goErr.New(msg)
}
// context.DeadlineExceeded uses a custom type.
func decodeDeadlineExceeded(_ context.Context, _ string, _ []string, _ proto.Message) error {
return context.DeadlineExceeded
}
// errors.fundamental from github.com/pkg/errors cannot be encoded
// exactly because it includes a non-serializable stack trace
// object. In order to work with it, we encode it by dumping
@@ -183,6 +188,8 @@ func init() {
baseErr := goErr.New("")
RegisterLeafDecoder(GetTypeKey(baseErr), decodeErrorString)
RegisterLeafDecoder(GetTypeKey(context.DeadlineExceeded), decodeDeadlineExceeded)
pkgE := pkgErr.New("")
RegisterLeafEncoder(GetTypeKey(pkgE), encodePkgFundamental)
@@ -191,9 +198,11 @@ func init() {
ws := pkgErr.WithStack(baseErr)
RegisterWrapperEncoder(GetTypeKey(ws), encodePkgWithStack)
registerOsPathErrorMigration() // Needed for Go 1.16.
pKey := GetTypeKey(&os.PathError{})
RegisterWrapperEncoder(pKey, encodePathError)
RegisterWrapperDecoder(pKey, decodePathError)
pKey = GetTypeKey(&os.LinkError{})
RegisterWrapperEncoder(pKey, encodeLinkError)
RegisterWrapperDecoder(pKey, decodeLinkError)

View File

@@ -23,6 +23,8 @@ import (
)
// DecodeError decodes an error.
//
// Can only be called if the EncodedError is set (see IsSet()).
func DecodeError(ctx context.Context, enc EncodedError) error {
if w := enc.GetWrapper(); w != nil {
return decodeWrapper(ctx, w)

View File

@@ -349,66 +349,76 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
bufIsRedactable := false
printDone := false
for _, fn := range specialCases {
if handled, desiredShortening := fn(err, (*safePrinter)(s), cause == nil /* leaf */); handled {
printDone = true
bufIsRedactable = true
if desiredShortening == nil {
// The error wants to elide the short messages from inner
// causes. Do it.
for i := range s.entries {
s.entries[i].elideShort = true
}
switch v := err.(type) {
case SafeFormatter:
bufIsRedactable = true
desiredShortening := v.SafeFormatError((*safePrinter)(s))
if desiredShortening == nil {
// The error wants to elide the short messages from inner
// causes. Do it.
for i := range s.entries {
s.entries[i].elideShort = true
}
break
}
}
if !printDone {
switch v := err.(type) {
case SafeFormatter:
bufIsRedactable = true
desiredShortening := v.SafeFormatError((*safePrinter)(s))
if desiredShortening == nil {
// The error wants to elide the short messages from inner
// causes. Do it.
for i := range s.entries {
s.entries[i].elideShort = true
}
}
case Formatter:
desiredShortening := v.FormatError((*printer)(s))
if desiredShortening == nil {
// The error wants to elide the short messages from inner
// causes. Do it.
for i := range s.entries {
s.entries[i].elideShort = true
}
case Formatter:
desiredShortening := v.FormatError((*printer)(s))
if desiredShortening == nil {
// The error wants to elide the short messages from inner
// causes. Do it.
for i := range s.entries {
s.entries[i].elideShort = true
}
}
case fmt.Formatter:
// We can only use a fmt.Formatter when both the following
// conditions are true:
// - when it is the leaf error, because a fmt.Formatter
// on a wrapper also recurses.
// - when it is not the outermost wrapper, because
// the Format() method is likely to be calling FormatError()
// to do its job and we want to avoid an infinite recursion.
if !isOutermost && cause == nil {
v.Format(s, 'v')
if st, ok := err.(StackTraceProvider); ok {
// This is likely a leaf error from github/pkg/errors.
// The thing probably printed its stack trace on its own.
seenTrace = true
// We'll subsequently simplify stack traces in wrappers.
s.lastStack = st.StackTrace()
}
} else {
s.formatSimple(err, cause)
case fmt.Formatter:
// We can only use a fmt.Formatter when both the following
// conditions are true:
// - when it is the leaf error, because a fmt.Formatter
// on a wrapper also recurses.
// - when it is not the outermost wrapper, because
// the Format() method is likely to be calling FormatError()
// to do its job and we want to avoid an infinite recursion.
if !isOutermost && cause == nil {
v.Format(s, 'v')
if st, ok := err.(StackTraceProvider); ok {
// This is likely a leaf error from github/pkg/errors.
// The thing probably printed its stack trace on its own.
seenTrace = true
// We'll subsequently simplify stack traces in wrappers.
s.lastStack = st.StackTrace()
}
} else {
s.formatSimple(err, cause)
}
default:
default:
// Handle the special case overrides for context.Canceled,
// os.PathError, etc for which we know how to extract some safe
// strings.
//
// We need to do this in the `default` branch, instead of doing
// this above the switch, because the special handler could call a
// .Error() that delegates its implementation to fmt.Formatter,
// errors.Safeformatter or errors.Formattable, which brings us
// back to this method in a call cycle. So we need to handle the
// various interfaces first.
printDone := false
for _, fn := range specialCases {
if handled, desiredShortening := fn(err, (*safePrinter)(s), cause == nil /* leaf */); handled {
printDone = true
bufIsRedactable = true
if desiredShortening == nil {
// The error wants to elide the short messages from inner
// causes. Do it.
for i := range s.entries {
s.entries[i].elideShort = true
}
}
break
}
}
if !printDone {
// If the error did not implement errors.Formatter nor
// fmt.Formatter, but it is a wrapper, still attempt best effort:
// print what we can at this level.

View File

@@ -0,0 +1,24 @@
// Copyright 2021 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
// +build go1.16
package errbase
import "io/fs"
func registerOsPathErrorMigration() {
// The os.PathError type was migrated to io.fs.PathError in Go 1.16.
RegisterTypeMigration("os", "*os.PathError", &fs.PathError{})
}

View File

@@ -0,0 +1,19 @@
// Copyright 2021 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
// +build !go1.16
package errbase
func registerOsPathErrorMigration() {}

View File

@@ -0,0 +1,21 @@
// Copyright 2021 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package errorspb
// IsSet returns true if the EncodedError contains an error, or false if it is
// empty.
func (m *EncodedError) IsSet() bool {
return m.Error != nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,15 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: github.com/cockroachdb/errors/errorspb/hintdetail.proto
// source: errorspb/hintdetail.proto
package errorspb
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import io "io"
import (
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@@ -18,7 +20,7 @@ var _ = math.Inf
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type StringPayload struct {
Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
@@ -28,21 +30,21 @@ func (m *StringPayload) Reset() { *m = StringPayload{} }
func (m *StringPayload) String() string { return proto.CompactTextString(m) }
func (*StringPayload) ProtoMessage() {}
func (*StringPayload) Descriptor() ([]byte, []int) {
return fileDescriptor_hintdetail_429641491537a364, []int{0}
return fileDescriptor_d99b88ec9cc4bc22, []int{0}
}
func (m *StringPayload) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *StringPayload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (dst *StringPayload) XXX_Merge(src proto.Message) {
xxx_messageInfo_StringPayload.Merge(dst, src)
func (m *StringPayload) XXX_Merge(src proto.Message) {
xxx_messageInfo_StringPayload.Merge(m, src)
}
func (m *StringPayload) XXX_Size() int {
return m.Size()
@@ -56,10 +58,26 @@ var xxx_messageInfo_StringPayload proto.InternalMessageInfo
func init() {
proto.RegisterType((*StringPayload)(nil), "cockroach.errorspb.StringPayload")
}
func init() { proto.RegisterFile("errorspb/hintdetail.proto", fileDescriptor_d99b88ec9cc4bc22) }
var fileDescriptor_d99b88ec9cc4bc22 = []byte{
// 143 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4c, 0x2d, 0x2a, 0xca,
0x2f, 0x2a, 0x2e, 0x48, 0xd2, 0xcf, 0xc8, 0xcc, 0x2b, 0x49, 0x49, 0x2d, 0x49, 0xcc, 0xcc, 0xd1,
0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x4a, 0xce, 0x4f, 0xce, 0x2e, 0xca, 0x4f, 0x4c, 0xce,
0xd0, 0x83, 0x29, 0x52, 0x52, 0xe4, 0xe2, 0x0d, 0x2e, 0x29, 0xca, 0xcc, 0x4b, 0x0f, 0x48, 0xac,
0xcc, 0xc9, 0x4f, 0x4c, 0x11, 0x12, 0xe0, 0x62, 0xce, 0x2d, 0x4e, 0x97, 0x60, 0x54, 0x60, 0xd4,
0xe0, 0x0c, 0x02, 0x31, 0x9d, 0xb4, 0x4e, 0x3c, 0x94, 0x63, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2,
0x23, 0x39, 0xc6, 0x1b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1,
0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0x38, 0x60, 0xc6, 0x25, 0xb1, 0x81, 0x6d,
0x32, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xe2, 0xbd, 0x3a, 0x3e, 0x86, 0x00, 0x00, 0x00,
}
func (m *StringPayload) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
@@ -67,27 +85,35 @@ func (m *StringPayload) Marshal() (dAtA []byte, err error) {
}
func (m *StringPayload) MarshalTo(dAtA []byte) (int, error) {
var i int
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *StringPayload) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Msg) > 0 {
dAtA[i] = 0xa
i++
i -= len(m.Msg)
copy(dAtA[i:], m.Msg)
i = encodeVarintHintdetail(dAtA, i, uint64(len(m.Msg)))
i += copy(dAtA[i:], m.Msg)
i--
dAtA[i] = 0xa
}
return i, nil
return len(dAtA) - i, nil
}
func encodeVarintHintdetail(dAtA []byte, offset int, v uint64) int {
offset -= sovHintdetail(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
return base
}
func (m *StringPayload) Size() (n int) {
if m == nil {
@@ -103,14 +129,7 @@ func (m *StringPayload) Size() (n int) {
}
func sovHintdetail(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
return (math_bits.Len64(x|1) + 6) / 7
}
func sozHintdetail(x uint64) (n int) {
return sovHintdetail(uint64((x << 1) ^ uint64((int64(x) >> 63))))
@@ -130,7 +149,7 @@ func (m *StringPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -158,7 +177,7 @@ func (m *StringPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -168,6 +187,9 @@ func (m *StringPayload) Unmarshal(dAtA []byte) error {
return ErrInvalidLengthHintdetail
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthHintdetail
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
@@ -179,7 +201,7 @@ func (m *StringPayload) Unmarshal(dAtA []byte) error {
if err != nil {
return err
}
if skippy < 0 {
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthHintdetail
}
if (iNdEx + skippy) > l {
@@ -197,6 +219,7 @@ func (m *StringPayload) Unmarshal(dAtA []byte) error {
func skipHintdetail(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -228,10 +251,8 @@ func skipHintdetail(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -248,72 +269,34 @@ func skipHintdetail(dAtA []byte) (n int, err error) {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthHintdetail
}
return iNdEx, nil
iNdEx += length
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowHintdetail
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipHintdetail(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupHintdetail
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthHintdetail
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthHintdetail = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowHintdetail = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthHintdetail = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowHintdetail = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupHintdetail = fmt.Errorf("proto: unexpected end of group")
)
func init() {
proto.RegisterFile("github.com/cockroachdb/errors/errorspb/hintdetail.proto", fileDescriptor_hintdetail_429641491537a364)
}
var fileDescriptor_hintdetail_429641491537a364 = []byte{
// 161 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x4f, 0xcf, 0x2c, 0xc9,
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x4f, 0xce, 0x2e, 0xca, 0x4f, 0x4c, 0xce,
0x48, 0x49, 0xd2, 0x4f, 0x2d, 0x2a, 0xca, 0x2f, 0x2a, 0x86, 0x52, 0x05, 0x49, 0xfa, 0x19, 0x99,
0x79, 0x25, 0x29, 0xa9, 0x25, 0x89, 0x99, 0x39, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x42,
0x70, 0xd5, 0x7a, 0x30, 0x45, 0x4a, 0x8a, 0x5c, 0xbc, 0xc1, 0x25, 0x45, 0x99, 0x79, 0xe9, 0x01,
0x89, 0x95, 0x39, 0xf9, 0x89, 0x29, 0x42, 0x02, 0x5c, 0xcc, 0xb9, 0xc5, 0xe9, 0x12, 0x8c, 0x0a,
0x8c, 0x1a, 0x9c, 0x41, 0x20, 0xa6, 0x93, 0xd6, 0x89, 0x87, 0x72, 0x0c, 0x27, 0x1e, 0xc9, 0x31,
0x5e, 0x78, 0x24, 0xc7, 0x78, 0xe3, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb,
0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x14, 0x07, 0xcc, 0xb8, 0x24, 0x36,
0xb0, 0x4d, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x71, 0x36, 0x88, 0xa4, 0x00, 0x00,
0x00,
}

View File

@@ -1,13 +1,16 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: github.com/cockroachdb/errors/errorspb/markers.proto
// source: errorspb/markers.proto
package errorspb
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import io "io"
import (
fmt "fmt"
_ "github.com/gogo/protobuf/gogoproto"
proto "github.com/gogo/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@@ -18,7 +21,7 @@ var _ = math.Inf
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// MarkPayload is the error payload for a forced marker.
// See errors/markers/markers.go and the RFC on
@@ -32,21 +35,21 @@ func (m *MarkPayload) Reset() { *m = MarkPayload{} }
func (m *MarkPayload) String() string { return proto.CompactTextString(m) }
func (*MarkPayload) ProtoMessage() {}
func (*MarkPayload) Descriptor() ([]byte, []int) {
return fileDescriptor_markers_2a35e57f2b6b5666, []int{0}
return fileDescriptor_84c3fd24ba37816d, []int{0}
}
func (m *MarkPayload) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *MarkPayload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (dst *MarkPayload) XXX_Merge(src proto.Message) {
xxx_messageInfo_MarkPayload.Merge(dst, src)
func (m *MarkPayload) XXX_Merge(src proto.Message) {
xxx_messageInfo_MarkPayload.Merge(m, src)
}
func (m *MarkPayload) XXX_Size() int {
return m.Size()
@@ -60,10 +63,30 @@ var xxx_messageInfo_MarkPayload proto.InternalMessageInfo
func init() {
proto.RegisterType((*MarkPayload)(nil), "cockroach.errorspb.MarkPayload")
}
func init() { proto.RegisterFile("errorspb/markers.proto", fileDescriptor_84c3fd24ba37816d) }
var fileDescriptor_84c3fd24ba37816d = []byte{
// 198 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4b, 0x2d, 0x2a, 0xca,
0x2f, 0x2a, 0x2e, 0x48, 0xd2, 0xcf, 0x4d, 0x2c, 0xca, 0x4e, 0x2d, 0x2a, 0xd6, 0x2b, 0x28, 0xca,
0x2f, 0xc9, 0x17, 0x12, 0x4a, 0xce, 0x4f, 0xce, 0x2e, 0xca, 0x4f, 0x4c, 0xce, 0xd0, 0x83, 0xa9,
0x90, 0x12, 0x85, 0xab, 0x85, 0x30, 0x20, 0x4a, 0xa5, 0x44, 0xd2, 0xf3, 0xd3, 0xf3, 0xc1, 0x4c,
0x7d, 0x10, 0x0b, 0x22, 0xaa, 0x14, 0xc7, 0xc5, 0xed, 0x9b, 0x58, 0x94, 0x1d, 0x90, 0x58, 0x99,
0x93, 0x9f, 0x98, 0x22, 0x24, 0xc0, 0xc5, 0x9c, 0x5b, 0x9c, 0x2e, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1,
0x19, 0x04, 0x62, 0x0a, 0xd9, 0x72, 0xb1, 0x96, 0x54, 0x16, 0xa4, 0x16, 0x4b, 0x30, 0x29, 0x30,
0x6b, 0x70, 0x1b, 0x29, 0xea, 0x61, 0xda, 0xa8, 0xe7, 0x0a, 0x62, 0x84, 0x54, 0x16, 0xa4, 0x82,
0x8c, 0x72, 0x62, 0x39, 0x71, 0x4f, 0x9e, 0x21, 0x08, 0xa2, 0xcb, 0x49, 0xeb, 0xc4, 0x43, 0x39,
0x86, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0xbc, 0xf1, 0x48, 0x8e, 0xf1, 0xc1, 0x23,
0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x8a,
0x03, 0x66, 0x4c, 0x12, 0x1b, 0xd8, 0x49, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x18, 0x43,
0x45, 0xef, 0xed, 0x00, 0x00, 0x00,
}
func (m *MarkPayload) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
@@ -71,39 +94,49 @@ func (m *MarkPayload) Marshal() (dAtA []byte, err error) {
}
func (m *MarkPayload) MarshalTo(dAtA []byte) (int, error) {
var i int
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *MarkPayload) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Msg) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintMarkers(dAtA, i, uint64(len(m.Msg)))
i += copy(dAtA[i:], m.Msg)
}
if len(m.Types) > 0 {
for _, msg := range m.Types {
dAtA[i] = 0x12
i++
i = encodeVarintMarkers(dAtA, i, uint64(msg.Size()))
n, err := msg.MarshalTo(dAtA[i:])
if err != nil {
return 0, err
for iNdEx := len(m.Types) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Types[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintMarkers(dAtA, i, uint64(size))
}
i += n
i--
dAtA[i] = 0x12
}
}
return i, nil
if len(m.Msg) > 0 {
i -= len(m.Msg)
copy(dAtA[i:], m.Msg)
i = encodeVarintMarkers(dAtA, i, uint64(len(m.Msg)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintMarkers(dAtA []byte, offset int, v uint64) int {
offset -= sovMarkers(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
return base
}
func (m *MarkPayload) Size() (n int) {
if m == nil {
@@ -125,14 +158,7 @@ func (m *MarkPayload) Size() (n int) {
}
func sovMarkers(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
return (math_bits.Len64(x|1) + 6) / 7
}
func sozMarkers(x uint64) (n int) {
return sovMarkers(uint64((x << 1) ^ uint64((int64(x) >> 63))))
@@ -152,7 +178,7 @@ func (m *MarkPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -180,7 +206,7 @@ func (m *MarkPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -190,6 +216,9 @@ func (m *MarkPayload) Unmarshal(dAtA []byte) error {
return ErrInvalidLengthMarkers
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthMarkers
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
@@ -209,7 +238,7 @@ func (m *MarkPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -218,6 +247,9 @@ func (m *MarkPayload) Unmarshal(dAtA []byte) error {
return ErrInvalidLengthMarkers
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthMarkers
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
@@ -232,7 +264,7 @@ func (m *MarkPayload) Unmarshal(dAtA []byte) error {
if err != nil {
return err
}
if skippy < 0 {
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthMarkers
}
if (iNdEx + skippy) > l {
@@ -250,6 +282,7 @@ func (m *MarkPayload) Unmarshal(dAtA []byte) error {
func skipMarkers(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -281,10 +314,8 @@ func skipMarkers(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -301,75 +332,34 @@ func skipMarkers(dAtA []byte) (n int, err error) {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthMarkers
}
return iNdEx, nil
iNdEx += length
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowMarkers
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipMarkers(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupMarkers
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthMarkers
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthMarkers = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowMarkers = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthMarkers = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowMarkers = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupMarkers = fmt.Errorf("proto: unexpected end of group")
)
func init() {
proto.RegisterFile("github.com/cockroachdb/errors/errorspb/markers.proto", fileDescriptor_markers_2a35e57f2b6b5666)
}
var fileDescriptor_markers_2a35e57f2b6b5666 = []byte{
// 216 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x49, 0xcf, 0x2c, 0xc9,
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x4f, 0xce, 0x2e, 0xca, 0x4f, 0x4c, 0xce,
0x48, 0x49, 0xd2, 0x4f, 0x2d, 0x2a, 0xca, 0x2f, 0x2a, 0x86, 0x52, 0x05, 0x49, 0xfa, 0xb9, 0x89,
0x45, 0xd9, 0xa9, 0x45, 0xc5, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x42, 0x70, 0xa5, 0x7a,
0x30, 0x15, 0x52, 0xc6, 0x44, 0x9a, 0x04, 0x61, 0x40, 0x0c, 0x92, 0x12, 0x49, 0xcf, 0x4f, 0xcf,
0x07, 0x33, 0xf5, 0x41, 0x2c, 0x88, 0xa8, 0x52, 0x1c, 0x17, 0xb7, 0x6f, 0x62, 0x51, 0x76, 0x40,
0x62, 0x65, 0x4e, 0x7e, 0x62, 0x8a, 0x90, 0x00, 0x17, 0x73, 0x6e, 0x71, 0xba, 0x04, 0xa3, 0x02,
0xa3, 0x06, 0x67, 0x10, 0x88, 0x29, 0x64, 0xcb, 0xc5, 0x5a, 0x52, 0x59, 0x90, 0x5a, 0x2c, 0xc1,
0xa4, 0xc0, 0xac, 0xc1, 0x6d, 0xa4, 0xa8, 0x87, 0xe9, 0x1e, 0x3d, 0x57, 0x10, 0x23, 0xa4, 0xb2,
0x20, 0x15, 0x64, 0x94, 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x10, 0x5d, 0x4e, 0x5a, 0x27,
0x1e, 0xca, 0x31, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x8d, 0x47, 0x72, 0x8c,
0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72,
0x0c, 0x51, 0x1c, 0x30, 0x63, 0x92, 0xd8, 0xc0, 0x4e, 0x32, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff,
0xdc, 0x2d, 0x59, 0x6b, 0x29, 0x01, 0x00, 0x00,
}

View File

@@ -2,7 +2,7 @@ syntax = "proto3";
package cockroach.errorspb;
option go_package = "errorspb";
import "github.com/cockroachdb/errors/errorspb/errors.proto";
import "errorspb/errors.proto";
import "gogoproto/gogo.proto";
// MarkPayload is the error payload for a forced marker.

View File

@@ -1,13 +1,16 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: github.com/cockroachdb/errors/errorspb/tags.proto
// source: errorspb/tags.proto
package errorspb
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import io "io"
import (
fmt "fmt"
_ "github.com/gogo/protobuf/gogoproto"
proto "github.com/gogo/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@@ -18,7 +21,7 @@ var _ = math.Inf
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// TagsPayload is the error payload for a WithContext
// marker.
@@ -32,21 +35,21 @@ func (m *TagsPayload) Reset() { *m = TagsPayload{} }
func (m *TagsPayload) String() string { return proto.CompactTextString(m) }
func (*TagsPayload) ProtoMessage() {}
func (*TagsPayload) Descriptor() ([]byte, []int) {
return fileDescriptor_tags_c75c5e322ef6b31c, []int{0}
return fileDescriptor_2f0d1dc5c54e9f63, []int{0}
}
func (m *TagsPayload) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *TagsPayload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (dst *TagsPayload) XXX_Merge(src proto.Message) {
xxx_messageInfo_TagsPayload.Merge(dst, src)
func (m *TagsPayload) XXX_Merge(src proto.Message) {
xxx_messageInfo_TagsPayload.Merge(m, src)
}
func (m *TagsPayload) XXX_Size() int {
return m.Size()
@@ -66,21 +69,21 @@ func (m *TagPayload) Reset() { *m = TagPayload{} }
func (m *TagPayload) String() string { return proto.CompactTextString(m) }
func (*TagPayload) ProtoMessage() {}
func (*TagPayload) Descriptor() ([]byte, []int) {
return fileDescriptor_tags_c75c5e322ef6b31c, []int{1}
return fileDescriptor_2f0d1dc5c54e9f63, []int{1}
}
func (m *TagPayload) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *TagPayload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (dst *TagPayload) XXX_Merge(src proto.Message) {
xxx_messageInfo_TagPayload.Merge(dst, src)
func (m *TagPayload) XXX_Merge(src proto.Message) {
xxx_messageInfo_TagPayload.Merge(m, src)
}
func (m *TagPayload) XXX_Size() int {
return m.Size()
@@ -95,10 +98,30 @@ func init() {
proto.RegisterType((*TagsPayload)(nil), "cockroach.errorspb.TagsPayload")
proto.RegisterType((*TagPayload)(nil), "cockroach.errorspb.TagPayload")
}
func init() { proto.RegisterFile("errorspb/tags.proto", fileDescriptor_2f0d1dc5c54e9f63) }
var fileDescriptor_2f0d1dc5c54e9f63 = []byte{
// 201 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0x2d, 0x2a, 0xca,
0x2f, 0x2a, 0x2e, 0x48, 0xd2, 0x2f, 0x49, 0x4c, 0x2f, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
0x12, 0x4a, 0xce, 0x4f, 0xce, 0x2e, 0xca, 0x4f, 0x4c, 0xce, 0xd0, 0x83, 0x49, 0x4b, 0x89, 0xa4,
0xe7, 0xa7, 0xe7, 0x83, 0xa5, 0xf5, 0x41, 0x2c, 0x88, 0x4a, 0x25, 0x77, 0x2e, 0xee, 0x90, 0xc4,
0xf4, 0xe2, 0x80, 0xc4, 0xca, 0x9c, 0xfc, 0xc4, 0x14, 0x21, 0x0b, 0x2e, 0x16, 0x90, 0x31, 0x12,
0x8c, 0x0a, 0xcc, 0x1a, 0xdc, 0x46, 0x72, 0x7a, 0x98, 0xe6, 0xe8, 0x85, 0x24, 0xa6, 0x43, 0x55,
0x3b, 0xb1, 0x9c, 0xb8, 0x27, 0xcf, 0x10, 0x04, 0xd6, 0xa1, 0x64, 0xc2, 0xc5, 0x85, 0x90, 0x11,
0x12, 0xe0, 0x62, 0x2e, 0x49, 0x4c, 0x97, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x02, 0x31, 0x85,
0x44, 0xb8, 0x58, 0xcb, 0x12, 0x73, 0x4a, 0x53, 0x25, 0x98, 0xc0, 0x62, 0x10, 0x8e, 0x93, 0xd6,
0x89, 0x87, 0x72, 0x0c, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0x78, 0xe3, 0x91, 0x1c,
0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1,
0x1c, 0x43, 0x14, 0x07, 0xcc, 0xe2, 0x24, 0x36, 0xb0, 0x8b, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff,
0xff, 0x0a, 0x90, 0xd1, 0xc9, 0xf2, 0x00, 0x00, 0x00,
}
func (m *TagsPayload) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
@@ -106,29 +129,36 @@ func (m *TagsPayload) Marshal() (dAtA []byte, err error) {
}
func (m *TagsPayload) MarshalTo(dAtA []byte) (int, error) {
var i int
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *TagsPayload) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Tags) > 0 {
for _, msg := range m.Tags {
dAtA[i] = 0xa
i++
i = encodeVarintTags(dAtA, i, uint64(msg.Size()))
n, err := msg.MarshalTo(dAtA[i:])
if err != nil {
return 0, err
for iNdEx := len(m.Tags) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Tags[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintTags(dAtA, i, uint64(size))
}
i += n
i--
dAtA[i] = 0xa
}
}
return i, nil
return len(dAtA) - i, nil
}
func (m *TagPayload) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
@@ -136,33 +166,42 @@ func (m *TagPayload) Marshal() (dAtA []byte, err error) {
}
func (m *TagPayload) MarshalTo(dAtA []byte) (int, error) {
var i int
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *TagPayload) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Tag) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintTags(dAtA, i, uint64(len(m.Tag)))
i += copy(dAtA[i:], m.Tag)
}
if len(m.Value) > 0 {
dAtA[i] = 0x12
i++
i -= len(m.Value)
copy(dAtA[i:], m.Value)
i = encodeVarintTags(dAtA, i, uint64(len(m.Value)))
i += copy(dAtA[i:], m.Value)
i--
dAtA[i] = 0x12
}
return i, nil
if len(m.Tag) > 0 {
i -= len(m.Tag)
copy(dAtA[i:], m.Tag)
i = encodeVarintTags(dAtA, i, uint64(len(m.Tag)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintTags(dAtA []byte, offset int, v uint64) int {
offset -= sovTags(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
return base
}
func (m *TagsPayload) Size() (n int) {
if m == nil {
@@ -197,14 +236,7 @@ func (m *TagPayload) Size() (n int) {
}
func sovTags(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
return (math_bits.Len64(x|1) + 6) / 7
}
func sozTags(x uint64) (n int) {
return sovTags(uint64((x << 1) ^ uint64((int64(x) >> 63))))
@@ -224,7 +256,7 @@ func (m *TagsPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -252,7 +284,7 @@ func (m *TagsPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -261,6 +293,9 @@ func (m *TagsPayload) Unmarshal(dAtA []byte) error {
return ErrInvalidLengthTags
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthTags
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
@@ -275,7 +310,7 @@ func (m *TagsPayload) Unmarshal(dAtA []byte) error {
if err != nil {
return err
}
if skippy < 0 {
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthTags
}
if (iNdEx + skippy) > l {
@@ -305,7 +340,7 @@ func (m *TagPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -333,7 +368,7 @@ func (m *TagPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -343,6 +378,9 @@ func (m *TagPayload) Unmarshal(dAtA []byte) error {
return ErrInvalidLengthTags
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthTags
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
@@ -362,7 +400,7 @@ func (m *TagPayload) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -372,6 +410,9 @@ func (m *TagPayload) Unmarshal(dAtA []byte) error {
return ErrInvalidLengthTags
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthTags
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
@@ -383,7 +424,7 @@ func (m *TagPayload) Unmarshal(dAtA []byte) error {
if err != nil {
return err
}
if skippy < 0 {
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthTags
}
if (iNdEx + skippy) > l {
@@ -401,6 +442,7 @@ func (m *TagPayload) Unmarshal(dAtA []byte) error {
func skipTags(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -432,10 +474,8 @@ func skipTags(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -452,75 +492,34 @@ func skipTags(dAtA []byte) (n int, err error) {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthTags
}
return iNdEx, nil
iNdEx += length
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowTags
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipTags(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupTags
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthTags
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthTags = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowTags = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthTags = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowTags = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupTags = fmt.Errorf("proto: unexpected end of group")
)
func init() {
proto.RegisterFile("github.com/cockroachdb/errors/errorspb/tags.proto", fileDescriptor_tags_c75c5e322ef6b31c)
}
var fileDescriptor_tags_c75c5e322ef6b31c = []byte{
// 218 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x4c, 0xcf, 0x2c, 0xc9,
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x4f, 0xce, 0x2e, 0xca, 0x4f, 0x4c, 0xce,
0x48, 0x49, 0xd2, 0x4f, 0x2d, 0x2a, 0xca, 0x2f, 0x2a, 0x86, 0x52, 0x05, 0x49, 0xfa, 0x25, 0x89,
0xe9, 0xc5, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x42, 0x70, 0x75, 0x7a, 0x30, 0x69, 0x29,
0x91, 0xf4, 0xfc, 0xf4, 0x7c, 0xb0, 0xb4, 0x3e, 0x88, 0x05, 0x51, 0xa9, 0xe4, 0xce, 0xc5, 0x1d,
0x92, 0x98, 0x5e, 0x1c, 0x90, 0x58, 0x99, 0x93, 0x9f, 0x98, 0x22, 0x64, 0xc1, 0xc5, 0x02, 0x32,
0x46, 0x82, 0x51, 0x81, 0x59, 0x83, 0xdb, 0x48, 0x4e, 0x0f, 0xd3, 0x1c, 0xbd, 0x90, 0xc4, 0x74,
0xa8, 0x6a, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, 0x82, 0xc0, 0x3a, 0x94, 0x4c, 0xb8, 0xb8, 0x10,
0x32, 0x42, 0x02, 0x5c, 0xcc, 0x25, 0x89, 0xe9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x20,
0xa6, 0x90, 0x08, 0x17, 0x6b, 0x59, 0x62, 0x4e, 0x69, 0xaa, 0x04, 0x13, 0x58, 0x0c, 0xc2, 0x71,
0xd2, 0x3a, 0xf1, 0x50, 0x8e, 0xe1, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x6f, 0x3c,
0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e,
0x3c, 0x96, 0x63, 0x88, 0xe2, 0x80, 0x59, 0x9c, 0xc4, 0x06, 0x76, 0xb1, 0x31, 0x20, 0x00, 0x00,
0xff, 0xff, 0xa6, 0x1b, 0x01, 0x30, 0x10, 0x01, 0x00, 0x00,
}

View File

@@ -1,13 +1,15 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: github.com/cockroachdb/errors/errorspb/testing.proto
// source: errorspb/testing.proto
package errorspb
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import io "io"
import (
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
@@ -18,7 +20,7 @@ var _ = math.Inf
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// TestError is meant for use in testing only.
type TestError struct {
@@ -28,21 +30,21 @@ func (m *TestError) Reset() { *m = TestError{} }
func (m *TestError) String() string { return proto.CompactTextString(m) }
func (*TestError) ProtoMessage() {}
func (*TestError) Descriptor() ([]byte, []int) {
return fileDescriptor_testing_d9751a0a5e86c096, []int{0}
return fileDescriptor_0551f0d913d6118f, []int{0}
}
func (m *TestError) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *TestError) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
b = b[:cap(b)]
n, err := m.MarshalTo(b)
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
func (dst *TestError) XXX_Merge(src proto.Message) {
xxx_messageInfo_TestError.Merge(dst, src)
func (m *TestError) XXX_Merge(src proto.Message) {
xxx_messageInfo_TestError.Merge(m, src)
}
func (m *TestError) XXX_Size() int {
return m.Size()
@@ -56,10 +58,25 @@ var xxx_messageInfo_TestError proto.InternalMessageInfo
func init() {
proto.RegisterType((*TestError)(nil), "cockroach.errorspb.TestError")
}
func init() { proto.RegisterFile("errorspb/testing.proto", fileDescriptor_0551f0d913d6118f) }
var fileDescriptor_0551f0d913d6118f = []byte{
// 118 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4b, 0x2d, 0x2a, 0xca,
0x2f, 0x2a, 0x2e, 0x48, 0xd2, 0x2f, 0x49, 0x2d, 0x2e, 0xc9, 0xcc, 0x4b, 0xd7, 0x2b, 0x28, 0xca,
0x2f, 0xc9, 0x17, 0x12, 0x4a, 0xce, 0x4f, 0xce, 0x2e, 0xca, 0x4f, 0x4c, 0xce, 0xd0, 0x83, 0xa9,
0x50, 0xe2, 0xe6, 0xe2, 0x0c, 0x49, 0x2d, 0x2e, 0x71, 0x05, 0xf1, 0x9d, 0xb4, 0x4e, 0x3c, 0x94,
0x63, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x1b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c,
0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2,
0x38, 0x60, 0x1a, 0x93, 0xd8, 0xc0, 0x66, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x4b, 0xf3,
0x3e, 0x92, 0x6d, 0x00, 0x00, 0x00,
}
func (m *TestError) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
@@ -67,21 +84,28 @@ func (m *TestError) Marshal() (dAtA []byte, err error) {
}
func (m *TestError) MarshalTo(dAtA []byte) (int, error) {
var i int
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *TestError) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
return i, nil
return len(dAtA) - i, nil
}
func encodeVarintTesting(dAtA []byte, offset int, v uint64) int {
offset -= sovTesting(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
return base
}
func (m *TestError) Size() (n int) {
if m == nil {
@@ -93,14 +117,7 @@ func (m *TestError) Size() (n int) {
}
func sovTesting(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
return (math_bits.Len64(x|1) + 6) / 7
}
func sozTesting(x uint64) (n int) {
return sovTesting(uint64((x << 1) ^ uint64((int64(x) >> 63))))
@@ -120,7 +137,7 @@ func (m *TestError) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
@@ -140,7 +157,7 @@ func (m *TestError) Unmarshal(dAtA []byte) error {
if err != nil {
return err
}
if skippy < 0 {
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthTesting
}
if (iNdEx + skippy) > l {
@@ -158,6 +175,7 @@ func (m *TestError) Unmarshal(dAtA []byte) error {
func skipTesting(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
@@ -189,10 +207,8 @@ func skipTesting(dAtA []byte) (n int, err error) {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
@@ -209,70 +225,34 @@ func skipTesting(dAtA []byte) (n int, err error) {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthTesting
}
return iNdEx, nil
iNdEx += length
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowTesting
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipTesting(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
depth++
case 4:
return iNdEx, nil
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupTesting
}
depth--
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthTesting
}
if depth == 0 {
return iNdEx, nil
}
}
panic("unreachable")
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthTesting = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowTesting = fmt.Errorf("proto: integer overflow")
ErrInvalidLengthTesting = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowTesting = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupTesting = fmt.Errorf("proto: unexpected end of group")
)
func init() {
proto.RegisterFile("github.com/cockroachdb/errors/errorspb/testing.proto", fileDescriptor_testing_d9751a0a5e86c096)
}
var fileDescriptor_testing_d9751a0a5e86c096 = []byte{
// 136 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0x49, 0xcf, 0x2c, 0xc9,
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0x4f, 0xce, 0x2e, 0xca, 0x4f, 0x4c, 0xce,
0x48, 0x49, 0xd2, 0x4f, 0x2d, 0x2a, 0xca, 0x2f, 0x2a, 0x86, 0x52, 0x05, 0x49, 0xfa, 0x25, 0xa9,
0xc5, 0x25, 0x99, 0x79, 0xe9, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0x42, 0x70, 0xa5, 0x7a,
0x30, 0x15, 0x4a, 0xdc, 0x5c, 0x9c, 0x21, 0xa9, 0xc5, 0x25, 0xae, 0x20, 0xbe, 0x93, 0xd6, 0x89,
0x87, 0x72, 0x0c, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0x78, 0xe3, 0x91, 0x1c, 0xe3,
0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c,
0x43, 0x14, 0x07, 0x4c, 0x63, 0x12, 0x1b, 0xd8, 0x4c, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff,
0x8a, 0x2c, 0x50, 0x5f, 0x8b, 0x00, 0x00, 0x00,
}

View File

@@ -28,12 +28,22 @@ import (
// Is determines whether one of the causes of the given error or any
// of its causes is equivalent to some reference error.
//
// As in the Go standard library, an error is considered to match a
// reference error if it is equal to that target or if it implements a
// method Is(error) bool such that Is(reference) returns true.
//
// Note: the inverse is not true - making an Is(reference) method
// return false does not imply that errors.Is() also returns
// false. Errors can be equal because their network equality marker is
// the same. To force errors to appear different to Is(), use
// errors.Mark().
//
// Note: if any of the error types has been migrated from a previous
// package location or a different type, ensure that
// RegisterTypeMigration() was called prior to Is().
func Is(err, reference error) bool {
if reference == nil {
return err == reference
return err == nil
}
// Direct reference comparison is the fastest, and most
@@ -42,6 +52,11 @@ func Is(err, reference error) bool {
if c == reference {
return true
}
// Compatibility with std go errors: if the error object itself
// implements Is(), try to use that.
if tryDelegateToIsMethod(c, reference) {
return true
}
}
if err == nil {
@@ -67,6 +82,13 @@ func Is(err, reference error) bool {
return false
}
func tryDelegateToIsMethod(err, reference error) bool {
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(reference) {
return true
}
return false
}
// HasType returns true iff err contains an error whose concrete type
// matches that of referenceType.
func HasType(err error, referenceType error) bool {
@@ -125,6 +147,11 @@ func IsAny(err error, references ...error) bool {
if c == refErr {
return true
}
// Compatibility with std go errors: if the error object itself
// implements Is(), try to use that.
if tryDelegateToIsMethod(c, refErr) {
return true
}
}
if c == nil {
// This special case is to support a comparison to a nil

View File

@@ -19,6 +19,16 @@ import "github.com/cockroachdb/errors/markers"
// Is determines whether one of the causes of the given error or any
// of its causes is equivalent to some reference error.
//
// As in the Go standard library, an error is considered to match a
// reference error if it is equal to that target or if it implements a
// method Is(error) bool such that Is(reference) returns true.
//
// Note: the inverse is not true - making an Is(reference) method
// return false does not imply that errors.Is() also returns
// false. Errors can be equal because their network equality marker is
// the same. To force errors to appear different to Is(), use
// errors.Mark().
//
// Note: if any of the error types has been migrated from a previous
// package location or a different type, ensure that
// RegisterTypeMigration() was called prior to Is().

View File

@@ -15,7 +15,6 @@
package report
import (
"bytes"
"fmt"
"strings"
@@ -23,7 +22,7 @@ import (
"github.com/cockroachdb/errors/errbase"
"github.com/cockroachdb/errors/withstack"
"github.com/cockroachdb/redact"
"github.com/cockroachdb/sentry-go"
sentry "github.com/getsentry/sentry-go"
)
// BuildSentryReport builds the components of a sentry report. This
@@ -76,13 +75,13 @@ import (
// Given this mapping, an error object is decomposed as follows:
//
// (1)/(7): <filename>:<lineno> (<functionname>)
// (3)/(8): <error type>: <first safe detail line, if any>
// (3)/(8): first line of verbose error printout
// (4): not populated in this function, caller is to manage this
// (5): detailed structure of the entire error object, with references to "additional data"
// and additional "exception" objects
// (5): detailed structure of the entire error object, with references to
// additional "exception" objects
// (9): generated from innermost stack trace
// (6): every exception object after the 1st reports additional stack trace contexts
// (11): "additional data" populated from safe detail payloads
// (11): the detailed error types and their error mark.
//
// If there is no stack trace in the error, a synthetic Exception
// object is still produced to provide visual detail in the Sentry UI.
@@ -119,17 +118,22 @@ func BuildSentryReport(err error) (event *sentry.Event, extraDetails map[string]
var firstDetailLine string
// longMsgBuf will become the Message field, which contains the full
// structure of the error with cross-references to "Exception" and
// "Additional data" fields.
// structure of the error.
var longMsgBuf strings.Builder
redactedErrStr := redact.Sprint(err).Redact()
if redactedErrStr != redactedMarker {
if f, l, _, ok := withstack.GetOneLineSource(err); ok {
fmt.Fprintf(&longMsgBuf, "%s:%d: ", f, l)
}
firstDetailLine = redactedErrStr.StripMarkers()
fmt.Fprintf(&longMsgBuf, "%v\n--\n", firstDetailLine)
if f, l, _, ok := withstack.GetOneLineSource(err); ok {
fmt.Fprintf(&longMsgBuf, "%s:%d: ", f, l)
}
// Include the verbose error printout, with sensitive bits redacted out.
verboseErr := redact.Sprintf("%+v", err).Redact().StripMarkers()
if verboseErr != redactedMarker {
idx := strings.IndexByte(verboseErr, '\n')
if idx == -1 {
firstDetailLine = verboseErr
} else {
firstDetailLine = verboseErr[:idx]
}
}
fmt.Fprint(&longMsgBuf, verboseErr)
// sep is used to separate the entries in the longMsgBuf / Message
// payload.
@@ -159,6 +163,7 @@ func BuildSentryReport(err error) (event *sentry.Event, extraDetails map[string]
// layer. We iterate in this order because we want to describe the
// error from innermost to outermost layer in longMsgBuf and
// typesBuf.
longMsgBuf.WriteString("\n-- report composition:\n")
for i := len(details) - 1; i >= 0; i-- {
// Collect the type name for this layer of error wrapping, towards
// the "error types" additional data field.
@@ -204,10 +209,6 @@ func BuildSentryReport(err error) (event *sentry.Event, extraDetails map[string]
// Now decide what kind of payload we want to add to the Event
// object.
// genExtra will remember whether we are adding an
// additional payload or not.
var genExtra bool
// Is there a stack trace?
if st := stacks[i]; st != nil {
var excType strings.Builder
@@ -245,17 +246,21 @@ func BuildSentryReport(err error) (event *sentry.Event, extraDetails map[string]
exceptions = append(exceptions, exc)
} else {
// No stack trace.
// Are there safe details? If so, print them.
// Are there safe details? If so, print the first safe detail
// string (we're assuming that all the important bits will
// also be included in the verbose printout, so there's no
// need to dig out more safe strings here.)
//
// TODO(knz): the SafeDetails API is not really meant for Sentry
// reporting. Once we have more experience to prove that the
// verbose printout is sufficient, we can remove the SafeDetails
// from sentry reports.
//
// Note: we only print the details if no stack trace was found
// at that level. This is because stack trace annotations also
// produce the stack trace as safe detail string.
genExtra = len(details[i].SafeDetails) > 1
if len(details[i].SafeDetails) > 0 {
d := details[i].SafeDetails[0]
if d != "" {
genExtra = true
}
if j := strings.IndexByte(d, '\n'); j >= 0 {
d = d[:j]
}
@@ -269,18 +274,6 @@ func BuildSentryReport(err error) (event *sentry.Event, extraDetails map[string]
}
}
}
// Are we generating another extra for the safe detail strings?
if genExtra {
stKey := fmt.Sprintf("%d: details", extraNum)
var extraStr bytes.Buffer
for _, d := range details[i].SafeDetails {
fmt.Fprintln(&extraStr, strings.ReplaceAll(d, "\n", "\n "))
}
extras[stKey] = extraStr.String()
fmt.Fprintf(&longMsgBuf, " (%d)", extraNum)
extraNum++
}
}
if extraNum > 1 {
@@ -354,7 +347,7 @@ func BuildSentryReport(err error) (event *sentry.Event, extraDetails map[string]
return event, extras
}
var redactedMarker = redact.RedactableString(redact.RedactedMarker())
var redactedMarker = redact.RedactableString(redact.RedactedMarker()).StripMarkers()
// ReportError reports the given error to Sentry. The caller is responsible for
// checking whether telemetry is enabled, and calling the sentry.Flush()

View File

@@ -18,7 +18,7 @@ import (
"bytes"
"fmt"
"github.com/cockroachdb/sentry-go"
"github.com/getsentry/sentry-go"
)
// StackTrace is an object suitable for inclusion in errors that can

View File

@@ -16,7 +16,7 @@ package errors
import (
"github.com/cockroachdb/errors/report"
"github.com/cockroachdb/sentry-go"
"github.com/getsentry/sentry-go"
)
// BuildSentryReport builds the components of a sentry report. This

View File

@@ -23,7 +23,7 @@ import (
"strings"
"github.com/cockroachdb/errors/errbase"
"github.com/cockroachdb/sentry-go"
"github.com/getsentry/sentry-go"
pkgErr "github.com/pkg/errors"
)

View File

@@ -67,7 +67,7 @@ func (b *Buffer) Merge(other *Buffer) *Buffer {
if b == nil || len(b.tags) == 0 {
return other
}
if len(other.tags) == 0 {
if other == nil || len(other.tags) == 0 {
return b
}

View File

@@ -2,3 +2,5 @@
cpu.*.prof
heap.prof
mutex.prof
# Testing artifacts
meta.*.test

View File

@@ -4,6 +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)
.PHONY: all
all:
@@ -13,13 +14,14 @@ all:
@echo " make stress"
@echo " make stressrace"
@echo " make stressmeta"
@echo " make crossversion-meta"
@echo " make mod-update"
@echo " make clean"
override testflags :=
.PHONY: test
test:
${GO} test -mod=vendor -tags '$(TAGS)' ${testflags} -run ${TESTS} ${PKG}
${GO} test -tags '$(TAGS)' ${testflags} -run ${TESTS} ${PKG}
.PHONY: testrace
testrace: testflags += -race -timeout 20m
@@ -36,14 +38,25 @@ stressmeta: override STRESSFLAGS += -p 1
stressmeta: override TESTS = TestMeta$$
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 -; \
${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'
.PHONY: stress-crossversion
stress-crossversion:
STRESS=1 ./scripts/run-crossversion-meta.sh crl-release-21.2 crl-release-22.1 crl-release-22.2 master
.PHONY: generate
generate:
${GO} generate -mod=vendor ${PKG}
${GO} generate ${PKG}
mod-update:
${GO} get -u
${GO} mod tidy
${GO} mod vendor
.PHONY: clean
clean:
@@ -70,7 +83,7 @@ endif
.PHONY: format
format:
for _file in $$(gofmt -s -l . | grep -vE '^vendor/'); do \
for _file in $$(gofmt -s -l .); do \
gofmt -s -w $$_file ; \
done
@@ -80,4 +93,5 @@ ifneq ($(git_dirty),)
$(error format-check must be invoked on a clean repository)
endif
$(MAKE) format
git diff
$(MAKE) git-clean-check

View File

@@ -78,7 +78,9 @@ func (d DeferredBatchOp) Finish() error {
// RangeKeyUnsets, and/or RangeKeyDeletes that are applied atomically. Batch
// implements the Reader interface, but only an indexed batch supports reading
// (without error) via Get or NewIter. A non-indexed batch will return
// ErrNotIndexed when read from .
// ErrNotIndexed when read from. A batch is not safe for concurrent use, and
// consumers should use a batch per goroutine or provide their own
// synchronization.
//
// Indexing
//
@@ -233,13 +235,21 @@ type Batch struct {
// Fragmented range deletion tombstones. Cached the first time a range
// deletion iterator is requested. The cache is invalidated whenever a new
// range deletion is added to the batch.
tombstones []keyspan.Span
// range deletion is added to the batch. This cache can only be used when
// opening an iterator to read at a batch sequence number >=
// tombstonesSeqNum. This is the case for all new iterators created over a
// batch but it's not the case for all cloned iterators.
tombstones []keyspan.Span
tombstonesSeqNum uint64
// Fragmented range key spans. Cached the first time a range key iterator is
// requested. The cache is invalidated whenever a new range key
// (RangeKey{Set,Unset,Del}) is added to the batch.
rangeKeys []keyspan.Span
// (RangeKey{Set,Unset,Del}) is added to the batch. This cache can only be
// used when opening an iterator to read at a batch sequence number >=
// tombstonesSeqNum. This is the case for all new iterators created over a
// batch but it's not the case for all cloned iterators.
rangeKeys []keyspan.Span
rangeKeysSeqNum uint64
// The flushableBatch wrapper if the batch is too large to fit in the
// memtable.
@@ -287,6 +297,16 @@ func newIndexedBatch(db *DB, comparer *Comparer) *Batch {
return &i.batch
}
// nextSeqNum returns the batch "sequence number" that will be given to the next
// key written to the batch. During iteration keys within an indexed batch are
// given a sequence number consisting of their offset within the batch combined
// with the base.InternalKeySeqNumBatch bit. These sequence numbers are only
// used during iteration, and the keys are assigned ordinary sequence numbers
// when the batch is committed.
func (b *Batch) nextSeqNum() uint64 {
return uint64(len(b.data)) | base.InternalKeySeqNumBatch
}
func (b *Batch) release() {
if b.db == nil {
// The batch was not created using newBatch or newIndexedBatch, or an error
@@ -378,12 +398,14 @@ func (b *Batch) Apply(batch *Batch, _ *WriteOptions) error {
switch kind {
case InternalKeyKindRangeDelete:
b.tombstones = nil
b.tombstonesSeqNum = 0
if b.rangeDelIndex == nil {
b.rangeDelIndex = batchskl.NewSkiplist(&b.data, b.cmp, b.abbreviatedKey)
}
err = b.rangeDelIndex.Add(uint32(offset))
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
b.rangeKeys = nil
b.rangeKeysSeqNum = 0
if b.rangeKeyIndex == nil {
b.rangeKeyIndex = batchskl.NewSkiplist(&b.data, b.cmp, b.abbreviatedKey)
}
@@ -638,6 +660,7 @@ func (b *Batch) DeleteRangeDeferred(startLen, endLen int) *DeferredBatchOp {
b.countRangeDels++
if b.index != nil {
b.tombstones = nil
b.tombstonesSeqNum = 0
// Range deletions are rare, so we lazily allocate the index for them.
if b.rangeDelIndex == nil {
b.rangeDelIndex = batchskl.NewSkiplist(&b.data, b.cmp, b.abbreviatedKey)
@@ -647,17 +670,13 @@ func (b *Batch) DeleteRangeDeferred(startLen, endLen int) *DeferredBatchOp {
return &b.deferredOp
}
// Experimental returns the experimental write API, backed by the same batch.
func (b *Batch) Experimental() ExperimentalWriter {
return experimentalBatch{b}
}
type experimentalBatch struct {
*Batch
}
// RangeKeySet implements the ExperimentalWriter interface.
func (b experimentalBatch) RangeKeySet(start, end, suffix, value []byte, _ *WriteOptions) error {
// RangeKeySet sets a range key mapping the key range [start, end) at the MVCC
// timestamp suffix to value. The suffix is optional. If any portion of the key
// range [start, end) is already set by a range key with the same suffix value,
// RangeKeySet overrides it.
//
// It is safe to modify the contents of the arguments after RangeKeySet returns.
func (b *Batch) RangeKeySet(start, end, suffix, value []byte, _ *WriteOptions) error {
suffixValues := [1]rangekey.SuffixValue{{Suffix: suffix, Value: value}}
internalValueLen := rangekey.EncodedSetValueLen(end, suffixValues[:])
@@ -687,6 +706,7 @@ func (b *Batch) incrementRangeKeysCount() {
b.countRangeKeys++
if b.index != nil {
b.rangeKeys = nil
b.rangeKeysSeqNum = 0
// Range keys are rare, so we lazily allocate the index for them.
if b.rangeKeyIndex == nil {
b.rangeKeyIndex = batchskl.NewSkiplist(&b.data, b.cmp, b.abbreviatedKey)
@@ -695,8 +715,15 @@ func (b *Batch) incrementRangeKeysCount() {
}
}
// RangeKeyUnset implements the ExperimentalWriter interface.
func (b experimentalBatch) RangeKeyUnset(start, end, suffix []byte, _ *WriteOptions) error {
// RangeKeyUnset removes a range key mapping the key range [start, end) at the
// MVCC timestamp suffix. The suffix may be omitted to remove an unsuffixed
// range key. RangeKeyUnset only removes portions of range keys that fall within
// the [start, end) key span, and only range keys with suffixes that exactly
// match the unset suffix.
//
// It is safe to modify the contents of the arguments after RangeKeyUnset
// returns.
func (b *Batch) RangeKeyUnset(start, end, suffix []byte, _ *WriteOptions) error {
suffixes := [1][]byte{suffix}
internalValueLen := rangekey.EncodedUnsetValueLen(end, suffixes[:])
@@ -722,8 +749,14 @@ func (b *Batch) rangeKeyUnsetDeferred(startLen, internalValueLen int) *DeferredB
return &b.deferredOp
}
// RangeKeyDelete implements the ExperimentalWriter interface.
func (b experimentalBatch) RangeKeyDelete(start, end []byte, _ *WriteOptions) error {
// RangeKeyDelete deletes all of the range keys in the range [start,end)
// (inclusive on start, exclusive on end). It does not delete point keys (for
// that use DeleteRange). RangeKeyDelete removes all range keys within the
// bounds, including those with or without suffixes.
//
// It is safe to modify the contents of the arguments after RangeKeyDelete
// returns.
func (b *Batch) RangeKeyDelete(start, end []byte, _ *WriteOptions) error {
deferredOp := b.RangeKeyDeleteDeferred(len(start), len(end))
copy(deferredOp.Key, start)
copy(deferredOp.Value, end)
@@ -769,6 +802,14 @@ func (b *Batch) Empty() bool {
return len(b.data) <= batchHeaderLen
}
// Len returns the current size of the batch in bytes.
func (b *Batch) Len() int {
if len(b.data) <= batchHeaderLen {
return batchHeaderLen
}
return len(b.data)
}
// Repr returns the underlying batch representation. It is not safe to modify
// the contents. Reset() will not change the contents of the returned value,
// though any other mutation operation may do so.
@@ -799,6 +840,10 @@ func (b *Batch) SetRepr(data []byte) error {
// NewIter returns an iterator that is unpositioned (Iterator.Valid() will
// return false). The iterator can be positioned via a call to SeekGE,
// SeekPrefixGE, SeekLT, First or Last. Only indexed batches support iterators.
//
// The returned Iterator observes all of the Batch's existing mutations, but no
// later mutations. Its view can be refreshed via RefreshBatchSnapshot or
// SetOptions().
func (b *Batch) NewIter(o *IterOptions) *Iterator {
if b.index == nil {
return &Iterator{err: ErrNotIndexed}
@@ -808,45 +853,94 @@ func (b *Batch) NewIter(o *IterOptions) *Iterator {
// newInternalIter creates a new internalIterator that iterates over the
// contents of the batch.
func (b *Batch) newInternalIter(o *IterOptions) internalIterator {
if b.index == nil {
return newErrorIter(ErrNotIndexed)
}
return &batchIter{
func (b *Batch) newInternalIter(o *IterOptions) *batchIter {
iter := &batchIter{}
b.initInternalIter(o, iter)
return iter
}
func (b *Batch) initInternalIter(o *IterOptions, iter *batchIter) {
*iter = batchIter{
cmp: b.cmp,
batch: b,
iter: b.index.NewIter(o.GetLowerBound(), o.GetUpperBound()),
// NB: We explicitly do not propagate the batch snapshot to the point
// key iterator. Filtering point keys within the batch iterator can
// cause pathological behavior where a batch iterator advances
// significantly farther than necessary filtering many batch keys that
// are not visible at the batch sequence number. Instead, the merging
// iterator enforces bounds.
//
// For example, consider an engine that contains the committed keys
// 'bar' and 'bax', with no keys between them. Consider a batch
// containing keys 1,000 keys within the range [a,z]. All of the
// batch keys were added to the batch after the iterator was
// constructed, so they are not visible to the iterator. A call to
// SeekGE('bax') would seek the LSM iterators and discover the key
// 'bax'. It would also seek the batch iterator, landing on the key
// 'baz' but discover it that it's not visible. The batch iterator would
// next through the rest of the batch's keys, only to discover there are
// no visible keys greater than or equal to 'bax'.
//
// Filtering these batch points within the merging iterator ensures that
// the batch iterator never needs to iterate beyond 'baz', because it
// already found a smaller, visible key 'bax'.
snapshot: base.InternalKeySeqNumMax,
}
}
func (b *Batch) newRangeDelIter(_ *IterOptions) keyspan.FragmentIterator {
if b.index == nil {
return newErrorKeyspanIter(ErrNotIndexed)
}
func (b *Batch) newRangeDelIter(o *IterOptions, batchSnapshot uint64) *keyspan.Iter {
// Construct an iterator even if rangeDelIndex is nil, because it is allowed
// to refresh later, so we need the container to exist.
iter := new(keyspan.Iter)
b.initRangeDelIter(o, iter, batchSnapshot)
return iter
}
func (b *Batch) initRangeDelIter(_ *IterOptions, iter *keyspan.Iter, batchSnapshot uint64) {
if b.rangeDelIndex == nil {
return nil
iter.Init(b.cmp, nil)
return
}
// Fragment the range tombstones the first time a range deletion iterator is
// requested. The cached tombstones are invalidated if another range deletion
// tombstone is added to the batch.
if b.tombstones == nil {
frag := &keyspan.Fragmenter{
Cmp: b.cmp,
Format: b.formatKey,
Emit: func(s keyspan.Span) {
b.tombstones = append(b.tombstones, s)
},
}
it := &batchIter{
cmp: b.cmp,
batch: b,
iter: b.rangeDelIndex.NewIter(nil, nil),
}
fragmentRangeDels(frag, it, int(b.countRangeDels))
// requested. The cached tombstones are invalidated if another range
// deletion tombstone is added to the batch. This cache is only guaranteed
// to be correct if we're opening an iterator to read at a batch sequence
// number at least as high as tombstonesSeqNum. The cache is guaranteed to
// include all tombstones up to tombstonesSeqNum, and if any additional
// tombstones were added after that sequence number the cache would've been
// cleared.
nextSeqNum := b.nextSeqNum()
if b.tombstones != nil && b.tombstonesSeqNum <= batchSnapshot {
iter.Init(b.cmp, b.tombstones)
return
}
return keyspan.NewIter(b.cmp, b.tombstones)
tombstones := make([]keyspan.Span, 0, b.countRangeDels)
frag := &keyspan.Fragmenter{
Cmp: b.cmp,
Format: b.formatKey,
Emit: func(s keyspan.Span) {
tombstones = append(tombstones, s)
},
}
it := &batchIter{
cmp: b.cmp,
batch: b,
iter: b.rangeDelIndex.NewIter(nil, nil),
snapshot: batchSnapshot,
}
fragmentRangeDels(frag, it, int(b.countRangeDels))
iter.Init(b.cmp, tombstones)
// If we just read all the tombstones in the batch (eg, batchSnapshot was
// set to b.nextSeqNum()), then cache the tombstones so that a subsequent
// call to initRangeDelIter may use them without refragmenting.
if nextSeqNum == batchSnapshot {
b.tombstones = tombstones
b.tombstonesSeqNum = nextSeqNum
}
}
func fragmentRangeDels(frag *keyspan.Fragmenter, it internalIterator, count int) {
@@ -861,7 +955,7 @@ func fragmentRangeDels(frag *keyspan.Fragmenter, it internalIterator, count int)
// individual []keyspan.Key slices with a single element each.
keyBuf := make([]keyspan.Key, 0, count)
for key, val := it.First(); key != nil; key, val = it.Next() {
s := rangedel.Decode(*key, val, keyBuf)
s := rangedel.Decode(*key, val.InPlaceValue(), keyBuf)
keyBuf = s.Keys[len(s.Keys):]
// Set a fixed capacity to avoid accidental overwriting.
@@ -871,34 +965,57 @@ func fragmentRangeDels(frag *keyspan.Fragmenter, it internalIterator, count int)
frag.Finish()
}
func (b *Batch) newRangeKeyIter(_ *IterOptions) keyspan.FragmentIterator {
if b.index == nil {
return newErrorKeyspanIter(ErrNotIndexed)
}
func (b *Batch) newRangeKeyIter(o *IterOptions, batchSnapshot uint64) *keyspan.Iter {
// Construct an iterator even if rangeKeyIndex is nil, because it is allowed
// to refresh later, so we need the container to exist.
iter := new(keyspan.Iter)
b.initRangeKeyIter(o, iter, batchSnapshot)
return iter
}
func (b *Batch) initRangeKeyIter(_ *IterOptions, iter *keyspan.Iter, batchSnapshot uint64) {
if b.rangeKeyIndex == nil {
return nil
iter.Init(b.cmp, nil)
return
}
// Fragment the range keys the first time a range key iterator is requested.
// The cached range keys are invalidated if another range key is added to
// the batch.
if b.rangeKeys == nil {
frag := &keyspan.Fragmenter{
Cmp: b.cmp,
Format: b.formatKey,
Emit: func(s keyspan.Span) {
b.rangeKeys = append(b.rangeKeys, s)
},
}
it := &batchIter{
cmp: b.cmp,
batch: b,
iter: b.rangeKeyIndex.NewIter(nil, nil),
}
fragmentRangeKeys(frag, it, int(b.countRangeKeys))
// The cached spans are invalidated if another range key is added to the
// batch. This cache is only guaranteed to be correct if we're opening an
// iterator to read at a batch sequence number at least as high as
// rangeKeysSeqNum. The cache is guaranteed to include all range keys up to
// rangeKeysSeqNum, and if any additional range keys were added after that
// sequence number the cache would've been cleared.
nextSeqNum := b.nextSeqNum()
if b.rangeKeys != nil && b.rangeKeysSeqNum <= batchSnapshot {
iter.Init(b.cmp, b.rangeKeys)
return
}
return keyspan.NewIter(b.cmp, b.rangeKeys)
rangeKeys := make([]keyspan.Span, 0, b.countRangeKeys)
frag := &keyspan.Fragmenter{
Cmp: b.cmp,
Format: b.formatKey,
Emit: func(s keyspan.Span) {
rangeKeys = append(rangeKeys, s)
},
}
it := &batchIter{
cmp: b.cmp,
batch: b,
iter: b.rangeKeyIndex.NewIter(nil, nil),
snapshot: batchSnapshot,
}
fragmentRangeKeys(frag, it, int(b.countRangeKeys))
iter.Init(b.cmp, rangeKeys)
// If we just read all the range keys in the batch (eg, batchSnapshot was
// set to b.nextSeqNum()), then cache the range keys so that a subsequent
// call to initRangeKeyIter may use them without refragmenting.
if nextSeqNum == batchSnapshot {
b.rangeKeys = rangeKeys
b.rangeKeysSeqNum = nextSeqNum
}
}
func fragmentRangeKeys(frag *keyspan.Fragmenter, it internalIterator, count int) error {
@@ -913,7 +1030,7 @@ func fragmentRangeKeys(frag *keyspan.Fragmenter, it internalIterator, count int)
// individual []keyspan.Key slices with a single element each.
keyBuf := make([]keyspan.Key, 0, count)
for ik, val := it.First(); ik != nil; ik, val = it.Next() {
s, err := rangekey.Decode(*ik, val, keyBuf)
s, err := rangekey.Decode(*ik, val.InPlaceValue(), keyBuf)
if err != nil {
return err
}
@@ -967,7 +1084,9 @@ func (b *Batch) Reset() {
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.commitErr = nil
@@ -1132,6 +1251,11 @@ type batchIter struct {
batch *Batch
iter batchskl.Iterator
err error
// snapshot holds a batch "sequence number" at which the batch is being
// read. This sequence number has the InternalKeySeqNumBatch bit set, so it
// encodes an offset within the batch. Only batch entries earlier than the
// offset are visible during iteration.
snapshot uint64
}
// batchIter implements the base.InternalIterator interface.
@@ -1141,72 +1265,89 @@ func (i *batchIter) String() string {
return "batch"
}
func (i *batchIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) {
// Ignore trySeekUsingNext since the batch may have changed, so using Next
// would be incorrect.
i.err = nil // clear cached iteration error
ikey := i.iter.SeekGE(key)
if ikey == nil {
return nil, nil
func (i *batchIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) {
// Ignore TrySeekUsingNext if the view of the batch changed.
if flags.TrySeekUsingNext() && flags.BatchJustRefreshed() {
flags = flags.DisableTrySeekUsingNext()
}
return ikey, i.Value()
i.err = nil // clear cached iteration error
ikey := i.iter.SeekGE(key, flags)
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Next()
}
if ikey == nil {
return nil, base.LazyValue{}
}
return ikey, base.MakeInPlaceValue(i.value())
}
func (i *batchIter) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
return i.SeekGE(key, trySeekUsingNext)
return i.SeekGE(key, flags)
}
func (i *batchIter) SeekLT(key []byte) (*InternalKey, []byte) {
func (i *batchIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
ikey := i.iter.SeekLT(key)
if ikey == nil {
return nil, nil
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Prev()
}
return ikey, i.Value()
if ikey == nil {
return nil, base.LazyValue{}
}
return ikey, base.MakeInPlaceValue(i.value())
}
func (i *batchIter) First() (*InternalKey, []byte) {
func (i *batchIter) First() (*InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
ikey := i.iter.First()
if ikey == nil {
return nil, nil
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Next()
}
return ikey, i.Value()
if ikey == nil {
return nil, base.LazyValue{}
}
return ikey, base.MakeInPlaceValue(i.value())
}
func (i *batchIter) Last() (*InternalKey, []byte) {
func (i *batchIter) Last() (*InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
ikey := i.iter.Last()
if ikey == nil {
return nil, nil
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Prev()
}
return ikey, i.Value()
if ikey == nil {
return nil, base.LazyValue{}
}
return ikey, base.MakeInPlaceValue(i.value())
}
func (i *batchIter) Next() (*InternalKey, []byte) {
func (i *batchIter) Next() (*InternalKey, base.LazyValue) {
ikey := i.iter.Next()
if ikey == nil {
return nil, nil
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Next()
}
return ikey, i.Value()
if ikey == nil {
return nil, base.LazyValue{}
}
return ikey, base.MakeInPlaceValue(i.value())
}
func (i *batchIter) Prev() (*InternalKey, []byte) {
func (i *batchIter) Prev() (*InternalKey, base.LazyValue) {
ikey := i.iter.Prev()
if ikey == nil {
return nil, nil
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Prev()
}
return ikey, i.Value()
if ikey == nil {
return nil, base.LazyValue{}
}
return ikey, base.MakeInPlaceValue(i.value())
}
func (i *batchIter) Key() *InternalKey {
return i.iter.Key()
}
func (i *batchIter) Value() []byte {
func (i *batchIter) value() []byte {
offset, _, keyEnd := i.iter.KeyInfo()
data := i.batch.data
if len(data[offset:]) == 0 {
@@ -1227,10 +1368,6 @@ func (i *batchIter) Value() []byte {
}
}
func (i *batchIter) Valid() bool {
return i.iter.Valid()
}
func (i *batchIter) Error() error {
return i.err
}
@@ -1474,6 +1611,8 @@ func (b *flushableBatch) newRangeKeyIter(o *IterOptions) keyspan.FragmentIterato
return keyspan.NewIter(b.cmp, b.rangeKeys)
}
func (b *flushableBatch) containsRangeKeys() bool { return len(b.rangeKeys) > 0 }
func (b *flushableBatch) inuseBytes() uint64 {
return uint64(len(b.data) - batchHeaderLen)
}
@@ -1517,36 +1656,40 @@ func (i *flushableBatchIter) String() string {
}
// SeekGE implements internalIterator.SeekGE, as documented in the pebble
// package. Ignore trySeekUsingNext since we don't expect this optimization
// to provide much benefit here at the moment.
func (i *flushableBatchIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) {
// package. Ignore flags.TrySeekUsingNext() since we don't expect this
// optimization to provide much benefit here at the moment.
func (i *flushableBatchIter) SeekGE(
key []byte, flags base.SeekGEFlags,
) (*InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
ikey := base.MakeSearchKey(key)
i.index = sort.Search(len(i.offsets), func(j int) bool {
return base.InternalCompare(i.cmp, ikey, i.getKey(j)) <= 0
})
if i.index >= len(i.offsets) {
return nil, nil
return nil, base.LazyValue{}
}
i.key = i.getKey(i.index)
if i.upper != nil && i.cmp(i.key.UserKey, i.upper) >= 0 {
i.index = len(i.offsets)
return nil, nil
return nil, base.LazyValue{}
}
return &i.key, i.Value()
return &i.key, i.value()
}
// SeekPrefixGE implements internalIterator.SeekPrefixGE, as documented in the
// pebble package.
func (i *flushableBatchIter) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
return i.SeekGE(key, trySeekUsingNext)
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
return i.SeekGE(key, flags)
}
// SeekLT implements internalIterator.SeekLT, as documented in the pebble
// package.
func (i *flushableBatchIter) SeekLT(key []byte) (*InternalKey, []byte) {
func (i *flushableBatchIter) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
ikey := base.MakeSearchKey(key)
i.index = sort.Search(len(i.offsets), func(j int) bool {
@@ -1554,80 +1697,80 @@ func (i *flushableBatchIter) SeekLT(key []byte) (*InternalKey, []byte) {
})
i.index--
if i.index < 0 {
return nil, nil
return nil, base.LazyValue{}
}
i.key = i.getKey(i.index)
if i.lower != nil && i.cmp(i.key.UserKey, i.lower) < 0 {
i.index = -1
return nil, nil
return nil, base.LazyValue{}
}
return &i.key, i.Value()
return &i.key, i.value()
}
// First implements internalIterator.First, as documented in the pebble
// package.
func (i *flushableBatchIter) First() (*InternalKey, []byte) {
func (i *flushableBatchIter) First() (*InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
if len(i.offsets) == 0 {
return nil, nil
return nil, base.LazyValue{}
}
i.index = 0
i.key = i.getKey(i.index)
if i.upper != nil && i.cmp(i.key.UserKey, i.upper) >= 0 {
i.index = len(i.offsets)
return nil, nil
return nil, base.LazyValue{}
}
return &i.key, i.Value()
return &i.key, i.value()
}
// Last implements internalIterator.Last, as documented in the pebble
// package.
func (i *flushableBatchIter) Last() (*InternalKey, []byte) {
func (i *flushableBatchIter) Last() (*InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
if len(i.offsets) == 0 {
return nil, nil
return nil, base.LazyValue{}
}
i.index = len(i.offsets) - 1
i.key = i.getKey(i.index)
if i.lower != nil && i.cmp(i.key.UserKey, i.lower) < 0 {
i.index = -1
return nil, nil
return nil, base.LazyValue{}
}
return &i.key, i.Value()
return &i.key, i.value()
}
// Note: flushFlushableBatchIter.Next mirrors the implementation of
// flushableBatchIter.Next due to performance. Keep the two in sync.
func (i *flushableBatchIter) Next() (*InternalKey, []byte) {
func (i *flushableBatchIter) Next() (*InternalKey, base.LazyValue) {
if i.index == len(i.offsets) {
return nil, nil
return nil, base.LazyValue{}
}
i.index++
if i.index == len(i.offsets) {
return nil, nil
return nil, base.LazyValue{}
}
i.key = i.getKey(i.index)
if i.upper != nil && i.cmp(i.key.UserKey, i.upper) >= 0 {
i.index = len(i.offsets)
return nil, nil
return nil, base.LazyValue{}
}
return &i.key, i.Value()
return &i.key, i.value()
}
func (i *flushableBatchIter) Prev() (*InternalKey, []byte) {
func (i *flushableBatchIter) Prev() (*InternalKey, base.LazyValue) {
if i.index < 0 {
return nil, nil
return nil, base.LazyValue{}
}
i.index--
if i.index < 0 {
return nil, nil
return nil, base.LazyValue{}
}
i.key = i.getKey(i.index)
if i.lower != nil && i.cmp(i.key.UserKey, i.lower) < 0 {
i.index = -1
return nil, nil
return nil, base.LazyValue{}
}
return &i.key, i.Value()
return &i.key, i.value()
}
func (i *flushableBatchIter) getKey(index int) InternalKey {
@@ -1637,20 +1780,16 @@ func (i *flushableBatchIter) getKey(index int) InternalKey {
return base.MakeInternalKey(key, i.batch.seqNum+uint64(e.index), kind)
}
func (i *flushableBatchIter) Key() *InternalKey {
return &i.key
}
func (i *flushableBatchIter) Value() []byte {
func (i *flushableBatchIter) value() base.LazyValue {
p := i.data[i.offsets[i.index].offset:]
if len(p) == 0 {
i.err = base.CorruptionErrorf("corrupted batch")
return nil
return base.LazyValue{}
}
kind := InternalKeyKind(p[0])
if kind > InternalKeyKindMax {
i.err = base.CorruptionErrorf("corrupted batch")
return nil
return base.LazyValue{}
}
var value []byte
var ok bool
@@ -1661,10 +1800,10 @@ func (i *flushableBatchIter) Value() []byte {
_, value, ok = batchDecodeStr(i.data[keyEnd:])
if !ok {
i.err = base.CorruptionErrorf("corrupted batch")
return nil
return base.LazyValue{}
}
}
return value
return base.MakeInPlaceValue(value)
}
func (i *flushableBatchIter) Valid() bool {
@@ -1698,25 +1837,29 @@ func (i *flushFlushableBatchIter) String() string {
return "flushable-batch"
}
func (i *flushFlushableBatchIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) {
func (i *flushFlushableBatchIter) SeekGE(
key []byte, flags base.SeekGEFlags,
) (*InternalKey, base.LazyValue) {
panic("pebble: SeekGE unimplemented")
}
func (i *flushFlushableBatchIter) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
panic("pebble: SeekPrefixGE unimplemented")
}
func (i *flushFlushableBatchIter) SeekLT(key []byte) (*InternalKey, []byte) {
func (i *flushFlushableBatchIter) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*InternalKey, base.LazyValue) {
panic("pebble: SeekLT unimplemented")
}
func (i *flushFlushableBatchIter) First() (*InternalKey, []byte) {
func (i *flushFlushableBatchIter) First() (*InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
key, val := i.flushableBatchIter.First()
if key == nil {
return nil, nil
return nil, base.LazyValue{}
}
entryBytes := i.offsets[i.index].keyEnd - i.offsets[i.index].offset
*i.bytesIterated += uint64(entryBytes) + i.valueSize()
@@ -1725,21 +1868,21 @@ func (i *flushFlushableBatchIter) First() (*InternalKey, []byte) {
// Note: flushFlushableBatchIter.Next mirrors the implementation of
// flushableBatchIter.Next due to performance. Keep the two in sync.
func (i *flushFlushableBatchIter) Next() (*InternalKey, []byte) {
func (i *flushFlushableBatchIter) Next() (*InternalKey, base.LazyValue) {
if i.index == len(i.offsets) {
return nil, nil
return nil, base.LazyValue{}
}
i.index++
if i.index == len(i.offsets) {
return nil, nil
return nil, base.LazyValue{}
}
i.key = i.getKey(i.index)
entryBytes := i.offsets[i.index].keyEnd - i.offsets[i.index].offset
*i.bytesIterated += uint64(entryBytes) + i.valueSize()
return &i.key, i.Value()
return &i.key, i.value()
}
func (i flushFlushableBatchIter) Prev() (*InternalKey, []byte) {
func (i flushFlushableBatchIter) Prev() (*InternalKey, base.LazyValue) {
panic("pebble: Prev unimplemented")
}
@@ -1780,7 +1923,10 @@ func batchSort(
) {
b := i.(*Batch)
if b.Indexed() {
return b.newInternalIter(nil), b.newRangeDelIter(nil), b.newRangeKeyIter(nil)
pointIter := b.newInternalIter(nil)
rangeDelIter := b.newRangeDelIter(nil, math.MaxUint64)
rangeKeyIter := b.newRangeKeyIter(nil, math.MaxUint64)
return pointIter, rangeDelIter, rangeKeyIter
}
f := newFlushableBatch(b, b.db.opts.Comparer)
return f.newIter(nil), f.newRangeDelIter(nil), f.newRangeKeyIter(nil)

View File

@@ -157,7 +157,8 @@ func (d *DB) Checkpoint(
fs := syncingFS{
FS: d.opts.FS,
syncOpts: vfs.SyncingFileOptions{
BytesPerSync: d.opts.BytesPerSync,
NoSyncOnClose: d.opts.NoSyncOnClose,
BytesPerSync: d.opts.BytesPerSync,
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,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"
)
// compactionIter provides a forward-only iterator that encapsulates the logic
@@ -156,7 +158,11 @@ type compactionIter struct {
// Additionally, it is the internal state when the code is moving to the
// next key so it can determine whether the user key has changed from
// the previous key.
key InternalKey
key InternalKey
// keyTrailer is updated when `i.key` is updated and holds the key's
// original trailer (eg, before any sequence-number zeroing or changes to
// key kind).
keyTrailer uint64
value []byte
valueCloser io.Closer
// Temporary buffer used for storing the previous user key in order to
@@ -198,8 +204,11 @@ type compactionIter struct {
// Reference to the range deletion tombstone fragmenter (e.g.,
// `compaction.rangeDelFrag`).
rangeDelFrag *keyspan.Fragmenter
rangeKeyFrag *keyspan.Fragmenter
// The fragmented tombstones.
tombstones []keyspan.Span
// The fragmented range keys.
rangeKeys []keyspan.Span
// Byte allocator for the tombstone keys.
alloc bytealloc.A
allowZeroSeqNum bool
@@ -218,6 +227,7 @@ func newCompactionIter(
iter internalIterator,
snapshots []uint64,
rangeDelFrag *keyspan.Fragmenter,
rangeKeyFrag *keyspan.Fragmenter,
allowZeroSeqNum bool,
elideTombstone func(key []byte) bool,
elideRangeTombstone func(start, end []byte) bool,
@@ -229,6 +239,7 @@ func newCompactionIter(
iter: iter,
snapshots: snapshots,
rangeDelFrag: rangeDelFrag,
rangeKeyFrag: rangeKeyFrag,
allowZeroSeqNum: allowZeroSeqNum,
elideTombstone: elideTombstone,
elideRangeTombstone: elideRangeTombstone,
@@ -237,6 +248,9 @@ func newCompactionIter(
i.rangeDelFrag.Cmp = cmp
i.rangeDelFrag.Format = formatKey
i.rangeDelFrag.Emit = i.emitRangeDelChunk
i.rangeKeyFrag.Cmp = cmp
i.rangeKeyFrag.Format = formatKey
i.rangeKeyFrag.Emit = i.emitRangeKeyChunk
return i
}
@@ -244,7 +258,12 @@ func (i *compactionIter) First() (*InternalKey, []byte) {
if i.err != nil {
return nil, nil
}
i.iterKey, i.iterValue = i.iter.First()
var iterValue LazyValue
i.iterKey, iterValue = i.iter.First()
i.iterValue, _, i.err = iterValue.Value(nil)
if i.err != nil {
return nil, nil
}
if i.iterKey != nil {
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(i.iterKey.SeqNum(), i.snapshots)
}
@@ -282,13 +301,13 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
i.pos = iterPosCurForward
i.valid = false
for i.iterKey != nil {
if i.iterKey.Kind() == InternalKeyKindRangeDelete {
// Return the range tombstone so the compaction can use it for
// file truncation and add it to the fragmenter. We do not set `skip`
// to true before returning as there may be a forthcoming point key
// with the same user key and sequence number. Such a point key must be
// visible (i.e., not skipped over) since we promise point keys are
// not deleted by range tombstones at the same sequence number.
if i.iterKey.Kind() == InternalKeyKindRangeDelete || rangekey.IsRangeKey(i.iterKey.Kind()) {
// Return the span so the compaction can use it for file truncation and add
// it to the relevant fragmenter. We do not set `skip` to true before
// returning as there may be a forthcoming point key with the same user key
// and sequence number. Such a point key must be visible (i.e., not skipped
// over) since we promise point keys are not deleted by range tombstones at
// the same sequence number.
//
// Although, note that `skip` may already be true before reaching here
// due to an earlier key in the stripe. Then it is fine to leave it set
@@ -456,10 +475,11 @@ func (i *compactionIter) skipInStripe() {
}
func (i *compactionIter) iterNext() bool {
i.iterKey, i.iterValue = i.iter.Next()
// We should never see an exclusive sentinel in the compaction input.
if i.iterKey != nil && i.iterKey.IsExclusiveSentinel() {
panic(fmt.Sprintf("pebble: unexpected exclusive sentinel in compaction input, trailer = %x", i.iterKey.Trailer))
var iterValue LazyValue
i.iterKey, iterValue = i.iter.Next()
i.iterValue, _, i.err = iterValue.Value(nil)
if i.err != nil {
i.iterKey = nil
}
return i.iterKey != nil
}
@@ -487,7 +507,26 @@ func (i *compactionIter) nextInStripe() stripeChangeType {
return newStripe
}
key := i.iterKey
if !i.equal(i.key.UserKey, key.UserKey) {
// 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) {
prevKey := i.key
prevKey.Trailer = i.keyTrailer
panic(fmt.Sprintf("pebble: invariant violation: %s and %s out of order", key, prevKey))
}
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
return newStripe
} else if !i.equal(i.key.UserKey, key.UserKey) {
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
return newStripe
}
@@ -502,6 +541,10 @@ func (i *compactionIter) nextInStripe() stripeChangeType {
return sameStripeNonSkippable
}
return newStripe
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
@@ -726,6 +769,7 @@ func (i *compactionIter) saveKey() {
i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
i.key.UserKey = i.keyBuf
i.key.Trailer = i.iterKey.Trailer
i.keyTrailer = i.iterKey.Trailer
}
func (i *compactionIter) cloneKey(key []byte) []byte {
@@ -766,9 +810,6 @@ func (i *compactionIter) Close() error {
// Tombstones returns a list of pending range tombstones in the fragmenter
// up to the specified key, or all pending range tombstones if key = nil.
// exclude specifies if the specified key is exclusive or inclusive.
// When exclude = true, all returned range tombstones are truncated to the
// specified key.
func (i *compactionIter) Tombstones(key []byte) []keyspan.Span {
if key == nil {
i.rangeDelFrag.Finish()
@@ -783,6 +824,22 @@ func (i *compactionIter) Tombstones(key []byte) []keyspan.Span {
return tombstones
}
// RangeKeys returns a list of pending fragmented range keys up to the specified
// key, or all pending range keys if key = nil.
func (i *compactionIter) RangeKeys(key []byte) []keyspan.Span {
if key == nil {
i.rangeKeyFrag.Finish()
} else {
// The specified end key is exclusive; no versions of the specified
// user key (including range tombstones covering that key) should
// be flushed yet.
i.rangeKeyFrag.TruncateAndFlushTo(key)
}
rangeKeys := i.rangeKeys
i.rangeKeys = nil
return rangeKeys
}
func (i *compactionIter) emitRangeDelChunk(fragmented keyspan.Span) {
// Apply the snapshot stripe rules, keeping only the latest tombstone for
// each snapshot stripe.
@@ -815,6 +872,14 @@ func (i *compactionIter) emitRangeDelChunk(fragmented keyspan.Span) {
}
}
func (i *compactionIter) emitRangeKeyChunk(fragmented keyspan.Span) {
// Elision of snapshot stripes happens in rangeKeyCompactionTransform, so no need to
// do that here.
if len(fragmented.Keys) > 0 {
i.rangeKeys = append(i.rangeKeys, fragmented)
}
}
// maybeZeroSeqnum attempts to set the seqnum for the current key to 0. Doing
// so improves compression and enables an optimization during forward iteration
// to skip some key comparisons. The seqnum for an entry can be zeroed if the

View File

@@ -19,10 +19,7 @@ import (
// heuristic.
const minIntraL0Count = 4
const levelMultiplier = 10
type compactionEnv struct {
bytesCompacted *uint64
earliestUnflushedSeqNum uint64
earliestSnapshotSeqNum uint64
inProgressCompactions []compactionInfo
@@ -146,10 +143,18 @@ type pickedCompaction struct {
// startLevel is the level that is being compacted. Inputs from startLevel
// and outputLevel will be merged to produce a set of outputLevel files.
startLevel *compactionLevel
// outputLevel is the level that files are being produced in. In default
// compactions, outputLevel is equal to startLevel+1 except when startLevel
// is 0 in which case it is equal to compactionPicker.baseLevel().
// outputLevel is the level that files are being produced in. outputLevel is
// equal to startLevel+1 except when:
// - if startLevel is 0, the output level equals compactionPicker.baseLevel().
// - in multilevel compaction, the output level is the lowest level involved in
// the compaction
outputLevel *compactionLevel
// 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.
@@ -161,7 +166,7 @@ type pickedCompaction struct {
// where startLevel == 0 that were generated by L0Sublevels.
lcf *manifest.L0CompactionFiles
// L0 sublevel info is used for compactions out of L0. It is nil for all
// L0SublevelInfo is used for compactions out of L0. It is nil for all
// other compactions.
l0SublevelInfo []sublevelInfo
@@ -252,37 +257,71 @@ func newPickedCompactionFromL0(
return pc
}
func (pc *pickedCompaction) setupInputs(opts *Options, diskAvailBytes uint64) bool {
// maybeExpandedBounds is a helper function for setupInputs which ensures the
// pickedCompaction's smallest and largest internal keys are updated iff
// the candidate keys expand the key span. This avoids a bug for multi-level
// compactions: during the second call to setupInputs, the picked compaction's
// smallest and largest keys should not decrease the key span.
func (pc *pickedCompaction) maybeExpandBounds(smallest InternalKey, largest InternalKey) {
emptyKey := InternalKey{}
if base.InternalCompare(pc.cmp, smallest, emptyKey) == 0 {
if base.InternalCompare(pc.cmp, largest, emptyKey) != 0 {
panic("either both candidate keys are empty or neither are empty")
}
return
}
if base.InternalCompare(pc.cmp, pc.smallest, emptyKey) == 0 {
if base.InternalCompare(pc.cmp, pc.largest, emptyKey) != 0 {
panic("either both pc keys are empty or neither are empty")
}
pc.smallest = smallest
pc.largest = largest
return
}
if base.InternalCompare(pc.cmp, pc.smallest, smallest) >= 0 {
pc.smallest = smallest
}
if base.InternalCompare(pc.cmp, pc.largest, largest) <= 0 {
pc.largest = largest
}
}
func (pc *pickedCompaction) setupInputs(
opts *Options, diskAvailBytes uint64, startLevel *compactionLevel,
) bool {
// maxExpandedBytes is the maximum size of an expanded compaction. If
// growing a compaction results in a larger size, the original compaction
// is used instead.
maxExpandedBytes := expandedCompactionByteSizeLimit(opts, pc.adjustedOutputLevel, diskAvailBytes)
maxExpandedBytes := expandedCompactionByteSizeLimit(
opts, pc.adjustedOutputLevel, diskAvailBytes,
)
// Expand the initial inputs to a clean cut.
var isCompacting bool
pc.startLevel.files, isCompacting = expandToAtomicUnit(pc.cmp, pc.startLevel.files, false /* disableIsCompacting */)
startLevel.files, isCompacting = expandToAtomicUnit(pc.cmp, startLevel.files, false /* disableIsCompacting */)
if isCompacting {
return false
}
pc.smallest, pc.largest = manifest.KeyRange(pc.cmp, pc.startLevel.files.Iter())
pc.maybeExpandBounds(manifest.KeyRange(pc.cmp, startLevel.files.Iter()))
// Determine the sstables in the output level which overlap with the input
// sstables, and then expand those tables to a clean cut. No need to do
// this for intra-L0 compactions; outputLevel.files is left empty for those.
if pc.startLevel.level != pc.outputLevel.level {
if startLevel.level != pc.outputLevel.level {
pc.outputLevel.files = pc.version.Overlaps(pc.outputLevel.level, pc.cmp, pc.smallest.UserKey,
pc.largest.UserKey, pc.largest.IsExclusiveSentinel())
pc.outputLevel.files, isCompacting = expandToAtomicUnit(pc.cmp, pc.outputLevel.files, false /* disableIsCompacting */)
pc.outputLevel.files, isCompacting = expandToAtomicUnit(pc.cmp, pc.outputLevel.files,
false /* disableIsCompacting */)
if isCompacting {
return false
}
pc.smallest, pc.largest = manifest.KeyRange(pc.cmp,
pc.startLevel.files.Iter(), pc.outputLevel.files.Iter())
pc.maybeExpandBounds(manifest.KeyRange(pc.cmp,
startLevel.files.Iter(), pc.outputLevel.files.Iter()))
}
// Grow the sstables in pc.startLevel.level as long as it doesn't affect the number
// Grow the sstables in startLevel.level as long as it doesn't affect the number
// of sstables included from pc.outputLevel.level.
if pc.lcf != nil && pc.startLevel.level == 0 && pc.outputLevel.level != 0 {
if pc.lcf != nil && startLevel.level == 0 && pc.outputLevel.level != 0 {
// Call the L0-specific compaction extension method. Similar logic as
// pc.grow. Additional L0 files are optionally added to the compaction at
// this step. Note that the bounds passed in are not the bounds of the
@@ -336,16 +375,16 @@ func (pc *pickedCompaction) setupInputs(opts *Options, diskAvailBytes uint64) bo
}
}
if sizeSum+pc.outputLevel.files.SizeSum() < maxExpandedBytes {
pc.startLevel.files = manifest.NewLevelSliceSeqSorted(newStartLevelFiles)
startLevel.files = manifest.NewLevelSliceSeqSorted(newStartLevelFiles)
pc.smallest, pc.largest = manifest.KeyRange(pc.cmp,
pc.startLevel.files.Iter(), pc.outputLevel.files.Iter())
startLevel.files.Iter(), pc.outputLevel.files.Iter())
} else {
*pc.lcf = oldLcf
}
}
} else if pc.grow(pc.smallest, pc.largest, maxExpandedBytes) {
pc.smallest, pc.largest = manifest.KeyRange(pc.cmp,
pc.startLevel.files.Iter(), pc.outputLevel.files.Iter())
} else if pc.grow(pc.smallest, pc.largest, maxExpandedBytes, startLevel) {
pc.maybeExpandBounds(manifest.KeyRange(pc.cmp,
startLevel.files.Iter(), pc.outputLevel.files.Iter()))
}
if pc.startLevel.level == 0 {
@@ -359,23 +398,27 @@ func (pc *pickedCompaction) setupInputs(opts *Options, diskAvailBytes uint64) bo
// grow grows the number of inputs at c.level without changing the number of
// c.level+1 files in the compaction, and returns whether the inputs grew. sm
// and la are the smallest and largest InternalKeys in all of the inputs.
func (pc *pickedCompaction) grow(sm, la InternalKey, maxExpandedBytes uint64) bool {
func (pc *pickedCompaction) grow(
sm, la InternalKey, maxExpandedBytes uint64, startLevel *compactionLevel,
) bool {
if pc.outputLevel.files.Empty() {
return false
}
grow0 := pc.version.Overlaps(pc.startLevel.level, pc.cmp, sm.UserKey,
grow0 := pc.version.Overlaps(startLevel.level, pc.cmp, sm.UserKey,
la.UserKey, la.IsExclusiveSentinel())
grow0, isCompacting := expandToAtomicUnit(pc.cmp, grow0, false /* disableIsCompacting */)
if isCompacting {
return false
}
if grow0.Len() <= pc.startLevel.files.Len() {
if grow0.Len() <= startLevel.files.Len() {
return false
}
if grow0.SizeSum()+pc.outputLevel.files.SizeSum() >= maxExpandedBytes {
return false
}
sm1, la1 := manifest.KeyRange(pc.cmp, grow0.Iter())
// We need to include the outputLevel iter because without it, in a multiLevel scenario,
// sm1 and la1 could shift the output level keyspace when pc.outputLevel.files is set to grow1.
sm1, la1 := manifest.KeyRange(pc.cmp, grow0.Iter(), pc.outputLevel.files.Iter())
grow1 := pc.version.Overlaps(pc.outputLevel.level, pc.cmp, sm1.UserKey,
la1.UserKey, la1.IsExclusiveSentinel())
grow1, isCompacting = expandToAtomicUnit(pc.cmp, grow1, false /* disableIsCompacting */)
@@ -385,11 +428,19 @@ func (pc *pickedCompaction) grow(sm, la InternalKey, maxExpandedBytes uint64) bo
if grow1.Len() != pc.outputLevel.files.Len() {
return false
}
pc.startLevel.files = grow0
startLevel.files = grow0
pc.outputLevel.files = grow1
return true
}
// initMultiLevelCompaction returns true if it initiated a multilevel input
// compaction. This currently never inits a multiLevel compaction.
func (pc *pickedCompaction) initMultiLevelCompaction(
opts *Options, vers *version, levelMaxBytes [7]int64, diskAvailBytes uint64,
) bool {
return false
}
// expandToAtomicUnit expands the provided level slice within its level both
// forwards and backwards to its "atomic compaction unit" boundaries, if
// necessary.
@@ -453,7 +504,7 @@ func expandToAtomicUnit(
iter := start.Clone()
iter.Prev()
for cur, prev := start.Current(), iter.Current(); prev != nil; cur, prev = start.Prev(), iter.Prev() {
if cur.Compacting {
if cur.IsCompacting() {
isCompacting = true
}
if cmp(prev.Largest.UserKey, cur.Smallest.UserKey) < 0 {
@@ -471,7 +522,7 @@ func expandToAtomicUnit(
iter = end.Clone()
iter.Next()
for cur, next := end.Current(), iter.Current(); next != nil; cur, next = end.Next(), iter.Next() {
if cur.Compacting {
if cur.IsCompacting() {
isCompacting = true
}
if cmp(cur.Largest.UserKey, next.Smallest.UserKey) < 0 {
@@ -487,7 +538,8 @@ func expandToAtomicUnit(
}
})
inputIter := inputs.Iter()
isCompacting = !disableIsCompacting && (isCompacting || inputIter.First().Compacting || inputIter.Last().Compacting)
isCompacting = !disableIsCompacting &&
(isCompacting || inputIter.First().IsCompacting() || inputIter.Last().IsCompacting())
return inputs, isCompacting
}
@@ -525,11 +577,11 @@ type candidateLevelInfo struct {
// compensatedSize returns f's file size, inflated according to compaction
// priorities.
func compensatedSize(f *fileMetadata) uint64 {
func compensatedSize(f *fileMetadata, pointTombstoneWeight float64) uint64 {
sz := f.Size
// Add in the estimate of disk space that may be reclaimed by compacting
// the file's tombstones.
sz += f.Stats.PointDeletionsBytesEstimate
sz += uint64(float64(f.Stats.PointDeletionsBytesEstimate) * pointTombstoneWeight)
sz += f.Stats.RangeDeletionsBytesEstimate
return sz
}
@@ -539,7 +591,9 @@ func compensatedSize(f *fileMetadata) uint64 {
// a *uint64. Compensated sizes may change once a table's stats are loaded
// asynchronously, so its values are marked as cacheable only if a file's
// stats have been loaded.
type compensatedSizeAnnotator struct{}
type compensatedSizeAnnotator struct {
pointTombstoneWeight float64
}
var _ manifest.Annotator = compensatedSizeAnnotator{}
@@ -556,8 +610,8 @@ func (a compensatedSizeAnnotator) Accumulate(
f *fileMetadata, dst interface{},
) (v interface{}, cacheOK bool) {
vptr := dst.(*uint64)
*vptr = *vptr + compensatedSize(f)
return vptr, f.Stats.Valid
*vptr = *vptr + compensatedSize(f, a.pointTombstoneWeight)
return vptr, f.StatsValidLocked()
}
func (a compensatedSizeAnnotator) Merge(src interface{}, dst interface{}) interface{} {
@@ -571,10 +625,10 @@ func (a compensatedSizeAnnotator) Merge(src interface{}, dst interface{}) interf
// iterator. Note that this function is linear in the files available to the
// iterator. Use the compensatedSizeAnnotator if querying the total
// compensated size of a level.
func totalCompensatedSize(iter manifest.LevelIterator) uint64 {
func totalCompensatedSize(iter manifest.LevelIterator, pointTombstoneWeight float64) uint64 {
var sz uint64
for f := iter.First(); f != nil; f = iter.Next() {
sz += compensatedSize(f)
sz += compensatedSize(f, pointTombstoneWeight)
}
return sz
}
@@ -732,11 +786,11 @@ func (p *compactionPickerByScore) initLevelMaxBytes(inProgressCompactions []comp
}
dbSize += p.levelSizes[0]
bottomLevelSize := dbSize - dbSize/levelMultiplier
bottomLevelSize := dbSize - dbSize/int64(p.opts.Experimental.LevelMultiplier)
curLevelSize := bottomLevelSize
for level := numLevels - 2; level >= firstNonEmptyLevel; level-- {
curLevelSize = int64(float64(curLevelSize) / levelMultiplier)
curLevelSize = int64(float64(curLevelSize) / float64(p.opts.Experimental.LevelMultiplier))
}
// Compute base level (where L0 data is compacted to).
@@ -744,7 +798,7 @@ func (p *compactionPickerByScore) initLevelMaxBytes(inProgressCompactions []comp
p.baseLevel = firstNonEmptyLevel
for p.baseLevel > 1 && curLevelSize > baseBytesMax {
p.baseLevel--
curLevelSize = int64(float64(curLevelSize) / levelMultiplier)
curLevelSize = int64(float64(curLevelSize) / float64(p.opts.Experimental.LevelMultiplier))
}
smoothedLevelMultiplier := 1.0
@@ -772,7 +826,9 @@ func (p *compactionPickerByScore) initLevelMaxBytes(inProgressCompactions []comp
}
}
func calculateSizeAdjust(inProgressCompactions []compactionInfo) [numLevels]int64 {
func calculateSizeAdjust(
inProgressCompactions []compactionInfo, pointTombstoneWeight float64,
) [numLevels]int64 {
// Compute a size adjustment for each level based on the in-progress
// compactions. We subtract the compensated size of start level inputs.
// Since compensated file sizes may be compensated because they reclaim
@@ -785,7 +841,7 @@ func calculateSizeAdjust(inProgressCompactions []compactionInfo) [numLevels]int6
for _, input := range c.inputs {
real := int64(input.files.SizeSum())
compensated := int64(totalCompensatedSize(input.files.Iter()))
compensated := int64(totalCompensatedSize(input.files.Iter(), pointTombstoneWeight))
if input.level != c.outputLevel {
sizeAdjust[input.level] -= compensated
@@ -812,7 +868,10 @@ func (p *compactionPickerByScore) calculateScores(
}
scores[0] = p.calculateL0Score(inProgressCompactions)
sizeAdjust := calculateSizeAdjust(inProgressCompactions)
sizeAdjust := calculateSizeAdjust(
inProgressCompactions,
p.opts.Experimental.PointTombstoneWeight,
)
for level := 1; level < numLevels; level++ {
levelSize := int64(levelCompensatedSize(p.vers.Levels[level])) + sizeAdjust[level]
scores[level].score = float64(levelSize) / float64(p.levelMaxBytes[level])
@@ -936,10 +995,10 @@ func (p *compactionPickerByScore) pickFile(
outputFile = outputIter.Next()
}
compacting := f.Compacting
compacting := f.IsCompacting()
for outputFile != nil && base.InternalCompare(cmp, outputFile.Smallest, f.Largest) < 0 {
overlappingBytes += outputFile.Size
compacting = compacting || outputFile.Compacting
compacting = compacting || outputFile.IsCompacting()
// For files in the bottommost level of the LSM, the
// Stats.RangeDeletionsBytesEstimate field is set to the estimate
@@ -969,8 +1028,9 @@ func (p *compactionPickerByScore) pickFile(
continue
}
scaledRatio := overlappingBytes * 1024 / compensatedSize(f)
if scaledRatio < smallestRatio && !f.Compacting {
compSz := compensatedSize(f, p.opts.Experimental.PointTombstoneWeight)
scaledRatio := overlappingBytes * 1024 / compSz
if scaledRatio < smallestRatio && !f.IsCompacting() {
smallestRatio = scaledRatio
file = startIter.Take()
}
@@ -1031,7 +1091,9 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
}
fmt.Fprintf(&buf, " %sL%d: %5.1f %5.1f %8s %8s",
marker, info.level, info.score, info.origScore,
humanize.Int64(int64(totalCompensatedSize(p.vers.Levels[info.level].Iter()))),
humanize.Int64(int64(totalCompensatedSize(
p.vers.Levels[info.level].Iter(), p.opts.Experimental.PointTombstoneWeight,
))),
humanize.Int64(p.levelMaxBytes[info.level]),
)
@@ -1092,7 +1154,7 @@ func (p *compactionPickerByScore) pickAuto(env compactionEnv) (pc *pickedCompact
continue
}
pc := pickAutoLPositive(env, p.opts, p.vers, *info, p.baseLevel, p.diskAvailBytes)
pc := pickAutoLPositive(env, p.opts, p.vers, *info, p.baseLevel, p.diskAvailBytes, p.levelMaxBytes)
// Fail-safe to protect against compacting the same sstable concurrently.
if pc != nil && !inputRangeAlreadyCompacting(env, pc) {
pc.score = info.score
@@ -1172,10 +1234,10 @@ func (a elisionOnlyAnnotator) Zero(interface{}) interface{} {
}
func (a elisionOnlyAnnotator) Accumulate(f *fileMetadata, dst interface{}) (interface{}, bool) {
if f.Compacting {
if f.IsCompacting() {
return dst, true
}
if !f.Stats.Valid {
if !f.StatsValidLocked() {
return dst, false
}
// Bottommost files are large and not worthwhile to compact just
@@ -1187,8 +1249,13 @@ func (a elisionOnlyAnnotator) Accumulate(f *fileMetadata, dst interface{}) (inte
// which may be collapsed. Ideally, we would have 'obsolete keys'
// statistics that would include tombstones, the keys that are
// dropped by tombstones and duplicated user keys. See #847.
//
// Note that tables that contain exclusively range keys (i.e. no point keys,
// `NumEntries` and `RangeDeletionsBytesEstimate` are both zero) are excluded
// from elision-only compactions.
// TODO(travers): Consider an alternative heuristic for elision of range-keys.
if f.Stats.RangeDeletionsBytesEstimate*10 < f.Size &&
f.Stats.NumDeletions*10 < f.Stats.NumEntries {
f.Stats.NumDeletions*10 <= f.Stats.NumEntries {
return dst, true
}
if dst == nil {
@@ -1266,7 +1333,7 @@ func (p *compactionPickerByScore) pickElisionOnlyCompaction(
return nil
}
candidate := v.(*fileMetadata)
if candidate.Compacting || candidate.LargestSeqNum >= env.earliestSnapshotSeqNum {
if candidate.IsCompacting() || candidate.LargestSeqNum >= env.earliestSnapshotSeqNum {
return nil
}
lf := p.vers.Levels[numLevels-1].Find(p.opts.Comparer.Compare, candidate)
@@ -1304,7 +1371,7 @@ func (p *compactionPickerByScore) pickRewriteCompaction(env compactionEnv) (pc *
continue
}
candidate := v.(*fileMetadata)
if candidate.Compacting {
if candidate.IsCompacting() {
// Try the next level.
continue
}
@@ -1364,6 +1431,7 @@ func pickAutoLPositive(
cInfo candidateLevelInfo,
baseLevel int,
diskAvailBytes func() uint64,
levelMaxBytes [7]int64,
) (pc *pickedCompaction) {
if cInfo.level == 0 {
panic("pebble: pickAutoLPositive called for L0")
@@ -1385,9 +1453,15 @@ func pickAutoLPositive(
}
}
if !pc.setupInputs(opts, diskAvailBytes()) {
if !pc.setupInputs(opts, diskAvailBytes(), pc.startLevel) {
return nil
}
if opts.Experimental.MultiLevelCompaction &&
pc.initMultiLevelCompaction(opts, vers, levelMaxBytes, diskAvailBytes()) {
if !pc.setupInputs(opts, diskAvailBytes(), pc.extraLevels[len(pc.extraLevels)-1]) {
return nil
}
}
return pc
}
@@ -1410,7 +1484,7 @@ func pickL0(
}
if lcf != nil {
pc = newPickedCompactionFromL0(lcf, opts, vers, baseLevel, true)
pc.setupInputs(opts, diskAvailBytes())
pc.setupInputs(opts, diskAvailBytes(), pc.startLevel)
if pc.startLevel.files.Empty() {
opts.Logger.Fatalf("empty compaction chosen")
}
@@ -1428,7 +1502,7 @@ func pickL0(
}
if lcf != nil {
pc = newPickedCompactionFromL0(lcf, opts, vers, 0, false)
if !pc.setupInputs(opts, diskAvailBytes()) {
if !pc.setupInputs(opts, diskAvailBytes(), pc.startLevel) {
return nil
}
if pc.startLevel.files.Empty() {
@@ -1474,12 +1548,16 @@ func (p *compactionPickerByScore) pickManual(
if conflictsWithInProgress(manual, outputLevel, env.inProgressCompactions, p.opts.Comparer.Compare) {
return nil, true
}
pc = pickManualHelper(p.opts, manual, p.vers, p.baseLevel, p.diskAvailBytes)
pc = pickManualHelper(p.opts, manual, p.vers, p.baseLevel, p.diskAvailBytes, p.levelMaxBytes)
if pc == nil {
return nil, false
}
if pc.outputLevel.level != outputLevel {
panic("pebble: compaction picked unexpected output level")
if len(pc.extraLevels) > 0 {
// multilevel compactions relax this invariant
} else {
panic("pebble: compaction picked unexpected output level")
}
}
// Fail-safe to protect against compacting the same sstable concurrently.
if inputRangeAlreadyCompacting(env, pc) {
@@ -1494,6 +1572,7 @@ func pickManualHelper(
vers *version,
baseLevel int,
diskAvailBytes func() uint64,
levelMaxBytes [7]int64,
) (pc *pickedCompaction) {
pc = newPickedCompaction(opts, vers, manual.level, defaultOutputLevel(manual.level, baseLevel), baseLevel)
manual.outputLevel = pc.outputLevel.level
@@ -1503,9 +1582,15 @@ func pickManualHelper(
// Nothing to do
return nil
}
if !pc.setupInputs(opts, diskAvailBytes()) {
if !pc.setupInputs(opts, diskAvailBytes(), pc.startLevel) {
return nil
}
if opts.Experimental.MultiLevelCompaction && pc.startLevel.level > 0 &&
pc.initMultiLevelCompaction(opts, vers, levelMaxBytes, diskAvailBytes()) {
if !pc.setupInputs(opts, diskAvailBytes(), pc.extraLevels[len(pc.extraLevels)-1]) {
return nil
}
}
return pc
}
@@ -1554,7 +1639,7 @@ func pickReadTriggeredCompactionHelper(
pc = newPickedCompaction(p.opts, p.vers, rc.level, defaultOutputLevel(rc.level, p.baseLevel), p.baseLevel)
pc.startLevel.files = overlapSlice
if !pc.setupInputs(p.opts, p.diskAvailBytes()) {
if !pc.setupInputs(p.opts, p.diskAvailBytes(), pc.startLevel) {
return nil
}
if inputRangeAlreadyCompacting(env, pc) {
@@ -1588,7 +1673,7 @@ func inputRangeAlreadyCompacting(env compactionEnv, pc *pickedCompaction) bool {
for _, cl := range pc.inputs {
iter := cl.files.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
if f.Compacting {
if f.IsCompacting() {
return true
}
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/cockroachdb/pebble/sstable"
"github.com/cockroachdb/pebble/vfs"
"github.com/cockroachdb/pebble/vfs/atomicfs"
"github.com/prometheus/client_golang/prometheus"
)
const (
@@ -140,22 +141,12 @@ type Writer interface {
// It is safe to modify the contents of the arguments after Set returns.
Set(key, value []byte, o *WriteOptions) error
// Experimental returns the experimental write API.
Experimental() ExperimentalWriter
}
// ExperimentalWriter provides access to experimental features of a Batch.
type ExperimentalWriter interface {
Writer
// RangeKeySet sets a range key mapping the key range [start, end) at the MVCC
// timestamp suffix to value. The suffix is optional. If any portion of the key
// range [start, end) is already set by a range key with the same suffix value,
// RangeKeySet overrides it.
//
// It is safe to modify the contents of the arguments after RangeKeySet returns.
//
// WARNING: This is an experimental feature with limited functionality.
RangeKeySet(start, end, suffix, value []byte, opts *WriteOptions) error
// RangeKeyUnset removes a range key mapping the key range [start, end) at the
@@ -166,8 +157,6 @@ type ExperimentalWriter interface {
//
// It is safe to modify the contents of the arguments after RangeKeyUnset
// returns.
//
// WARNING: This is an experimental feature with limited functionality.
RangeKeyUnset(start, end, suffix []byte, opts *WriteOptions) error
// RangeKeyDelete deletes all of the range keys in the range [start,end)
@@ -177,11 +166,43 @@ type ExperimentalWriter interface {
//
// It is safe to modify the contents of the arguments after RangeKeyDelete
// returns.
//
// WARNING: This is an experimental feature with limited functionality.
RangeKeyDelete(start, end []byte, opts *WriteOptions) error
}
// CPUWorkHandle represents a handle used by the CPUWorkPermissionGranter API.
type CPUWorkHandle interface {
// Permitted indicates whether Pebble can use additional CPU resources.
Permitted() bool
}
// CPUWorkPermissionGranter is used to request permission to opportunistically
// use additional CPUs to speed up internal background work.
type CPUWorkPermissionGranter interface {
// GetPermission returns a handle regardless of whether permission is granted
// or not. In the latter case, the handle is only useful for recording
// the CPU time actually spent on this calling goroutine.
GetPermission(time.Duration) CPUWorkHandle
// CPUWorkDone must be called regardless of whether CPUWorkHandle.Permitted
// returns true or false.
CPUWorkDone(CPUWorkHandle)
}
// Use a default implementation for the CPU work granter to avoid excessive nil
// checks in the code.
type defaultCPUWorkHandle struct{}
func (d defaultCPUWorkHandle) Permitted() bool {
return false
}
type defaultCPUWorkGranter struct{}
func (d defaultCPUWorkGranter) GetPermission(_ time.Duration) CPUWorkHandle {
return defaultCPUWorkHandle{}
}
func (d defaultCPUWorkGranter) CPUWorkDone(_ CPUWorkHandle) {}
// DB provides a concurrent, persistent ordered key/value store.
//
// A DB's basic operations (Get, Set, Delete) should be self-explanatory. Get
@@ -222,16 +243,6 @@ type DB struct {
memTableCount int64
memTableReserved int64 // number of bytes reserved in the cache for memtables
// bytesFlushed is the number of bytes flushed in the current flush. This
// must be read/written atomically since it is accessed by both the flush
// and compaction routines.
bytesFlushed uint64
// bytesCompacted is the number of bytes compacted in the current compaction.
// This is used as a dummy variable to increment during compaction, and the
// value is not used anywhere.
bytesCompacted uint64
// The size of the current log file (i.e. db.mu.log.queue[len(queue)-1].
logSize uint64
@@ -281,9 +292,7 @@ type DB struct {
closed *atomic.Value
closedCh chan struct{}
compactionLimiter limiter
flushLimiter limiter
deletionLimiter limiter
deletionLimiter limiter
// Async deletion jobs spawned by cleaners increment this WaitGroup, and
// call Done when completed. Once `d.mu.cleaning` is false, the db.Close()
@@ -355,7 +364,10 @@ type DB struct {
// (i.e. makeRoomForWrite).
*record.LogWriter
// Can be nil.
metrics *record.LogWriterMetrics
metrics struct {
fsyncLatency prometheus.Histogram
record.LogWriterMetrics
}
}
mem struct {
@@ -461,11 +473,6 @@ type DB struct {
}
}
// rangeKeys is a temporary field so that Pebble can provide a non-durable
// implementation of range keys in advance of the real implementation.
// TODO(jackson): Remove this.
rangeKeys *RangeKeysArena
// Normally equal to time.Now() but may be overridden in tests.
timeNow func() time.Time
}
@@ -542,15 +549,13 @@ func (d *DB) getInternal(key []byte, b *Batch, s *Snapshot) ([]byte, io.Closer,
}
i := &buf.dbi
pointIter := base.WrapIterWithStats(get)
pointIter := get
*i = Iterator{
getIterAlloc: buf,
cmp: d.cmp,
equal: d.equal,
iter: pointIter,
pointIter: pointIter,
merge: d.merge,
split: d.split,
comparer: *d.opts.Comparer,
readState: readState,
keyBuf: buf.keyBuf,
}
@@ -658,21 +663,16 @@ func (d *DB) LogData(data []byte, opts *WriteOptions) error {
return nil
}
// Experimental returns the experimental write API.
func (d *DB) Experimental() ExperimentalWriter {
return experimentalDB{d}
}
type experimentalDB struct {
*DB
}
// RangeKeySet implements the ExperimentalWriter interface.
func (e experimentalDB) RangeKeySet(start, end, suffix, value []byte, opts *WriteOptions) error {
b := newBatch(e.DB)
eb := b.Experimental()
_ = eb.RangeKeySet(start, end, suffix, value, opts)
if err := e.Apply(b, opts); err != nil {
// RangeKeySet sets a range key mapping the key range [start, end) at the MVCC
// timestamp suffix to value. The suffix is optional. If any portion of the key
// range [start, end) is already set by a range key with the same suffix value,
// RangeKeySet overrides it.
//
// It is safe to modify the contents of the arguments after RangeKeySet returns.
func (d *DB) RangeKeySet(start, end, suffix, value []byte, opts *WriteOptions) error {
b := newBatch(d)
_ = b.RangeKeySet(start, end, suffix, value, opts)
if err := d.Apply(b, opts); err != nil {
return err
}
// Only release the batch on success.
@@ -680,12 +680,18 @@ func (e experimentalDB) RangeKeySet(start, end, suffix, value []byte, opts *Writ
return nil
}
// RangeKeyUnset implements the ExperimentalWriter interface.
func (e experimentalDB) RangeKeyUnset(start, end, suffix []byte, opts *WriteOptions) error {
b := newBatch(e.DB)
eb := b.Experimental()
_ = eb.RangeKeyUnset(start, end, suffix, opts)
if err := e.Apply(b, opts); err != nil {
// RangeKeyUnset removes a range key mapping the key range [start, end) at the
// MVCC timestamp suffix. The suffix may be omitted to remove an unsuffixed
// range key. RangeKeyUnset only removes portions of range keys that fall within
// the [start, end) key span, and only range keys with suffixes that exactly
// match the unset suffix.
//
// It is safe to modify the contents of the arguments after RangeKeyUnset
// returns.
func (d *DB) RangeKeyUnset(start, end, suffix []byte, opts *WriteOptions) error {
b := newBatch(d)
_ = b.RangeKeyUnset(start, end, suffix, opts)
if err := d.Apply(b, opts); err != nil {
return err
}
// Only release the batch on success.
@@ -693,12 +699,17 @@ func (e experimentalDB) RangeKeyUnset(start, end, suffix []byte, opts *WriteOpti
return nil
}
// RangeKeyDelete implements the ExperimentalWriter interface.
func (e experimentalDB) RangeKeyDelete(start, end []byte, opts *WriteOptions) error {
b := newBatch(e.DB)
eb := b.Experimental()
_ = eb.RangeKeyDelete(start, end, opts)
if err := e.Apply(b, opts); err != nil {
// RangeKeyDelete deletes all of the range keys in the range [start,end)
// (inclusive on start, exclusive on end). It does not delete point keys (for
// that use DeleteRange). RangeKeyDelete removes all range keys within the
// bounds, including those with or without suffixes.
//
// It is safe to modify the contents of the arguments after RangeKeyDelete
// returns.
func (d *DB) RangeKeyDelete(start, end []byte, opts *WriteOptions) error {
b := newBatch(d)
_ = b.RangeKeyDelete(start, end, opts)
if err := d.Apply(b, opts); err != nil {
return err
}
// Only release the batch on success.
@@ -735,9 +746,6 @@ func (d *DB) Apply(batch *Batch, opts *WriteOptions) error {
if d.split == nil {
return errNoSplit
}
if d.opts.Experimental.RangeKeys == nil {
panic("pebble: range keys require the Experimental.RangeKeys option")
}
if d.FormatMajorVersion() < FormatRangeKeys {
panic(fmt.Sprintf(
"pebble: range keys require at least format major version %d (current: %d)",
@@ -785,9 +793,18 @@ func (d *DB) commitApply(b *Batch, mem *memTable) error {
// If the batch contains range tombstones and the database is configured
// to flush range deletions, schedule a delayed flush so that disk space
// may be reclaimed without additional writes or an explicit flush.
if b.countRangeDels > 0 && d.opts.Experimental.DeleteRangeFlushDelay > 0 {
if b.countRangeDels > 0 && d.opts.FlushDelayDeleteRange > 0 {
d.mu.Lock()
d.maybeScheduleDelayedFlush(mem)
d.maybeScheduleDelayedFlush(mem, d.opts.FlushDelayDeleteRange)
d.mu.Unlock()
}
// If the batch contains range keys and the database is configured to flush
// range keys, schedule a delayed flush so that the range keys are cleared
// from the memtable.
if b.countRangeKeys > 0 && d.opts.FlushDelayRangeKey > 0 {
d.mu.Lock()
d.maybeScheduleDelayedFlush(mem, d.opts.FlushDelayRangeKey)
d.mu.Unlock()
}
@@ -881,9 +898,6 @@ func (d *DB) newIterInternal(batch *Batch, s *Snapshot, o *IterOptions) *Iterato
panic(err)
}
if o.rangeKeys() {
if d.opts.Experimental.RangeKeys == nil {
panic("pebble: range keys require the Experimental.RangeKeys option")
}
if d.FormatMajorVersion() < FormatRangeKeys {
panic(fmt.Sprintf(
"pebble: range keys require at least format major version %d (current: %d)",
@@ -921,24 +935,28 @@ func (d *DB) newIterInternal(batch *Batch, s *Snapshot, o *IterOptions) *Iterato
dbi := &buf.dbi
*dbi = Iterator{
alloc: buf,
cmp: d.cmp,
equal: d.equal,
merge: d.merge,
split: d.split,
comparer: *d.opts.Comparer,
readState: readState,
keyBuf: buf.keyBuf,
prefixOrFullSeekKey: buf.prefixOrFullSeekKey,
boundsBuf: buf.boundsBuf,
batch: batch,
newIters: d.newIters,
newIterRangeKey: d.tableNewRangeKeyIter,
seqNum: seqNum,
db: d,
}
if o != nil {
dbi.opts = *o
dbi.saveBounds(o.LowerBound, o.UpperBound)
}
dbi.opts.logger = d.opts.Logger
if d.opts.private.disableLazyCombinedIteration {
dbi.opts.disableLazyCombinedIteration = true
}
if batch != nil {
dbi.batchSeqNum = dbi.batch.nextSeqNum()
}
return finishInitializingIter(buf)
}
@@ -963,45 +981,95 @@ func finishInitializingIter(buf *iterAlloc) *Iterator {
}
}
// Construct the point iterator. This function either returns a pointer to
// dbi.merging (if points are enabled) or an emptyIter. If this is called
// during a SetOptions call and this Iterator has already initialized
// dbi.merging, constructPointIter returns the existing, unmodified point
// iterator which is stored in dbi.pointIter.
dbi.iter = constructPointIter(dbi, dbi.batch, memtables, buf)
if dbi.opts.pointKeys() {
// Construct the point iterator, initializing dbi.pointIter to point to
// dbi.merging. If this is called during a SetOptions call and this
// Iterator has already initialized dbi.merging, constructPointIter is a
// noop and an initialized pointIter already exists in dbi.pointIter.
dbi.constructPointIter(memtables, buf)
dbi.iter = dbi.pointIter
} else {
dbi.iter = emptyIter
}
// If range keys are enabled, construct the range key iterator stack too.
if dbi.opts.rangeKeys() {
if dbi.rangeKey == nil {
dbi.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
dbi.rangeKey.keys.cmp = dbi.cmp
dbi.rangeKey.rangeKeyIter = dbi.db.newRangeKeyIter(dbi.rangeKey, dbi.seqNum, dbi.batch, dbi.readState, &dbi.opts)
dbi.rangeKeyMasking.init(dbi, dbi.comparer.Compare, dbi.comparer.Split)
// When iterating over both point and range keys, don't create the
// range-key iterator stack immediately if we can avoid it. This
// optimization takes advantage of the expected sparseness of range
// keys, and configures the point-key iterator to dynamically switch to
// combined iteration when it observes a file containing range keys.
//
// Lazy combined iteration is not possible if a batch or a memtable
// contains any range keys.
useLazyCombinedIteration := dbi.rangeKey == nil &&
dbi.opts.KeyTypes == IterKeyTypePointsAndRanges &&
(dbi.batch == nil || dbi.batch.countRangeKeys == 0) &&
!dbi.opts.disableLazyCombinedIteration
if useLazyCombinedIteration {
// The user requested combined iteration, and there's no indexed
// batch currently containing range keys that would prevent lazy
// combined iteration. Check the memtables to see if they contain
// any range keys.
for i := range memtables {
if memtables[i].containsRangeKeys() {
useLazyCombinedIteration = false
break
}
}
}
// Wrap the point iterator (currently dbi.iter) with an interleaving
// iterator that interleaves range keys pulled from
// dbi.rangeKey.rangeKeyIter.
if useLazyCombinedIteration {
dbi.lazyCombinedIter = lazyCombinedIter{
parent: dbi,
pointIter: dbi.pointIter,
combinedIterState: combinedIterState{
initialized: false,
},
}
dbi.iter = &dbi.lazyCombinedIter
} else {
dbi.lazyCombinedIter.combinedIterState = combinedIterState{
initialized: true,
}
if dbi.rangeKey == nil {
dbi.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
dbi.rangeKey.init(dbi.comparer.Compare, dbi.comparer.Split, &dbi.opts)
dbi.constructRangeKeyIter()
} else {
dbi.rangeKey.iterConfig.SetBounds(dbi.opts.LowerBound, dbi.opts.UpperBound)
}
// Wrap the point iterator (currently dbi.iter) with an interleaving
// iterator that interleaves range keys pulled from
// dbi.rangeKey.rangeKeyIter.
//
// NB: The interleaving iterator is always reinitialized, even if
// dbi already had an initialized range key iterator, in case the point
// iterator changed or the range key masking suffix changed.
dbi.rangeKey.iiter.Init(&dbi.comparer, dbi.iter, dbi.rangeKey.rangeKeyIter,
&dbi.rangeKeyMasking, dbi.opts.LowerBound, dbi.opts.UpperBound)
dbi.iter = &dbi.rangeKey.iiter
}
} else {
// !dbi.opts.rangeKeys()
//
// NB: The interleaving iterator is always reinitialized, even if
// dbi already had an initialized range key iterator, in case the point
// iterator changed or the range key masking suffix changed.
dbi.rangeKey.iter.Init(dbi.cmp, dbi.iter, dbi.rangeKey.rangeKeyIter, keyspan.Hooks{
SpanChanged: dbi.rangeKeySpanChanged,
SkipPoint: dbi.rangeKeySkipPoint,
}, dbi.opts.LowerBound, dbi.opts.UpperBound)
dbi.iter = &dbi.rangeKey.iter
// Reset the combined iterator state. The initialized=true ensures the
// iterator doesn't unnecessarily try to switch to combined iteration.
dbi.lazyCombinedIter.combinedIterState = combinedIterState{initialized: true}
}
return dbi
}
func constructPointIter(
dbi *Iterator, batch *Batch, memtables flushableList, buf *iterAlloc,
) internalIteratorWithStats {
if !dbi.opts.pointKeys() {
return emptyIter
func (i *Iterator) constructPointIter(memtables flushableList, buf *iterAlloc) {
if i.pointIter != nil {
// Already have one.
return
}
if dbi.pointIter != nil {
return dbi.pointIter
internalOpts := internalIterOpts{stats: &i.stats.InternalStats}
if i.opts.RangeKeyMasking.Filter != nil {
internalOpts.boundLimitedFilter = &i.rangeKeyMasking
}
// Merging levels and levels from iterAlloc.
@@ -1013,12 +1081,12 @@ func constructPointIter(
// should improve the performance.
numMergingLevels := 0
numLevelIters := 0
if batch != nil {
if i.batch != nil {
numMergingLevels++
}
numMergingLevels += len(memtables)
current := dbi.readState.current
current := i.readState.current
numMergingLevels += len(current.L0SublevelFiles)
numLevelIters += len(current.L0SublevelFiles)
for level := 1; level < len(current.Levels); level++ {
@@ -1037,19 +1105,39 @@ func constructPointIter(
}
// Top-level is the batch, if any.
if batch != nil {
mlevels = append(mlevels, mergingIterLevel{
iter: base.WrapIterWithStats(batch.newInternalIter(&dbi.opts)),
rangeDelIter: batch.newRangeDelIter(&dbi.opts),
})
if i.batch != nil {
if i.batch.index == nil {
// This isn't an indexed batch. Include an error iterator so that
// the resulting iterator correctly surfaces ErrIndexed.
mlevels = append(mlevels, mergingIterLevel{
iter: newErrorIter(ErrNotIndexed),
rangeDelIter: newErrorKeyspanIter(ErrNotIndexed),
})
} else {
i.batch.initInternalIter(&i.opts, &i.batchPointIter)
i.batch.initRangeDelIter(&i.opts, &i.batchRangeDelIter, i.batchSeqNum)
// Only include the batch's rangedel iterator if it's non-empty.
// This requires some subtle logic in the case a rangedel is later
// written to the batch and the view of the batch is refreshed
// during a call to SetOptions—in this case, we need to reconstruct
// the point iterator to add the batch rangedel iterator.
var rangeDelIter keyspan.FragmentIterator
if i.batchRangeDelIter.Count() > 0 {
rangeDelIter = &i.batchRangeDelIter
}
mlevels = append(mlevels, mergingIterLevel{
iter: &i.batchPointIter,
rangeDelIter: rangeDelIter,
})
}
}
// Next are the memtables.
for i := len(memtables) - 1; i >= 0; i-- {
mem := memtables[i]
for j := len(memtables) - 1; j >= 0; j-- {
mem := memtables[j]
mlevels = append(mlevels, mergingIterLevel{
iter: base.WrapIterWithStats(mem.newIter(&dbi.opts)),
rangeDelIter: mem.newRangeDelIter(&dbi.opts),
iter: mem.newIter(&i.opts),
rangeDelIter: mem.newRangeDelIter(&i.opts),
})
}
@@ -1058,16 +1146,13 @@ func constructPointIter(
levelsIndex := len(levels)
mlevels = mlevels[:numMergingLevels]
levels = levels[:numLevelIters]
addLevelIterForFiles := func(files manifest.LevelIterator, level manifest.Level) {
li := &levels[levelsIndex]
li.init(dbi.opts, dbi.cmp, dbi.split, dbi.newIters, files, level, nil)
li.init(i.opts, i.comparer.Compare, i.comparer.Split, i.newIters, files, level, internalOpts)
li.initRangeDel(&mlevels[mlevelsIndex].rangeDelIter)
li.initSmallestLargestUserKey(&mlevels[mlevelsIndex].smallestUserKey,
&mlevels[mlevelsIndex].largestUserKey,
&mlevels[mlevelsIndex].isLargestUserKeyRangeDelSentinel)
li.initIsSyntheticIterBoundsKey(&mlevels[mlevelsIndex].isSyntheticIterBoundsKey)
li.initBoundaryContext(&mlevels[mlevelsIndex].levelIterBoundaryContext)
li.initCombinedIterState(&i.lazyCombinedIter.combinedIterState)
mlevels[mlevelsIndex].iter = li
levelsIndex++
@@ -1087,11 +1172,13 @@ func constructPointIter(
}
addLevelIterForFiles(current.Levels[level].Iter(), manifest.Level(level))
}
buf.merging.init(&dbi.opts, dbi.cmp, dbi.split, mlevels...)
buf.merging.snapshot = dbi.seqNum
buf.merging.init(&i.opts, &i.stats.InternalStats, i.comparer.Compare, i.comparer.Split, mlevels...)
buf.merging.snapshot = i.seqNum
buf.merging.batchSnapshot = i.batchSeqNum
buf.merging.elideRangeTombstones = true
dbi.pointIter = &buf.merging
return &buf.merging
buf.merging.combinedIterState = &i.lazyCombinedIter.combinedIterState
i.pointIter = &buf.merging
i.merging = &buf.merging
}
// NewBatch returns a new empty write-only batch. Any reads on the batch will
@@ -1150,6 +1237,18 @@ func (d *DB) NewSnapshot() *Snapshot {
// or to call Close concurrently with any other DB method. It is not valid
// to call any of a DB's methods after the DB has been closed.
func (d *DB) Close() error {
// Lock the commit pipeline for the duration of Close. This prevents a race
// with makeRoomForWrite. Rotating the WAL in makeRoomForWrite requires
// dropping d.mu several times for I/O. If Close only holds d.mu, an
// in-progress WAL rotation may re-acquire d.mu only once the database is
// closed.
//
// Additionally, locking the commit pipeline makes it more likely that
// (illegal) concurrent writes will observe d.closed.Load() != nil, creating
// more understable panics if the database is improperly used concurrently
// during Close.
d.commit.mu.Lock()
defer d.commit.mu.Unlock()
d.mu.Lock()
defer d.mu.Unlock()
if err := d.closed.Load(); err != nil {
@@ -1239,10 +1338,22 @@ func (d *DB) Close() error {
d.compactionSchedulers.Wait()
d.mu.Lock()
// As a sanity check, ensure that there are no zombie tables. A non-zero count
// hints at a reference count leak.
if ztbls := len(d.mu.versions.zombieTables); ztbls > 0 {
err = firstError(err, errors.Errorf("non-zero zombie file count: %d", ztbls))
}
// If the options include a closer to 'close' the filesystem, close it.
if d.opts.private.fsCloser != nil {
d.opts.private.fsCloser.Close()
}
// Return an error if the user failed to close all open snapshots.
if v := d.mu.snapshots.count(); v > 0 {
err = firstError(err, errors.Errorf("leaked snapshots: %d open snapshots on DB %p", v, d))
}
return err
}
@@ -1426,36 +1537,18 @@ func (d *DB) AsyncFlush() (<-chan struct{}, error) {
return flushed, nil
}
// InternalIntervalMetrics returns the InternalIntervalMetrics and resets for
// the next interval (which is until the next call to this method).
func (d *DB) InternalIntervalMetrics() *InternalIntervalMetrics {
m := &InternalIntervalMetrics{}
d.mu.Lock()
defer d.mu.Unlock()
if d.mu.log.metrics != nil {
m.LogWriter.WriteThroughput = d.mu.log.metrics.WriteThroughput
m.LogWriter.PendingBufferUtilization =
d.mu.log.metrics.PendingBufferLen.Mean() / record.CapAllocatedBlocks
m.LogWriter.SyncQueueUtilization = d.mu.log.metrics.SyncQueueLen.Mean() / record.SyncConcurrency
m.LogWriter.SyncLatencyMicros = d.mu.log.metrics.SyncLatencyMicros
d.mu.log.metrics = nil
}
m.Flush.WriteThroughput = d.mu.compact.flushWriteThroughput
d.mu.compact.flushWriteThroughput = ThroughputMetric{}
return m
}
// Metrics returns metrics about the database.
func (d *DB) Metrics() *Metrics {
metrics := &Metrics{}
recycledLogsCount, recycledLogSize := d.logRecycler.stats()
d.mu.Lock()
vers := d.mu.versions.currentVersion()
*metrics = d.mu.versions.metrics
metrics.Compact.EstimatedDebt = d.mu.versions.picker.estimatedCompactionDebt(0)
metrics.Compact.InProgressBytes = atomic.LoadInt64(&d.mu.versions.atomic.atomicInProgressBytes)
metrics.Compact.NumInProgress = int64(d.mu.compact.compactingCount)
metrics.Compact.MarkedFiles = d.mu.versions.currentVersion().Stats.MarkedForCompaction
metrics.Compact.MarkedFiles = vers.Stats.MarkedForCompaction
for _, m := range d.mu.mem.queue {
metrics.MemTable.Size += m.totalBytes()
}
@@ -1499,9 +1592,18 @@ func (d *DB) Metrics() *Metrics {
}
metrics.private.optionsFileSize = d.optionsFileSize
metrics.Keys.RangeKeySetsCount = countRangeKeySetFragments(vers)
d.mu.versions.logLock()
metrics.private.manifestFileSize = uint64(d.mu.versions.manifest.Size())
d.mu.versions.logUnlock()
metrics.LogWriter.FsyncLatency = d.mu.log.metrics.fsyncLatency
if err := metrics.LogWriter.Merge(&d.mu.log.metrics.LogWriterMetrics); err != nil {
d.opts.Logger.Infof("metrics error: %s", err)
}
metrics.Flush.WriteThroughput = d.mu.compact.flushWriteThroughput
d.mu.Unlock()
metrics.BlockCache = d.opts.Cache.Metrics()
@@ -1791,12 +1893,8 @@ func (d *DB) makeRoomForWrite(b *Batch) error {
err = d.mu.log.LogWriter.Close()
metrics := d.mu.log.LogWriter.Metrics()
d.mu.Lock()
if d.mu.log.metrics == nil {
d.mu.log.metrics = metrics
} else {
if err := d.mu.log.metrics.Merge(metrics); err != nil {
d.opts.Logger.Infof("metrics error: %s", err)
}
if err := d.mu.log.metrics.Merge(metrics); err != nil {
d.opts.Logger.Infof("metrics error: %s", err)
}
d.mu.Unlock()
@@ -1848,6 +1946,7 @@ func (d *DB) makeRoomForWrite(b *Batch) error {
newLogFile.Close()
} else if err == nil {
newLogFile = vfs.NewSyncingFile(newLogFile, vfs.SyncingFileOptions{
NoSyncOnClose: d.opts.NoSyncOnClose,
BytesPerSync: d.opts.WALBytesPerSync,
PreallocateSize: d.walPreallocateSize(),
})
@@ -1883,8 +1982,10 @@ func (d *DB) makeRoomForWrite(b *Batch) error {
if !d.opts.DisableWAL {
d.mu.log.queue = append(d.mu.log.queue, fileInfo{fileNum: newLogNum, fileSize: newLogSize})
d.mu.log.LogWriter = record.NewLogWriter(newLogFile, newLogNum)
d.mu.log.LogWriter.SetMinSyncInterval(d.opts.WALMinSyncInterval)
d.mu.log.LogWriter = record.NewLogWriter(newLogFile, newLogNum, record.LogWriterConfig{
WALFsyncLatency: d.mu.log.metrics.fsyncLatency,
WALMinSyncInterval: d.opts.WALMinSyncInterval,
})
}
immMem := d.mu.mem.mutable
@@ -1946,7 +2047,7 @@ func (d *DB) makeRoomForWrite(b *Batch) error {
var entry *flushableEntry
d.mu.mem.mutable, entry = d.newMemTable(newLogNum, logSeqNum)
d.mu.mem.queue = append(d.mu.mem.queue, entry)
d.updateReadStateLocked(nil, nil)
d.updateReadStateLocked(nil)
if immMem.writerUnref() {
d.maybeScheduleFlush()
}

View File

@@ -14,40 +14,40 @@ type errorIter struct {
}
// errorIter implements the base.InternalIterator interface.
var _ internalIteratorWithStats = (*errorIter)(nil)
var _ internalIterator = (*errorIter)(nil)
func newErrorIter(err error) *errorIter {
return &errorIter{err: err}
}
func (c *errorIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) {
return nil, nil
func (c *errorIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) {
return nil, base.LazyValue{}
}
func (c *errorIter) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
return nil, nil
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
return nil, base.LazyValue{}
}
func (c *errorIter) SeekLT(key []byte) (*InternalKey, []byte) {
return nil, nil
func (c *errorIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
return nil, base.LazyValue{}
}
func (c *errorIter) First() (*InternalKey, []byte) {
return nil, nil
func (c *errorIter) First() (*InternalKey, base.LazyValue) {
return nil, base.LazyValue{}
}
func (c *errorIter) Last() (*InternalKey, []byte) {
return nil, nil
func (c *errorIter) Last() (*InternalKey, base.LazyValue) {
return nil, base.LazyValue{}
}
func (c *errorIter) Next() (*InternalKey, []byte) {
return nil, nil
func (c *errorIter) Next() (*InternalKey, base.LazyValue) {
return nil, base.LazyValue{}
}
func (c *errorIter) Prev() (*InternalKey, []byte) {
return nil, nil
func (c *errorIter) Prev() (*InternalKey, base.LazyValue) {
return nil, base.LazyValue{}
}
func (c *errorIter) Error() error {
@@ -63,8 +63,6 @@ func (c *errorIter) String() string {
}
func (c *errorIter) SetBounds(lower, upper []byte) {}
func (c *errorIter) Stats() InternalIteratorStats { return InternalIteratorStats{} }
func (c *errorIter) ResetStats() {}
type errorKeyspanIter struct {
err error
@@ -77,12 +75,12 @@ func newErrorKeyspanIter(err error) *errorKeyspanIter {
return &errorKeyspanIter{err: err}
}
func (*errorKeyspanIter) SeekGE(key []byte) keyspan.Span { return keyspan.Span{} }
func (*errorKeyspanIter) SeekLT(key []byte) keyspan.Span { return keyspan.Span{} }
func (*errorKeyspanIter) First() keyspan.Span { return keyspan.Span{} }
func (*errorKeyspanIter) Last() keyspan.Span { return keyspan.Span{} }
func (*errorKeyspanIter) Next() keyspan.Span { return keyspan.Span{} }
func (*errorKeyspanIter) Prev() keyspan.Span { return keyspan.Span{} }
func (i *errorKeyspanIter) Error() error { return i.err }
func (i *errorKeyspanIter) Close() error { return i.err }
func (*errorKeyspanIter) String() string { return "error" }
func (*errorKeyspanIter) SeekGE(key []byte) *keyspan.Span { return nil }
func (*errorKeyspanIter) SeekLT(key []byte) *keyspan.Span { return nil }
func (*errorKeyspanIter) First() *keyspan.Span { return nil }
func (*errorKeyspanIter) Last() *keyspan.Span { return nil }
func (*errorKeyspanIter) Next() *keyspan.Span { return nil }
func (*errorKeyspanIter) Prev() *keyspan.Span { return nil }
func (i *errorKeyspanIter) Error() error { return i.err }
func (i *errorKeyspanIter) Close() error { return i.err }
func (*errorKeyspanIter) String() string { return "error" }

View File

@@ -5,41 +5,113 @@
package pebble
import (
"fmt"
"sort"
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/keyspan"
"github.com/cockroachdb/pebble/internal/manifest"
"github.com/cockroachdb/pebble/internal/rangekey"
"github.com/cockroachdb/pebble/sstable"
)
// NewExternalIter takes an input set of sstable files which may overlap
// arbitrarily and returns an Iterator over the merged contents of the sstables.
// ExternalIterOption provide an interface to specify open-time options to
// NewExternalIter.
type ExternalIterOption interface {
// iterApply is called on the iterator during opening in order to set internal
// parameters.
iterApply(*Iterator)
// readerOptions returns any reader options added by this iter option.
readerOptions() []sstable.ReaderOption
}
type externalIterReaderOptions struct {
opts []sstable.ReaderOption
}
func (e *externalIterReaderOptions) iterApply(iterator *Iterator) {
// Do nothing.
}
func (e *externalIterReaderOptions) readerOptions() []sstable.ReaderOption {
return e.opts
}
// ExternalIterReaderOptions returns an ExternalIterOption that specifies
// sstable.ReaderOptions to be applied on sstable readers in NewExternalIter.
func ExternalIterReaderOptions(opts ...sstable.ReaderOption) ExternalIterOption {
return &externalIterReaderOptions{opts: opts}
}
// ExternalIterForwardOnly is an ExternalIterOption that specifies this iterator
// will only be used for forward positioning operations (First, SeekGE, Next).
// This could enable optimizations that take advantage of this invariant.
// Behaviour when a reverse positioning operation is done on an iterator
// opened with this option is unpredictable, though in most cases it should.
type ExternalIterForwardOnly struct{}
func (e ExternalIterForwardOnly) iterApply(iter *Iterator) {
iter.forwardOnly = true
}
func (e ExternalIterForwardOnly) readerOptions() []sstable.ReaderOption {
return nil
}
// NewExternalIter takes an input 2d array of sstable files which may overlap
// across subarrays but not within a subarray (at least as far as points are
// concerned; range keys are allowed to overlap arbitrarily even within a
// subarray), and returns an Iterator over the merged contents of the sstables.
// Input sstables may contain point keys, range keys, range deletions, etc. The
// input files slice must be sorted in reverse chronological ordering. A key in
// a file at a lower index will shadow a key with an identical user key
// contained within a file at a higher index.
// input files slice must be sorted in reverse chronological ordering. A key in a
// file at a lower index subarray will shadow a key with an identical user key
// contained within a file at a higher index subarray. Each subarray must be
// sorted in internal key order, where lower index files contain keys that sort
// left of files with higher indexes.
//
// Input sstables must only contain keys with the zero sequence number.
//
// Iterators constructed through NewExternalIter do not support all iterator
// options, including block-property and table filters. NewExternalIter errors
// if an incompatible option is set.
func NewExternalIter(
o *Options,
iterOpts *IterOptions,
files []sstable.ReadableFile,
extraReaderOpts ...sstable.ReaderOption,
files [][]sstable.ReadableFile,
extraOpts ...ExternalIterOption,
) (it *Iterator, err error) {
var readers []*sstable.Reader
// Ensure we close all the opened readers if we error out.
closeReaders := func() {
for i := range readers {
_ = readers[i].Close()
if iterOpts != nil {
if err := validateExternalIterOpts(iterOpts); err != nil {
return nil, err
}
}
var readers [][]*sstable.Reader
// Ensure we close all the opened readers if we error out.
defer func() {
if err != nil {
closeReaders()
for i := range readers {
for j := range readers[i] {
_ = readers[i][j].Close()
}
}
}
}()
readers, err = openExternalTables(o, files, o.MakeReaderOptions(), extraReaderOpts...)
seqNumOffset := 0
var extraReaderOpts []sstable.ReaderOption
for i := range extraOpts {
extraReaderOpts = append(extraReaderOpts, extraOpts[i].readerOptions()...)
}
for _, levelFiles := range files {
seqNumOffset += len(levelFiles)
}
for _, levelFiles := range files {
var subReaders []*sstable.Reader
seqNumOffset -= len(levelFiles)
subReaders, err = openExternalTables(o, levelFiles, seqNumOffset, o.MakeReaderOptions(), extraReaderOpts...)
readers = append(readers, subReaders)
}
if err != nil {
return nil, err
}
@@ -48,16 +120,17 @@ func NewExternalIter(
dbi := &buf.dbi
*dbi = Iterator{
alloc: buf,
cmp: o.Comparer.Compare,
equal: o.equal(),
merge: o.Merger.Merge,
split: o.Comparer.Split,
comparer: *o.Comparer,
readState: nil,
keyBuf: buf.keyBuf,
prefixOrFullSeekKey: buf.prefixOrFullSeekKey,
boundsBuf: buf.boundsBuf,
batch: nil,
newIters: func(f *manifest.FileMetadata, opts *IterOptions, bytesIterated *uint64) (internalIterator, keyspan.FragmentIterator, error) {
// Add the readers to the Iterator so that Close closes them, and
// SetOptions can re-construct iterators from them.
externalReaders: readers,
newIters: func(f *manifest.FileMetadata, opts *IterOptions, internalOpts internalIterOpts) (internalIterator, keyspan.FragmentIterator, error) {
// NB: External iterators are currently constructed without any
// `levelIters`. newIters should never be called. When we support
// organizing multiple non-overlapping files into a single level
@@ -72,92 +145,170 @@ func NewExternalIter(
dbi.opts = *iterOpts
dbi.saveBounds(iterOpts.LowerBound, iterOpts.UpperBound)
}
for i := range extraOpts {
extraOpts[i].iterApply(dbi)
}
finishInitializingExternal(dbi)
return dbi, nil
}
func validateExternalIterOpts(iterOpts *IterOptions) error {
switch {
case iterOpts.TableFilter != nil:
return errors.Errorf("pebble: external iterator: TableFilter unsupported")
case iterOpts.PointKeyFilters != nil:
return errors.Errorf("pebble: external iterator: PointKeyFilters unsupported")
case iterOpts.RangeKeyFilters != nil:
return errors.Errorf("pebble: external iterator: RangeKeyFilters unsupported")
case iterOpts.OnlyReadGuaranteedDurable:
return errors.Errorf("pebble: external iterator: OnlyReadGuaranteedDurable unsupported")
case iterOpts.UseL6Filters:
return errors.Errorf("pebble: external iterator: UseL6Filters unsupported")
}
return nil
}
func createExternalPointIter(it *Iterator) (internalIterator, error) {
// TODO(jackson): In some instances we could generate fewer levels by using
// L0Sublevels code to organize nonoverlapping files into the same level.
// This would allow us to use levelIters and keep a smaller set of data and
// files in-memory. However, it would also require us to identify the bounds
// of all the files upfront.
// Ensure we close all iters if error out early.
mlevels := buf.mlevels[:0]
var rangeKeyIters []keyspan.FragmentIterator
defer func() {
if err != nil {
for i := range rangeKeyIters {
_ = rangeKeyIters[i].Close()
}
for i := range mlevels {
if mlevels[i].iter != nil {
_ = mlevels[i].iter.Close()
}
if mlevels[i].rangeDelIter != nil {
_ = mlevels[i].rangeDelIter.Close()
}
}
}
}()
if iterOpts.pointKeys() {
if len(files) > cap(mlevels) {
mlevels = make([]mergingIterLevel, 0, len(files))
}
if !it.opts.pointKeys() {
return emptyIter, nil
} else if it.pointIter != nil {
return it.pointIter, nil
}
mlevels := it.alloc.mlevels[:0]
if len(it.externalReaders) > cap(mlevels) {
mlevels = make([]mergingIterLevel, 0, len(it.externalReaders))
}
for _, readers := range it.externalReaders {
var combinedIters []internalIterator
for _, r := range readers {
pointIter, err := r.NewIter(dbi.opts.LowerBound, dbi.opts.UpperBound)
var (
rangeDelIter keyspan.FragmentIterator
pointIter internalIterator
err error
)
pointIter, err = r.NewIterWithBlockPropertyFilters(
it.opts.LowerBound,
it.opts.UpperBound,
nil, /* BlockPropertiesFilterer */
false, /* useFilterBlock */
&it.stats.InternalStats,
)
if err != nil {
return nil, err
}
rangeDelIter, err := r.NewRawRangeDelIter()
rangeDelIter, err = r.NewRawRangeDelIter()
if err != nil {
_ = pointIter.Close()
return nil, err
}
if rangeDelIter == nil && pointIter != nil && it.forwardOnly {
// TODO(bilal): Consider implementing range key pausing in
// simpleLevelIter so we can reduce mergingIterLevels even more by
// sending all sstable iterators to combinedIters, not just those
// corresponding to sstables without range deletes.
combinedIters = append(combinedIters, pointIter)
continue
}
mlevels = append(mlevels, mergingIterLevel{
iter: base.WrapIterWithStats(pointIter),
iter: pointIter,
rangeDelIter: rangeDelIter,
})
}
}
buf.merging.init(&dbi.opts, dbi.cmp, dbi.split, mlevels...)
buf.merging.snapshot = base.InternalKeySeqNumMax
buf.merging.elideRangeTombstones = true
dbi.pointIter = &buf.merging
dbi.iter = dbi.pointIter
if dbi.opts.rangeKeys() {
for _, r := range readers {
rki, err := r.NewRawRangeKeyIter()
if err != nil {
return nil, err
if len(combinedIters) == 1 {
mlevels = append(mlevels, mergingIterLevel{
iter: combinedIters[0],
})
} else if len(combinedIters) > 1 {
sli := &simpleLevelIter{
cmp: it.cmp,
iters: combinedIters,
}
if rki != nil {
rangeKeyIters = append(rangeKeyIters, rki)
sli.init(it.opts)
mlevels = append(mlevels, mergingIterLevel{
iter: sli,
rangeDelIter: nil,
})
}
}
if len(mlevels) == 1 && mlevels[0].rangeDelIter == nil {
// Set closePointIterOnce to true. This is because we're bypassing the
// merging iter, which turns Close()s on it idempotent for any child
// iterators. The outer Iterator could call Close() on a point iter twice,
// which sstable iterators do not support (as they release themselves to
// a pool).
it.closePointIterOnce = true
return mlevels[0].iter, nil
}
it.alloc.merging.init(&it.opts, &it.stats.InternalStats, it.comparer.Compare, it.comparer.Split, mlevels...)
it.alloc.merging.snapshot = base.InternalKeySeqNumMax
it.alloc.merging.elideRangeTombstones = true
return &it.alloc.merging, nil
}
func finishInitializingExternal(it *Iterator) {
pointIter, err := createExternalPointIter(it)
if err != nil {
it.pointIter = &errorIter{err: err}
} else {
it.pointIter = pointIter
}
it.iter = it.pointIter
if it.opts.rangeKeys() {
it.rangeKeyMasking.init(it, it.comparer.Compare, it.comparer.Split)
var rangeKeyIters []keyspan.FragmentIterator
if it.rangeKey == nil {
// We could take advantage of the lack of overlaps in range keys within
// each slice in it.externalReaders, and generate keyspan.LevelIters
// out of those. However, since range keys are expected to be sparse to
// begin with, the performance gain might not be significant enough to
// warrant it.
//
// TODO(bilal): Explore adding a simpleRangeKeyLevelIter that does not
// operate on FileMetadatas (similar to simpleLevelIter), and implements
// this optimization.
for _, readers := range it.externalReaders {
for _, r := range readers {
if rki, err := r.NewRawRangeKeyIter(); err != nil {
rangeKeyIters = append(rangeKeyIters, &errorKeyspanIter{err: err})
} else if rki != nil {
rangeKeyIters = append(rangeKeyIters, rki)
}
}
}
if len(rangeKeyIters) > 0 {
it.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
it.rangeKey.init(it.comparer.Compare, it.comparer.Split, &it.opts)
it.rangeKey.rangeKeyIter = it.rangeKey.iterConfig.Init(
&it.comparer,
base.InternalKeySeqNumMax,
it.opts.LowerBound, it.opts.UpperBound,
&it.hasPrefix, &it.prefixOrFullSeekKey,
)
for i := range rangeKeyIters {
it.rangeKey.iterConfig.AddLevel(rangeKeyIters[i])
}
}
}
dbi.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
dbi.rangeKey.rangeKeyIter = rangekey.InitUserIteration(
o.Comparer.Compare,
base.InternalKeySeqNumMax,
&dbi.rangeKey.alloc.merging,
&dbi.rangeKey.alloc.defraging,
rangeKeyIters...,
)
dbi.rangeKey.iter.Init(dbi.cmp, &buf.merging, dbi.rangeKey.rangeKeyIter, keyspan.Hooks{
SpanChanged: dbi.rangeKeySpanChanged,
SkipPoint: dbi.rangeKeySkipPoint,
}, dbi.opts.LowerBound, dbi.opts.UpperBound)
dbi.iter = &dbi.rangeKey.iter
if it.rangeKey != nil {
it.rangeKey.iiter.Init(&it.comparer, it.iter, it.rangeKey.rangeKeyIter, &it.rangeKeyMasking,
it.opts.LowerBound, it.opts.UpperBound)
it.iter = &it.rangeKey.iiter
}
}
// Close all the opened sstable.Readers when the Iterator is closed.
dbi.closeHook = closeReaders
return dbi, nil
}
func openExternalTables(
o *Options,
files []sstable.ReadableFile,
seqNumOffset int,
readerOpts sstable.ReaderOptions,
extraReaderOpts ...sstable.ReaderOption,
) (readers []*sstable.Reader, err error) {
@@ -169,8 +320,193 @@ func openExternalTables(
}
// Use the index of the file in files as the sequence number for all of
// its keys.
r.Properties.GlobalSeqNum = uint64(len(files) - i)
r.Properties.GlobalSeqNum = uint64(len(files) - i + seqNumOffset)
readers = append(readers, r)
}
return readers, err
}
// simpleLevelIter is similar to a levelIter in that it merges the points
// from multiple point iterators that are non-overlapping in the key ranges
// they return. It is only expected to support forward iteration and forward
// regular seeking; reverse iteration and prefix seeking is not supported.
// Intended to be a low-overhead, non-FileMetadata dependent option for
// NewExternalIter. To optimize seeking and forward iteration, it maintains
// two slices of child iterators; one of all iterators, and a subset of it that
// contains just the iterators that contain point keys within the current
// bounds.
//
// Note that this levelIter does not support pausing at file boundaries
// in case of range tombstones in this file that could apply to points outside
// of this file (and outside of this level). This is sufficient for optimizing
// the main use cases of NewExternalIter, however for completeness it would make
// sense to build this pausing functionality in.
type simpleLevelIter struct {
cmp Compare
err error
lowerBound []byte
iters []internalIterator
filtered []internalIterator
firstKeys [][]byte
firstKeysBuf []byte
currentIdx int
}
var _ internalIterator = &simpleLevelIter{}
// init initializes this simpleLevelIter.
func (s *simpleLevelIter) init(opts IterOptions) {
s.currentIdx = 0
s.lowerBound = opts.LowerBound
s.resetFilteredIters()
}
func (s *simpleLevelIter) resetFilteredIters() {
s.filtered = s.filtered[:0]
s.firstKeys = s.firstKeys[:0]
s.firstKeysBuf = s.firstKeysBuf[:0]
s.err = nil
for i := range s.iters {
var iterKey *base.InternalKey
if s.lowerBound != nil {
iterKey, _ = s.iters[i].SeekGE(s.lowerBound, base.SeekGEFlagsNone)
} else {
iterKey, _ = s.iters[i].First()
}
if iterKey != nil {
s.filtered = append(s.filtered, s.iters[i])
bufStart := len(s.firstKeysBuf)
s.firstKeysBuf = append(s.firstKeysBuf, iterKey.UserKey...)
s.firstKeys = append(s.firstKeys, s.firstKeysBuf[bufStart:bufStart+len(iterKey.UserKey)])
} else if err := s.iters[i].Error(); err != nil {
s.err = err
}
}
}
func (s *simpleLevelIter) SeekGE(
key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
if s.err != nil {
return nil, base.LazyValue{}
}
// Find the first file that is entirely >= key. The file before that could
// contain the key we're looking for.
n := sort.Search(len(s.firstKeys), func(i int) bool {
return s.cmp(key, s.firstKeys[i]) <= 0
})
if n > 0 {
s.currentIdx = n - 1
} else {
s.currentIdx = n
}
if s.currentIdx < len(s.filtered) {
if iterKey, val := s.filtered[s.currentIdx].SeekGE(key, flags); iterKey != nil {
return iterKey, val
}
if err := s.filtered[s.currentIdx].Error(); err != nil {
s.err = err
}
s.currentIdx++
}
return s.skipEmptyFileForward(key, flags)
}
func (s *simpleLevelIter) skipEmptyFileForward(
seekKey []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
var iterKey *base.InternalKey
var val base.LazyValue
for s.currentIdx >= 0 && s.currentIdx < len(s.filtered) && s.err == nil {
if seekKey != nil {
iterKey, val = s.filtered[s.currentIdx].SeekGE(seekKey, flags)
} else if s.lowerBound != nil {
iterKey, val = s.filtered[s.currentIdx].SeekGE(s.lowerBound, flags)
} else {
iterKey, val = s.filtered[s.currentIdx].First()
}
if iterKey != nil {
return iterKey, val
}
if err := s.filtered[s.currentIdx].Error(); err != nil {
s.err = err
}
s.currentIdx++
}
return nil, base.LazyValue{}
}
func (s *simpleLevelIter) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
func (s *simpleLevelIter) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
func (s *simpleLevelIter) First() (*base.InternalKey, base.LazyValue) {
if s.err != nil {
return nil, base.LazyValue{}
}
s.currentIdx = 0
return s.skipEmptyFileForward(nil /* seekKey */, base.SeekGEFlagsNone)
}
func (s *simpleLevelIter) Last() (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
func (s *simpleLevelIter) Next() (*base.InternalKey, base.LazyValue) {
if s.err != nil {
return nil, base.LazyValue{}
}
if s.currentIdx < 0 || s.currentIdx >= len(s.filtered) {
return nil, base.LazyValue{}
}
if iterKey, val := s.filtered[s.currentIdx].Next(); iterKey != nil {
return iterKey, val
}
s.currentIdx++
return s.skipEmptyFileForward(nil /* seekKey */, base.SeekGEFlagsNone)
}
func (s *simpleLevelIter) Prev() (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
func (s *simpleLevelIter) Error() error {
if s.currentIdx >= 0 && s.currentIdx < len(s.filtered) {
s.err = firstError(s.err, s.filtered[s.currentIdx].Error())
}
return s.err
}
func (s *simpleLevelIter) Close() error {
var err error
for i := range s.iters {
err = firstError(err, s.iters[i].Close())
}
return err
}
func (s *simpleLevelIter) SetBounds(lower, upper []byte) {
s.currentIdx = -1
s.lowerBound = lower
for i := range s.iters {
s.iters[i].SetBounds(lower, upper)
}
s.resetFilteredIters()
}
func (s *simpleLevelIter) String() string {
if s.currentIdx < 0 || s.currentIdx >= len(s.filtered) {
return "simpleLevelIter: current=<nil>"
}
return fmt.Sprintf("simpleLevelIter: current=%s", s.filtered[s.currentIdx])
}
var _ internalIterator = &simpleLevelIter{}

View File

@@ -85,7 +85,7 @@ func flushExternalTable(untypedDB interface{}, path string, originalMeta *fileMe
}
return err
}
d.updateReadStateLocked(d.opts.DebugCheck, nil)
d.updateReadStateLocked(d.opts.DebugCheck)
d.updateTableStatsLocked(ve.NewFiles)
d.deleteObsoleteFiles(jobID, true /* waitForOngoing */)
d.maybeScheduleCompaction()

View File

@@ -7,6 +7,7 @@ package pebble
import (
"fmt"
"sync/atomic"
"time"
"github.com/cockroachdb/pebble/internal/keyspan"
)
@@ -17,6 +18,7 @@ type flushable interface {
newFlushIter(o *IterOptions, bytesFlushed *uint64) internalIterator
newRangeDelIter(o *IterOptions) keyspan.FragmentIterator
newRangeKeyIter(o *IterOptions) keyspan.FragmentIterator
containsRangeKeys() bool
// inuseBytes returns the number of inuse bytes by the flushable.
inuseBytes() uint64
// totalBytes returns the total number of bytes allocated by the flushable.
@@ -36,9 +38,10 @@ type flushableEntry struct {
// flushForced indicates whether a flush was forced on this memtable (either
// manual, or due to ingestion). Protected by DB.mu.
flushForced bool
// delayedFlushForced indicates whether a timer has been set to force a flush
// on this memtable at some point in the future. Protected by DB.mu
delayedFlushForced bool
// delayedFlushForcedAt indicates whether a timer has been set to force a
// flush on this memtable at some point in the future. Protected by DB.mu.
// Holds the timestamp of when the flush will be issued.
delayedFlushForcedAt time.Time
// logNum corresponds to the WAL that contains the records present in the
// receiver.
logNum FileNum

View File

@@ -40,6 +40,8 @@ func (v FormatMajorVersion) String() string {
}
const (
// 21.2 versions.
// FormatDefault leaves the format version unspecified. The
// FormatDefault constant may be ratcheted upwards over time.
FormatDefault FormatMajorVersion = iota
@@ -68,6 +70,9 @@ const (
// kind, base.InternalKeyKindSetWithDelete. Previous Pebble versions will be
// unable to open this database.
FormatSetWithDelete
// 22.1 versions.
// FormatBlockPropertyCollector is a format major version that introduces
// BlockPropertyCollectors.
FormatBlockPropertyCollector
@@ -77,21 +82,46 @@ const (
// (without holding mutexes) until the scan of the LSM is complete and the
// manifest has been rotated.
FormatSplitUserKeysMarked
// FormatMarkedCompacted is a format major version that guarantees that all
// files explicitly marked for compaction in the manifest have been
// compacted. Combined with the FormatSplitUserKeysMarked format major
// version, this version guarantees that there are no user keys split across
// multiple files within a level L1+. Ratcheting to this format version will
// block (without holding mutexes) until all necessary compactions for files
// marked for compaction are complete.
FormatMarkedCompacted
// 22.2 versions.
// FormatSplitUserKeysMarkedCompacted is a format major version that
// guarantees that all files explicitly marked for compaction in the manifest
// have been compacted. Combined with the FormatSplitUserKeysMarked format
// major version, this version guarantees that there are no user keys split
// across multiple files within a level L1+. Ratcheting to this format version
// will block (without holding mutexes) until all necessary compactions for
// files marked for compaction are complete.
FormatSplitUserKeysMarkedCompacted
// FormatRangeKeys is a format major version that introduces range keys.
FormatRangeKeys
// FormatMinTableFormatPebblev1 is a format major version that guarantees that
// tables created by or ingested into the DB at or above this format major
// version will have a table format version of at least Pebblev1 (Block
// Properties).
FormatMinTableFormatPebblev1
// FormatPrePebblev1Marked is a format major version that guarantees that all
// sstables with a table format version pre-Pebblev1 (i.e. those that are
// guaranteed to not contain block properties) are marked for compaction in
// the manifest. Ratcheting to FormatPrePebblev1Marked will block (without
// holding mutexes) until the scan of the LSM is complete and the manifest has
// been rotated.
FormatPrePebblev1Marked
// 23.1 versions.
// FormatPrePebblev1MarkedCompacted is a format major version that
// guarantees that all sstables explicitly marked for compaction in the
// manifest have been compacted. Ratcheting to this format version will block
// (without holding mutexes) until all necessary compactions for files marked
// for compaction are complete.
FormatPrePebblev1MarkedCompacted
// FormatNewest always contains the most recent format major version.
// NB: When adding new versions, the MaxTableFormat method should also be
// updated to return the maximum allowable version for the new
// FormatMajorVersion.
FormatNewest FormatMajorVersion = FormatRangeKeys
FormatNewest FormatMajorVersion = FormatPrePebblev1MarkedCompacted
)
// MaxTableFormat returns the maximum sstable.TableFormat that can be used at
@@ -101,15 +131,34 @@ func (v FormatMajorVersion) MaxTableFormat() sstable.TableFormat {
case FormatDefault, FormatMostCompatible, formatVersionedManifestMarker,
FormatVersioned, FormatSetWithDelete:
return sstable.TableFormatRocksDBv2
case FormatBlockPropertyCollector, FormatSplitUserKeysMarked, FormatMarkedCompacted:
case FormatBlockPropertyCollector, FormatSplitUserKeysMarked,
FormatSplitUserKeysMarkedCompacted:
return sstable.TableFormatPebblev1
case FormatRangeKeys:
case FormatRangeKeys, FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
FormatPrePebblev1MarkedCompacted:
return sstable.TableFormatPebblev2
default:
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
}
}
// MinTableFormat returns the minimum sstable.TableFormat that can be used at
// this FormatMajorVersion.
func (v FormatMajorVersion) MinTableFormat() sstable.TableFormat {
switch v {
case FormatDefault, FormatMostCompatible, formatVersionedManifestMarker,
FormatVersioned, FormatSetWithDelete, FormatBlockPropertyCollector,
FormatSplitUserKeysMarked, FormatSplitUserKeysMarkedCompacted,
FormatRangeKeys:
return sstable.TableFormatLevelDB
case FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
FormatPrePebblev1MarkedCompacted:
return sstable.TableFormatPebblev1
default:
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
}
}
// 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
@@ -189,23 +238,43 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
FormatSplitUserKeysMarked: func(d *DB) error {
// Mark any unmarked files with split-user keys. Note all format major
// versions migrations are invoked with DB.mu locked.
if err := d.markFilesWithSplitUserKeysLocked(); err != nil {
if err := d.markFilesLocked(markFilesWithSplitUserKeys(d.opts.Comparer.Equal)); err != nil {
return err
}
return d.finalizeFormatVersUpgrade(FormatSplitUserKeysMarked)
},
FormatMarkedCompacted: func(d *DB) error {
FormatSplitUserKeysMarkedCompacted: func(d *DB) error {
// Before finalizing the format major version, rewrite any sstables
// still marked for compaction. Note all format major versions
// migrations are invoked with DB.mu locked.
if err := d.compactMarkedFilesLocked(); err != nil {
return err
}
return d.finalizeFormatVersUpgrade(FormatMarkedCompacted)
return d.finalizeFormatVersUpgrade(FormatSplitUserKeysMarkedCompacted)
},
FormatRangeKeys: func(d *DB) error {
return d.finalizeFormatVersUpgrade(FormatRangeKeys)
},
FormatMinTableFormatPebblev1: func(d *DB) error {
return d.finalizeFormatVersUpgrade(FormatMinTableFormatPebblev1)
},
FormatPrePebblev1Marked: func(d *DB) error {
// Mark any unmarked files that contain only table properties. Note all
// format major versions migrations are invoked with DB.mu locked.
if err := d.markFilesLocked(markFilesPrePebblev1(d.tableCache)); err != nil {
return err
}
return d.finalizeFormatVersUpgrade(FormatPrePebblev1Marked)
},
FormatPrePebblev1MarkedCompacted: func(d *DB) error {
// Before finalizing the format major version, rewrite any sstables
// still marked for compaction. Note all format major versions
// migrations are invoked with DB.mu locked.
if err := d.compactMarkedFilesLocked(); err != nil {
return err
}
return d.finalizeFormatVersUpgrade(FormatPrePebblev1MarkedCompacted)
},
}
const formatVersionMarkerName = `format-version`
@@ -316,7 +385,11 @@ func (d *DB) finalizeFormatVersUpgrade(formatVers FormatMajorVersion) error {
// compactMarkedFilesLocked performs a migration that schedules rewrite
// compactions to compact away any sstables marked for compaction.
// compactMarkedFilesLocked is run while ratcheting the database's format major
// version to FormatMarkedCompacted.
// version to FormatSplitUserKeysMarkedCompacted.
//
// Note that while this method is called with the DB.mu held, and will not
// return until all marked files have been compacted, the mutex is dropped while
// waiting for compactions to complete (or for slots to free up).
func (d *DB) compactMarkedFilesLocked() error {
curr := d.mu.versions.currentVersion()
for curr.Stats.MarkedForCompaction > 0 {
@@ -339,44 +412,41 @@ func (d *DB) compactMarkedFilesLocked() error {
if err := d.closed.Load(); err != nil {
return err.(error)
}
// NB: Waiting on this condition variable drops d.mu while blocked.
d.mu.compact.cond.Wait()
// Some flush or compaction was scheduled or completed. Loop again to
// check again for files that must be compacted. The next iteration may
// find same file again, but that's okay. It'll eventually succeed in
// scheduling the compaction and eventually be woken by its completion.
// Some flush or compaction may have scheduled or completed while we waited
// for the manifest lock in maybeScheduleCompactionPicker. Get the latest
// Version before waiting on a compaction.
curr = d.mu.versions.currentVersion()
// Only wait on compactions if there are files still marked for compaction.
// NB: Waiting on this condition variable drops d.mu while blocked.
if curr.Stats.MarkedForCompaction > 0 {
if d.mu.compact.compactingCount == 0 {
panic("expected a compaction of marked files in progress")
}
d.mu.compact.cond.Wait()
// Refresh the current version again.
curr = d.mu.versions.currentVersion()
}
}
return nil
}
// markFilesWithSplitUserKeysLocked scans the LSM's levels 1 through 6 for
// adjacent files that contain the same user key. Such arrangements of files
// were permitted in RocksDB and in Pebble up to SHA a860bbad.
// markFilesWithSplitUserKeysLocked durably marks such files for compaction.
func (d *DB) markFilesWithSplitUserKeysLocked() error {
jobID := d.mu.nextJobID
d.mu.nextJobID++
// Files with split user keys are expected to be rare and performing key
// comparisons for every file within the LSM is expensive, so drop the
// database lock while scanning the file metadata.
//
// After scanning, if we found files to mark, we'll re-acquire the mutex
// before marking them, since MarkedForCompaction and the version's
// Stats.MarkedForCompaction are protected by d.mu.
var files [numLevels][]*fileMetadata
var foundSplitUserKey bool
func() {
equal := d.opts.equal()
vers := d.mu.versions.currentVersion()
// Note the unusual locking: unlock, defer Lock().
d.mu.Unlock()
defer d.mu.Lock()
// findFilesFunc scans the LSM for files, returning true if at least one
// file was found. The returned array contains the matched files, if any, per
// level.
type findFilesFunc func(v *version) (found bool, files [numLevels][]*fileMetadata, _ error)
// markFilesWithSplitUserKeys scans the LSM's levels 1 through 6 for adjacent
// files that contain the same user key. Such arrangements of files were
// permitted in RocksDB and in Pebble up to SHA a860bbad.
var markFilesWithSplitUserKeys = func(equal Equal) findFilesFunc {
return func(v *version) (found bool, files [numLevels][]*fileMetadata, _ error) {
// Files with split user keys are expected to be rare and performing key
// comparisons for every file within the LSM is expensive, so drop the
// database lock while scanning the file metadata.
for l := numLevels - 1; l > 0; l-- {
iter := vers.Levels[l].Iter()
iter := v.Levels[l].Iter()
var prevFile *fileMetadata
var prevUserKey []byte
for f := iter.First(); f != nil; f = iter.Next() {
@@ -384,7 +454,7 @@ func (d *DB) markFilesWithSplitUserKeysLocked() error {
// NB: We may append a file twice, once as prevFile and once
// as f. That's okay, and handled below.
files[l] = append(files[l], prevFile, f)
foundSplitUserKey = true
found = true
}
if f.Largest.IsExclusiveSentinel() {
prevUserKey = nil
@@ -395,41 +465,102 @@ func (d *DB) markFilesWithSplitUserKeysLocked() error {
}
}
}
return
}
}
// markFilesPrePebblev1 scans the LSM for files that do not support block
// properties (i.e. a table format version pre-Pebblev1).
var markFilesPrePebblev1 = func(tc *tableCacheContainer) findFilesFunc {
return func(v *version) (found bool, files [numLevels][]*fileMetadata, err error) {
for l := numLevels - 1; l > 0; l-- {
iter := v.Levels[l].Iter()
for f := iter.First(); f != nil; f = iter.Next() {
err = tc.withReader(f, func(r *sstable.Reader) error {
tf, err := r.TableFormat()
if err != nil {
return err
}
if tf < sstable.TableFormatPebblev1 {
found = true
files[l] = append(files[l], f)
}
return nil
})
if err != nil {
return
}
}
}
return
}
}
// markFilesLock durably marks the files that match the given findFilesFunc for
// compaction.
func (d *DB) markFilesLocked(findFn findFilesFunc) error {
jobID := d.mu.nextJobID
d.mu.nextJobID++
vers := d.mu.versions.currentVersion()
var (
found bool
files [numLevels][]*fileMetadata
err error
)
func() {
// Disable file deletions. Some migrations that mark files may need to
// read the files themselves. The LSM may be changing while the
// migration is determining which files to mark. That's ok, because
// after re-acquiring the mutex and acquiring the manifest lock, we'll
// discard any sstables that have been removed from the LSM. However, we
// also need to make sure that the files we try to open during the scan
// are not removed before we read them. This is preferable over
// gracefully handling nonexistent files because some environments (eg,
// Windows) error if you attempt to remove an open file.
d.disableFileDeletions()
defer d.enableFileDeletions()
// Note the unusual locking: unlock, defer Lock(). The scan of the files in
// the version does not need to block other operations that require the
// DB.mu. Drop it for the scan, before re-acquiring it.
d.mu.Unlock()
defer d.mu.Lock()
found, files, err = findFn(vers)
}()
if err != nil {
return err
}
// The database lock has been acquired again by the defer within the above
// anonymous function.
if !foundSplitUserKey {
if !found {
// Nothing to do.
return nil
}
// Lock the manifest for a coherent view of the LSM.
// After scanning, if we found files to mark, we fetch the current state of
// the LSM (which may have changed) and set MarkedForCompaction on the files,
// and update the version's Stats.MarkedForCompaction count, which are both
// protected by d.mu.
// Lock the manifest for a coherent view of the LSM. The database lock has
// been re-acquired by the defer within the above anonymous function.
d.mu.versions.logLock()
vers := d.mu.versions.currentVersion()
vers = d.mu.versions.currentVersion()
for l, filesToMark := range files {
if len(filesToMark) == 0 {
continue
}
for _, f := range filesToMark {
if f.MarkedForCompaction {
// Ignore files to be marked that have already been compacted or marked.
if f.CompactionState == manifest.CompactionStateCompacted ||
f.MarkedForCompaction {
continue
}
// We need to determine if the file still exists in the current
// version of the LSM. Note that we're making the assumption that if
// f still exists within the LSM, it's within the same level. That's
// okay, because only move compactions may move files between levels
// and move compactions only apply to compactions with a single file
// in the start level, but we already know these files have
// multi-file atomic compaction units.
if m := vers.Levels[l].Find(d.cmp, f); m != nil {
// It's still there. Mark it for compaction and update the
// version's stats.
f.MarkedForCompaction = true
vers.Stats.MarkedForCompaction++
}
// Else, mark the file for compaction in this version.
vers.Stats.MarkedForCompaction++
f.MarkedForCompaction = true
}
// The compaction picker uses the markedForCompactionAnnotator to
// quickly find files marked for compaction, or to quickly determine

View File

@@ -25,7 +25,7 @@ type getIter struct {
key []byte
iter internalIterator
rangeDelIter keyspan.FragmentIterator
tombstone keyspan.Span
tombstone *keyspan.Span
levelIter levelIter
level int
batch *Batch
@@ -33,7 +33,7 @@ type getIter struct {
l0 []manifest.LevelSlice
version *version
iterKey *InternalKey
iterValue []byte
iterValue base.LazyValue
err error
}
@@ -47,29 +47,29 @@ func (g *getIter) String() string {
return fmt.Sprintf("len(l0)=%d, len(mem)=%d, level=%d", len(g.l0), len(g.mem), g.level)
}
func (g *getIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) {
func (g *getIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) {
panic("pebble: SeekGE unimplemented")
}
func (g *getIter) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
panic("pebble: SeekPrefixGE unimplemented")
}
func (g *getIter) SeekLT(key []byte) (*InternalKey, []byte) {
func (g *getIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
panic("pebble: SeekLT unimplemented")
}
func (g *getIter) First() (*InternalKey, []byte) {
func (g *getIter) First() (*InternalKey, base.LazyValue) {
return g.Next()
}
func (g *getIter) Last() (*InternalKey, []byte) {
func (g *getIter) Last() (*InternalKey, base.LazyValue) {
panic("pebble: Last unimplemented")
}
func (g *getIter) Next() (*InternalKey, []byte) {
func (g *getIter) Next() (*InternalKey, base.LazyValue) {
if g.iter != nil {
g.iterKey, g.iterValue = g.iter.Next()
}
@@ -82,26 +82,26 @@ func (g *getIter) Next() (*InternalKey, []byte) {
// 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.snapshot)
g.tombstone = keyspan.Get(g.cmp, g.rangeDelIter, g.key)
if g.err = g.rangeDelIter.Close(); g.err != nil {
return nil, nil
return nil, base.LazyValue{}
}
g.rangeDelIter = nil
}
if g.iterKey != nil {
key := g.iterKey
if g.tombstone.Covers(key.SeqNum()) {
if g.tombstone != nil && g.tombstone.CoversAt(g.snapshot, key.SeqNum()) {
// We have a range tombstone covering this key. Rather than return a
// point or range deletion here, we return false and close our
// internal iterator which will make Valid() return false,
// effectively stopping iteration.
g.err = g.iter.Close()
g.iter = nil
return nil, nil
return nil, base.LazyValue{}
}
if g.equal(g.key, key.UserKey) {
if !key.Visible(g.snapshot) {
if !key.Visible(g.snapshot, base.InternalKeySeqNumMax) {
g.iterKey, g.iterValue = g.iter.Next()
continue
}
@@ -113,23 +113,33 @@ func (g *getIter) Next() (*InternalKey, []byte) {
g.err = g.iter.Close()
g.iter = nil
if g.err != nil {
return nil, nil
return nil, base.LazyValue{}
}
}
// Create an iterator from the batch.
if g.batch != nil {
if g.batch.index == nil {
g.err = ErrNotIndexed
g.iterKey, g.iterValue = nil, base.LazyValue{}
return nil, base.LazyValue{}
}
g.iter = g.batch.newInternalIter(nil)
g.rangeDelIter = g.batch.newRangeDelIter(nil)
g.rangeDelIter = g.batch.newRangeDelIter(
nil,
// Get always reads the entirety of the batch's history, so no
// batch keys should be filtered.
base.InternalKeySeqNumMax,
)
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, base.SeekGEFlagsNone)
g.batch = nil
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, false /* trySeekUsingNext */)
continue
}
// If we have a tombstone from a previous level it is guaranteed to delete
// keys in lower levels.
if !g.tombstone.Empty() {
return nil, nil
if g.tombstone != nil && g.tombstone.VisibleAt(g.snapshot) {
return nil, base.LazyValue{}
}
// Create iterators from memtables from newest to oldest.
@@ -138,7 +148,7 @@ func (g *getIter) Next() (*InternalKey, []byte) {
g.iter = m.newIter(nil)
g.rangeDelIter = m.newRangeDelIter(nil)
g.mem = g.mem[:n-1]
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, false /* trySeekUsingNext */)
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, base.SeekGEFlagsNone)
continue
}
@@ -149,17 +159,17 @@ func (g *getIter) Next() (*InternalKey, []byte) {
g.l0 = g.l0[:n-1]
iterOpts := IterOptions{logger: g.logger}
g.levelIter.init(iterOpts, g.cmp, nil /* split */, g.newIters,
files, manifest.L0Sublevel(n), nil)
files, manifest.L0Sublevel(n), internalIterOpts{})
g.levelIter.initRangeDel(&g.rangeDelIter)
g.iter = &g.levelIter
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, false /* trySeekUsingNext */)
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, base.SeekGEFlagsNone)
continue
}
g.level++
}
if g.level >= numLevels {
return nil, nil
return nil, base.LazyValue{}
}
if g.version.Levels[g.level].Empty() {
g.level++
@@ -168,26 +178,18 @@ func (g *getIter) Next() (*InternalKey, []byte) {
iterOpts := IterOptions{logger: g.logger}
g.levelIter.init(iterOpts, g.cmp, nil /* split */, g.newIters,
g.version.Levels[g.level].Iter(), manifest.Level(g.level), nil)
g.version.Levels[g.level].Iter(), manifest.Level(g.level), internalIterOpts{})
g.levelIter.initRangeDel(&g.rangeDelIter)
g.level++
g.iter = &g.levelIter
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, false /* trySeekUsingNext */)
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, base.SeekGEFlagsNone)
}
}
func (g *getIter) Prev() (*InternalKey, []byte) {
func (g *getIter) Prev() (*InternalKey, base.LazyValue) {
panic("pebble: Prev unimplemented")
}
func (g *getIter) Key() *InternalKey {
return g.iterKey
}
func (g *getIter) Value() []byte {
return g.iterValue
}
func (g *getIter) Valid() bool {
return g.iterKey != nil && g.err == nil
}

View File

@@ -69,10 +69,10 @@ func ingestLoad1(
if err != nil {
return nil, err
}
if tf > fmv.MaxTableFormat() {
if tf < fmv.MinTableFormat() || tf > fmv.MaxTableFormat() {
return nil, errors.Newf(
"pebble: table with format %s unsupported at DB format major version %d, %s",
tf, fmv, fmv.MaxTableFormat(),
"pebble: table format %s is not within range supported at DB format major version %d, (%s,%s)",
tf, fmv, fmv.MinTableFormat(), fmv.MaxTableFormat(),
)
}
@@ -126,7 +126,7 @@ func ingestLoad1(
if iter != nil {
defer iter.Close()
var smallest InternalKey
if s := iter.First(); s.Valid() {
if s := iter.First(); s != nil {
key := s.SmallestKey()
if err := ingestValidateKey(opts, &key); err != nil {
return nil, err
@@ -136,7 +136,7 @@ func ingestLoad1(
if err := iter.Error(); err != nil {
return nil, err
}
if s := iter.Last(); s.Valid() {
if s := iter.Last(); s != nil {
k := s.SmallestKey()
if err := ingestValidateKey(opts, &k); err != nil {
return nil, err
@@ -155,7 +155,7 @@ func ingestLoad1(
if iter != nil {
defer iter.Close()
var smallest InternalKey
if s := iter.First(); s.Valid() {
if s := iter.First(); s != nil {
key := s.SmallestKey()
if err := ingestValidateKey(opts, &key); err != nil {
return nil, err
@@ -165,7 +165,7 @@ func ingestLoad1(
if err := iter.Error(); err != nil {
return nil, err
}
if s := iter.Last(); s.Valid() {
if s := iter.Last(); s != nil {
k := s.SmallestKey()
if err := ingestValidateKey(opts, &k); err != nil {
return nil, err
@@ -271,7 +271,8 @@ func ingestLink(
fs := syncingFS{
FS: opts.FS,
syncOpts: vfs.SyncingFileOptions{
BytesPerSync: opts.BytesPerSync,
NoSyncOnClose: opts.NoSyncOnClose,
BytesPerSync: opts.BytesPerSync,
},
}
@@ -397,7 +398,7 @@ func overlapWithIterator(
// means boundary < L and hence is similar to 1).
// 4) boundary == L and L is sentinel,
// we'll always overlap since for any values of i,j ranges [i, k) and [j, k) always overlap.
key, _ := iter.SeekGE(meta.Smallest.UserKey, false /* trySeekUsingNext */)
key, _ := iter.SeekGE(meta.Smallest.UserKey, base.SeekGEFlagsNone)
if key != nil {
c := sstableKeyCompare(cmp, *key, meta.Largest)
if c <= 0 {
@@ -411,10 +412,10 @@ func overlapWithIterator(
}
rangeDelItr := *rangeDelIter
rangeDel := rangeDelItr.SeekLT(meta.Smallest.UserKey)
if !rangeDel.Valid() {
if rangeDel == nil {
rangeDel = rangeDelItr.Next()
}
for ; rangeDel.Valid(); rangeDel = rangeDelItr.Next() {
for ; rangeDel != nil; rangeDel = rangeDelItr.Next() {
key := rangeDel.SmallestKey()
c := sstableKeyCompare(cmp, key, meta.Largest)
if c > 0 {
@@ -510,7 +511,7 @@ func ingestTargetLevel(
continue
}
iter, rangeDelIter, err := newIters(iter.Current(), nil, nil)
iter, rangeDelIter, err := newIters(iter.Current(), nil, internalIterOpts{})
if err != nil {
return 0, err
}
@@ -848,7 +849,7 @@ func (d *DB) ingestApply(
}); err != nil {
return nil, err
}
d.updateReadStateLocked(d.opts.DebugCheck, nil)
d.updateReadStateLocked(d.opts.DebugCheck)
d.updateTableStatsLocked(ve.NewFiles)
d.deleteObsoleteFiles(jobID, false /* waitForOngoing */)
// The ingestion may have pushed a level over the threshold for compaction,

View File

@@ -33,4 +33,6 @@ type InternalKey = base.InternalKey
type internalIterator = base.InternalIterator
type internalIteratorWithStats = base.InternalIteratorWithStats
// ErrCorruption is a marker to indicate that data in a file (WAL, MANIFEST,
// sstable) isn't in the expected format.
var ErrCorruption = base.ErrCorruption

View File

@@ -34,17 +34,21 @@ func (it *flushIterator) String() string {
return "memtable"
}
func (it *flushIterator) SeekGE(key []byte, tryNextUsingSeek bool) (*base.InternalKey, []byte) {
func (it *flushIterator) SeekGE(
key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
panic("pebble: SeekGE unimplemented")
}
func (it *flushIterator) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
panic("pebble: SeekPrefixGE unimplemented")
}
func (it *flushIterator) SeekLT(key []byte) (*base.InternalKey, []byte) {
func (it *flushIterator) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*base.InternalKey, base.LazyValue) {
panic("pebble: SeekLT unimplemented")
}
@@ -52,10 +56,10 @@ func (it *flushIterator) SeekLT(key []byte) (*base.InternalKey, []byte) {
// if the iterator is pointing at a valid entry, and (nil, nil) otherwise. Note
// that First only checks the upper bound. It is up to the caller to ensure
// that key is greater than or equal to the lower bound.
func (it *flushIterator) First() (*base.InternalKey, []byte) {
func (it *flushIterator) First() (*base.InternalKey, base.LazyValue) {
key, val := it.Iterator.First()
if key == nil {
return nil, nil
return nil, base.LazyValue{}
}
*it.bytesIterated += uint64(it.nd.allocSize)
return key, val
@@ -65,16 +69,16 @@ func (it *flushIterator) First() (*base.InternalKey, []byte) {
// iterator is pointing at a valid entry, and (nil, nil) otherwise.
// Note: flushIterator.Next mirrors the implementation of Iterator.Next
// due to performance. Keep the two in sync.
func (it *flushIterator) Next() (*base.InternalKey, []byte) {
func (it *flushIterator) Next() (*base.InternalKey, base.LazyValue) {
it.nd = it.list.getNext(it.nd, 0)
if it.nd == it.list.tail {
return nil, nil
return nil, base.LazyValue{}
}
it.decodeKey()
*it.bytesIterated += uint64(it.nd.allocSize)
return &it.key, it.value()
return &it.key, base.MakeInPlaceValue(it.value())
}
func (it *flushIterator) Prev() (*base.InternalKey, []byte) {
func (it *flushIterator) Prev() (*base.InternalKey, base.LazyValue) {
panic("pebble: Prev unimplemented")
}

View File

@@ -78,11 +78,11 @@ func (it *Iterator) Error() error {
// pointing at a valid entry, and (nil, nil) otherwise. Note that SeekGE only
// checks the upper bound. It is up to the caller to ensure that key is greater
// than or equal to the lower bound.
func (it *Iterator) SeekGE(key []byte, trySeekUsingNext bool) (*base.InternalKey, []byte) {
if trySeekUsingNext {
func (it *Iterator) SeekGE(key []byte, flags base.SeekGEFlags) (*base.InternalKey, base.LazyValue) {
if flags.TrySeekUsingNext() {
if it.nd == it.list.tail {
// Iterator is done.
return nil, nil
return nil, base.LazyValue{}
}
less := it.list.cmp(it.key.UserKey, key) < 0
// Arbitrary constant. By measuring the seek cost as a function of the
@@ -94,24 +94,24 @@ func (it *Iterator) SeekGE(key []byte, trySeekUsingNext bool) (*base.InternalKey
k, _ := it.Next()
if k == nil {
// Iterator is done.
return nil, nil
return nil, base.LazyValue{}
}
less = it.list.cmp(it.key.UserKey, key) < 0
}
if !less {
return &it.key, it.value()
return &it.key, base.MakeInPlaceValue(it.value())
}
}
_, it.nd, _ = it.seekForBaseSplice(key)
if it.nd == it.list.tail {
return nil, nil
return nil, base.LazyValue{}
}
it.decodeKey()
if it.upper != nil && it.list.cmp(it.upper, it.key.UserKey) <= 0 {
it.nd = it.list.tail
return nil, nil
return nil, base.LazyValue{}
}
return &it.key, it.value()
return &it.key, base.MakeInPlaceValue(it.value())
}
// SeekPrefixGE moves the iterator to the first entry whose key is greater than
@@ -119,94 +119,94 @@ func (it *Iterator) SeekGE(key []byte, trySeekUsingNext bool) (*base.InternalKey
// provided so that an arenaskl.Iterator implements the
// internal/base.InternalIterator interface.
func (it *Iterator) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
return it.SeekGE(key, trySeekUsingNext)
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
return it.SeekGE(key, flags)
}
// SeekLT moves the iterator to the last entry whose key is less than the given
// key. Returns the key and value if the iterator is pointing at a valid entry,
// and (nil, nil) otherwise. Note that SeekLT only checks the lower bound. It
// is up to the caller to ensure that key is less than the upper bound.
func (it *Iterator) SeekLT(key []byte) (*base.InternalKey, []byte) {
func (it *Iterator) SeekLT(key []byte, flags base.SeekLTFlags) (*base.InternalKey, base.LazyValue) {
// NB: the top-level Iterator has already adjusted key based on
// the upper-bound.
it.nd, _, _ = it.seekForBaseSplice(key)
if it.nd == it.list.head {
return nil, nil
return nil, base.LazyValue{}
}
it.decodeKey()
if it.lower != nil && it.list.cmp(it.lower, it.key.UserKey) > 0 {
it.nd = it.list.head
return nil, nil
return nil, base.LazyValue{}
}
return &it.key, it.value()
return &it.key, base.MakeInPlaceValue(it.value())
}
// First seeks position at the first entry in list. Returns the key and value
// if the iterator is pointing at a valid entry, and (nil, nil) otherwise. Note
// that First only checks the upper bound. It is up to the caller to ensure
// that key is greater than or equal to the lower bound (e.g. via a call to SeekGE(lower)).
func (it *Iterator) First() (*base.InternalKey, []byte) {
func (it *Iterator) First() (*base.InternalKey, base.LazyValue) {
it.nd = it.list.getNext(it.list.head, 0)
if it.nd == it.list.tail {
return nil, nil
return nil, base.LazyValue{}
}
it.decodeKey()
if it.upper != nil && it.list.cmp(it.upper, it.key.UserKey) <= 0 {
it.nd = it.list.tail
return nil, nil
return nil, base.LazyValue{}
}
return &it.key, it.value()
return &it.key, base.MakeInPlaceValue(it.value())
}
// Last seeks position at the last entry in list. Returns the key and value if
// the iterator is pointing at a valid entry, and (nil, nil) otherwise. Note
// that Last only checks the lower bound. It is up to the caller to ensure that
// key is less than the upper bound (e.g. via a call to SeekLT(upper)).
func (it *Iterator) Last() (*base.InternalKey, []byte) {
func (it *Iterator) Last() (*base.InternalKey, base.LazyValue) {
it.nd = it.list.getPrev(it.list.tail, 0)
if it.nd == it.list.head {
return nil, nil
return nil, base.LazyValue{}
}
it.decodeKey()
if it.lower != nil && it.list.cmp(it.lower, it.key.UserKey) > 0 {
it.nd = it.list.head
return nil, nil
return nil, base.LazyValue{}
}
return &it.key, it.value()
return &it.key, base.MakeInPlaceValue(it.value())
}
// Next advances to the next position. Returns the key and value if the
// iterator is pointing at a valid entry, and (nil, nil) otherwise.
// Note: flushIterator.Next mirrors the implementation of Iterator.Next
// due to performance. Keep the two in sync.
func (it *Iterator) Next() (*base.InternalKey, []byte) {
func (it *Iterator) Next() (*base.InternalKey, base.LazyValue) {
it.nd = it.list.getNext(it.nd, 0)
if it.nd == it.list.tail {
return nil, nil
return nil, base.LazyValue{}
}
it.decodeKey()
if it.upper != nil && it.list.cmp(it.upper, it.key.UserKey) <= 0 {
it.nd = it.list.tail
return nil, nil
return nil, base.LazyValue{}
}
return &it.key, it.value()
return &it.key, base.MakeInPlaceValue(it.value())
}
// Prev moves to the previous position. Returns the key and value if the
// iterator is pointing at a valid entry, and (nil, nil) otherwise.
func (it *Iterator) Prev() (*base.InternalKey, []byte) {
func (it *Iterator) Prev() (*base.InternalKey, base.LazyValue) {
it.nd = it.list.getPrev(it.nd, 0)
if it.nd == it.list.head {
return nil, nil
return nil, base.LazyValue{}
}
it.decodeKey()
if it.lower != nil && it.list.cmp(it.lower, it.key.UserKey) > 0 {
it.nd = it.list.head
return nil, nil
return nil, base.LazyValue{}
}
return &it.key, it.value()
return &it.key, base.MakeInPlaceValue(it.value())
}
// value returns the value at the current position.

View File

@@ -66,6 +66,23 @@ type Separator func(dst, a, b []byte) []byte
// key must be valid to pass to Compare.
type Successor func(dst, a []byte) []byte
// ImmediateSuccessor is invoked with a prefix key ([Split(a) == len(a)]) and
// returns the smallest key that is larger than the given prefix a.
// ImmediateSuccessor must return a prefix key k such that:
//
// Split(k) == len(k) and Compare(k, a) > 0
//
// and there exists no representable k2 such that:
//
// Split(k2) == len(k2) and Compare(k2, a) > 0 and Compare(k2, k) < 0
//
// As an example, an implementation built on the natural byte ordering using
// bytes.Compare could append a `\0` to `a`.
//
// The dst parameter may be used to store the returned key, though it is valid
// to pass nil. The returned key must be valid to pass to Compare.
type ImmediateSuccessor func(dst, a []byte) []byte
// Split returns the length of the prefix of the user key that corresponds to
// the key portion of an MVCC encoding scheme to enable the use of prefix bloom
// filters.
@@ -104,14 +121,15 @@ type Split func(a []byte) int
// Comparer defines a total ordering over the space of []byte keys: a 'less
// than' relationship.
type Comparer struct {
Compare Compare
Equal Equal
AbbreviatedKey AbbreviatedKey
FormatKey FormatKey
FormatValue FormatValue
Separator Separator
Split Split
Successor Successor
Compare Compare
Equal Equal
AbbreviatedKey AbbreviatedKey
FormatKey FormatKey
FormatValue FormatValue
Separator Separator
Split Split
Successor Successor
ImmediateSuccessor ImmediateSuccessor
// Name is the name of the comparer.
//
@@ -193,6 +211,10 @@ var DefaultComparer = &Comparer{
return append(dst, a...)
},
ImmediateSuccessor: func(dst, a []byte) (ret []byte) {
return append(append(dst, a...), 0x00)
},
// This name is part of the C++ Level-DB implementation's default file
// format, and should not be changed.
Name: "leveldb.BytewiseComparator",

View File

@@ -71,6 +71,10 @@ const (
// seqNum.
InternalKeyKindMax InternalKeyKind = 21
// InternalKeyZeroSeqnumMaxTrailer is the largest trailer with a
// zero sequence number.
InternalKeyZeroSeqnumMaxTrailer = uint64(InternalKeyKindInvalid)
// A marker for an invalid key.
InternalKeyKindInvalid InternalKeyKind = 255
@@ -329,14 +333,28 @@ func (k InternalKey) SeqNum() uint64 {
// Visible returns true if the key is visible at the specified snapshot
// sequence number.
func (k InternalKey) Visible(snapshot uint64) bool {
return Visible(k.SeqNum(), snapshot)
func (k InternalKey) Visible(snapshot, batchSnapshot uint64) bool {
return Visible(k.SeqNum(), snapshot, batchSnapshot)
}
// Visible returns true if a key with the provided sequence number is visible at
// the specified snapshot sequence number.
func Visible(seqNum uint64, snapshot uint64) bool {
return seqNum < snapshot || (seqNum&InternalKeySeqNumBatch) != 0
// the specified snapshot sequence numbers.
func Visible(seqNum uint64, snapshot, batchSnapshot uint64) bool {
// There are two snapshot sequence numbers, one for committed keys and one
// for batch keys. If a seqNum is less than `snapshot`, then seqNum
// corresponds to a committed key that is visible. If seqNum has its batch
// bit set, then seqNum corresponds to an uncommitted batch key. Its
// visible if its snapshot is less than batchSnapshot.
//
// There's one complication. The maximal sequence number
// (`InternalKeySeqNumMax`) is used across Pebble for exclusive sentinel
// keys and other purposes. The maximal sequence number has its batch bit
// set, but it can never be < `batchSnapshot`, since there is no expressible
// larger snapshot. We dictate that the maximal sequence number is always
// visible.
return seqNum < snapshot ||
((seqNum&InternalKeySeqNumBatch) != 0 && seqNum < batchSnapshot) ||
seqNum == InternalKeySeqNumMax
}
// SetKind sets the kind component of the key.

View File

@@ -85,33 +85,19 @@ import "fmt"
// them through the Error method. All of the absolute positioning methods
// reset any accumulated error before positioning. Relative positioning
// methods return without advancing if the iterator has accumulated an error.
//
// nilv == shorthand for LazyValue{}, which represents a nil value.
type InternalIterator interface {
// SeekGE moves the iterator to the first key/value pair whose key is greater
// than or equal to the given key. Returns the key and value if the iterator
// is pointing at a valid entry, and (nil, nil) otherwise. Note that SeekGE
// is pointing at a valid entry, and (nil, nilv) otherwise. Note that SeekGE
// only checks the upper bound. It is up to the caller to ensure that key
// is greater than or equal to the lower bound.
//
// trySeekUsingNext is a performance optimization that callers can use to
// indicate that they have not done any action to move this iterator beyond
// the first key that would be found if this iterator were to honestly do
// the intended seek. For example, say the caller did a
// SeekGE(k1...), followed by SeekGE(k2...) where k1 <= k2, without any
// intermediate positioning calls. The caller can safely specify true for this
// parameter in the second call. As another example, say the caller did do one
// call to Next between the two Seek calls, and k1 < k2. Again, the caller can
// safely specify a true value for this parameter. Note that a false value is
// always safe. The callee is free to ignore the true value if its
// implementation does not permit this optimization.
// - We make the caller do this determination since a string comparison of
// k1, k2 is not necessarily cheap, and there may be many iterators in
// the iterator stack. Doing it once at the root of the iterator stack
// is cheaper.
SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte)
SeekGE(key []byte, flags SeekGEFlags) (*InternalKey, LazyValue)
// SeekPrefixGE moves the iterator to the first key/value pair whose key is
// greater than or equal to the given key. Returns the key and value if the
// iterator is pointing at a valid entry, and (nil, nil) otherwise. Note that
// iterator is pointing at a valid entry, and (nil, nilv) otherwise. Note that
// SeekPrefixGE only checks the upper bound. It is up to the caller to ensure
// that key is greater than or equal to the lower bound.
//
@@ -120,7 +106,7 @@ type InternalIterator interface {
// function must be supplied to the Comparer for the DB. The supplied prefix
// will be the prefix of the given key returned by that Split function. If
// the iterator is able to determine that no key with the prefix exists, it
// can return (nil,nil). Unlike SeekGE, this is not an indication that
// can return (nil,nilv). Unlike SeekGE, this is not an indication that
// iteration is exhausted.
//
// Note that the iterator may return keys not matching the prefix. It is up
@@ -132,78 +118,53 @@ type InternalIterator interface {
// not supporting reverse iteration in prefix iteration mode until a
// different positioning routine (SeekGE, SeekLT, First or Last) switches the
// iterator out of prefix iteration.
//
// trySeekUsingNext is a performance optimization that callers can use to
// indicate that they have not done any action to move this iterator
// beyond the first key that would be found if this iterator were to
// honestly do the indicated seek. For example, say the caller did a
// SeekPrefixGE(k1...), followed by SeekPrefixGE(k2...) where k1 <= k2,
// without any intermediate positioning calls. The caller can safely
// specify true for this parameter in the second call. As another example,
// say the caller did do one call to Next between the two SeekPrefixGE
// calls, and k1 < k2. Again, the caller can safely specify a true value
// for this parameter. Note that a false value is always safe. The callee
// is free to ignore the true value if its implementation does not permit
// this optimization.
// - We make the caller do this determination since a string comparison of
// k1, k2 is not necessarily cheap, and there may be many iterators in
// the iterator stack. Doing it once at the root of the iterator stack
// is cheaper.
// - This optimization could also be applied to SeekLT (where it would be
// trySeekUsingPrev). We currently only do it for SeekPrefixGE and SeekGE
// because this is where this optimization helps the performance of
// CockroachDB. The SeekLT cases in CockroachDB are typically accompanied
// with bounds that change between seek calls, and is optimized inside
// certain iterator implementations, like singleLevelIterator, without any
// extra parameter passing (though the same amortization of string
// comparisons could be done to improve that optimization, by making the
// root of the iterator stack do it).
SeekPrefixGE(prefix, key []byte, trySeekUsingNext bool) (*InternalKey, []byte)
SeekPrefixGE(prefix, key []byte, flags SeekGEFlags) (*InternalKey, LazyValue)
// SeekLT moves the iterator to the last key/value pair whose key is less
// than the given key. Returns the key and value if the iterator is pointing
// at a valid entry, and (nil, nil) otherwise. Note that SeekLT only checks
// at a valid entry, and (nil, nilv) otherwise. Note that SeekLT only checks
// the lower bound. It is up to the caller to ensure that key is less than
// the upper bound.
SeekLT(key []byte) (*InternalKey, []byte)
SeekLT(key []byte, flags SeekLTFlags) (*InternalKey, LazyValue)
// First moves the iterator the the first key/value pair. Returns the key and
// value if the iterator is pointing at a valid entry, and (nil, nil)
// value if the iterator is pointing at a valid entry, and (nil, nilv)
// otherwise. Note that First only checks the upper bound. It is up to the
// caller to ensure that First() is not called when there is a lower bound,
// and instead call SeekGE(lower).
First() (*InternalKey, []byte)
First() (*InternalKey, LazyValue)
// Last moves the iterator the the last key/value pair. Returns the key and
// value if the iterator is pointing at a valid entry, and (nil, nil)
// value if the iterator is pointing at a valid entry, and (nil, nilv)
// otherwise. Note that Last only checks the lower bound. It is up to the
// caller to ensure that Last() is not called when there is an upper bound,
// and instead call SeekLT(upper).
Last() (*InternalKey, []byte)
Last() (*InternalKey, LazyValue)
// Next moves the iterator to the next key/value pair. Returns the key and
// value if the iterator is pointing at a valid entry, and (nil, nil)
// value if the iterator is pointing at a valid entry, and (nil, nilv)
// otherwise. Note that Next only checks the upper bound. It is up to the
// caller to ensure that key is greater than or equal to the lower bound.
//
// It is valid to call Next when the iterator is positioned before the first
// key/value pair due to either a prior call to SeekLT or Prev which returned
// (nil, nil). It is not allowed to call Next when the previous call to SeekGE,
// SeekPrefixGE or Next returned (nil, nil).
Next() (*InternalKey, []byte)
// (nil, nilv). It is not allowed to call Next when the previous call to SeekGE,
// SeekPrefixGE or Next returned (nil, nilv).
Next() (*InternalKey, LazyValue)
// Prev moves the iterator to the previous key/value pair. Returns the key
// and value if the iterator is pointing at a valid entry, and (nil, nil)
// and value if the iterator is pointing at a valid entry, and (nil, nilv)
// otherwise. Note that Prev only checks the lower bound. It is up to the
// caller to ensure that key is less than the upper bound.
//
// It is valid to call Prev when the iterator is positioned after the last
// key/value pair due to either a prior call to SeekGE or Next which returned
// (nil, nil). It is not allowed to call Prev when the previous call to SeekLT
// or Prev returned (nil, nil).
Prev() (*InternalKey, []byte)
// (nil, nilv). It is not allowed to call Prev when the previous call to SeekLT
// or Prev returned (nil, nilv).
Prev() (*InternalKey, LazyValue)
// Error returns any accumulated error.
// Error returns any accumulated error. It may not include errors returned
// to the client when calling LazyValue.Value().
Error() error
// Close closes the iterator and returns any accumulated error. Exhausting
@@ -225,11 +186,137 @@ type InternalIterator interface {
fmt.Stringer
}
// InternalIteratorWithStats extends InternalIterator to expose stats.
type InternalIteratorWithStats interface {
InternalIterator
Stats() InternalIteratorStats
ResetStats()
// SeekGEFlags holds flags that may configure the behavior of a forward seek.
// Not all flags are relevant to all iterators.
type SeekGEFlags uint8
const (
seekGEFlagTrySeekUsingNext uint8 = iota
seekGEFlagRelativeSeek
seekGEFlagBatchJustRefreshed
)
// SeekGEFlagsNone is the default value of SeekGEFlags, with all flags disabled.
const SeekGEFlagsNone = SeekGEFlags(0)
// TrySeekUsingNext indicates whether a performance optimization was enabled
// by a caller, indicating the caller has not done any action to move this
// iterator beyond the first key that would be found if this iterator were to
// honestly do the intended seek. For example, say the caller did a
// SeekGE(k1...), followed by SeekGE(k2...) where k1 <= k2, without any
// intermediate positioning calls. The caller can safely specify true for this
// parameter in the second call. As another example, say the caller did do one
// call to Next between the two Seek calls, and k1 < k2. Again, the caller can
// safely specify a true value for this parameter. Note that a false value is
// always safe. The callee is free to ignore the true value if its
// implementation does not permit this optimization.
//
// We make the caller do this determination since a string comparison of k1, k2
// is not necessarily cheap, and there may be many iterators in the iterator
// stack. Doing it once at the root of the iterator stack is cheaper.
//
// This optimization could also be applied to SeekLT (where it would be
// trySeekUsingPrev). We currently only do it for SeekPrefixGE and SeekGE
// because this is where this optimization helps the performance of CockroachDB.
// The SeekLT cases in CockroachDB are typically accompanied with bounds that
// change between seek calls, and is optimized inside certain iterator
// implementations, like singleLevelIterator, without any extra parameter
// passing (though the same amortization of string comparisons could be done to
// improve that optimization, by making the root of the iterator stack do it).
func (s SeekGEFlags) TrySeekUsingNext() bool { return (s & (1 << seekGEFlagTrySeekUsingNext)) != 0 }
// RelativeSeek is set when in the course of a forward positioning operation, a
// higher-level iterator seeks a lower-level iterator to a larger key than the
// one at the current iterator position.
//
// Concretely, this occurs when the merging iterator observes a range deletion
// covering the key at a level's current position, and the merging iterator
// seeks the level to the range deletion's end key. During lazy-combined
// iteration, this flag signals to the level iterator that the seek is NOT an
// absolute-positioning operation from the perspective of the pebble.Iterator,
// and the level iterator must look for range keys in tables between the current
// iterator position and the new seeked position.
func (s SeekGEFlags) RelativeSeek() bool { return (s & (1 << seekGEFlagRelativeSeek)) != 0 }
// BatchJustRefreshed is set by Seek[Prefix]GE when an iterator's view of an
// indexed batch was just refreshed. It serves as a signal to the batch iterator
// to ignore the TrySeekUsingNext optimization, because the external knowledge
// imparted by the TrySeekUsingNext flag does not apply to the batch iterator's
// position. See (pebble.Iterator).batchJustRefreshed.
func (s SeekGEFlags) BatchJustRefreshed() bool { return (s & (1 << seekGEFlagBatchJustRefreshed)) != 0 }
// EnableTrySeekUsingNext returns the provided flags with the
// try-seek-using-next optimization enabled. See TrySeekUsingNext for an
// explanation of this optimization.
func (s SeekGEFlags) EnableTrySeekUsingNext() SeekGEFlags {
return s | (1 << seekGEFlagTrySeekUsingNext)
}
// DisableTrySeekUsingNext returns the provided flags with the
// try-seek-using-next optimization disabled.
func (s SeekGEFlags) DisableTrySeekUsingNext() SeekGEFlags {
return s &^ (1 << seekGEFlagTrySeekUsingNext)
}
// EnableRelativeSeek returns the provided flags with the relative-seek flag
// enabled. See RelativeSeek for an explanation of this flag's use.
func (s SeekGEFlags) EnableRelativeSeek() SeekGEFlags {
return s | (1 << seekGEFlagRelativeSeek)
}
// DisableRelativeSeek returns the provided flags with the relative-seek flag
// disabled.
func (s SeekGEFlags) DisableRelativeSeek() SeekGEFlags {
return s &^ (1 << seekGEFlagRelativeSeek)
}
// EnableBatchJustRefreshed returns the provided flags with the
// batch-just-refreshed bit set. See BatchJustRefreshed for an explanation of
// this flag.
func (s SeekGEFlags) EnableBatchJustRefreshed() SeekGEFlags {
return s | (1 << seekGEFlagBatchJustRefreshed)
}
// DisableBatchJustRefreshed returns the provided flags with the
// batch-just-refreshed bit unset.
func (s SeekGEFlags) DisableBatchJustRefreshed() SeekGEFlags {
return s &^ (1 << seekGEFlagBatchJustRefreshed)
}
// SeekLTFlags holds flags that may configure the behavior of a reverse seek.
// Not all flags are relevant to all iterators.
type SeekLTFlags uint8
const (
seekLTFlagRelativeSeek uint8 = iota
)
// SeekLTFlagsNone is the default value of SeekLTFlags, with all flags disabled.
const SeekLTFlagsNone = SeekLTFlags(0)
// RelativeSeek is set when in the course of a reverse positioning operation, a
// higher-level iterator seeks a lower-level iterator to a smaller key than the
// one at the current iterator position.
//
// Concretely, this occurs when the merging iterator observes a range deletion
// covering the key at a level's current position, and the merging iterator
// seeks the level to the range deletion's start key. During lazy-combined
// iteration, this flag signals to the level iterator that the seek is NOT an
// absolute-positioning operation from the perspective of the pebble.Iterator,
// and the level iterator must look for range keys in tables between the current
// iterator position and the new seeked position.
func (s SeekLTFlags) RelativeSeek() bool { return s&(1<<seekLTFlagRelativeSeek) != 0 }
// EnableRelativeSeek returns the provided flags with the relative-seek flag
// enabled. See RelativeSeek for an explanation of this flag's use.
func (s SeekLTFlags) EnableRelativeSeek() SeekLTFlags {
return s | (1 << seekLTFlagRelativeSeek)
}
// DisableRelativeSeek returns the provided flags with the relative-seek flag
// disabled.
func (s SeekLTFlags) DisableRelativeSeek() SeekLTFlags {
return s &^ (1 << seekLTFlagRelativeSeek)
}
// InternalIteratorStats contains miscellaneous stats produced by
@@ -238,8 +325,8 @@ type InternalIteratorWithStats interface {
// are aggregated as one goes up the InternalIterator tree.
type InternalIteratorStats struct {
// Bytes in the loaded blocks. If the block was compressed, this is the
// compressed bytes. Currently, only the second-level index and data blocks
// containing points are included.
// compressed bytes. Currently, only the index blocks, data blocks
// containing points, and filter blocks are included.
BlockBytes uint64
// Subset of BlockBytes that were in the block cache.
BlockBytesInCache uint64
@@ -271,30 +358,3 @@ func (s *InternalIteratorStats) Merge(from InternalIteratorStats) {
s.PointCount += from.PointCount
s.PointsCoveredByRangeTombstones += from.PointsCoveredByRangeTombstones
}
type internalIteratorWithEmptyStats struct {
InternalIterator
}
var _ InternalIteratorWithStats = internalIteratorWithEmptyStats{}
// Stats implements InternalIteratorWithStats.
func (i internalIteratorWithEmptyStats) Stats() InternalIteratorStats {
return InternalIteratorStats{}
}
// ResetStats implements InternalIteratorWithStats.
func (i internalIteratorWithEmptyStats) ResetStats() {}
// WrapIterWithStats ensures that either iter implements the stats methods or
// wraps it, such that the return value implements InternalIteratorWithStats.
func WrapIterWithStats(iter InternalIterator) InternalIteratorWithStats {
if iter == nil {
return nil
}
i, ok := iter.(InternalIteratorWithStats)
if ok {
return i
}
return internalIteratorWithEmptyStats{InternalIterator: iter}
}

View File

@@ -0,0 +1,256 @@
// Copyright 2022 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
package base
import "github.com/cockroachdb/pebble/internal/invariants"
// A value can have user-defined attributes that are a function of the value
// byte slice. For now, we only support "short attributes", which can be
// encoded in 3 bits. We will likely extend this to "long attributes" later
// for values that are even more expensive to access than those in value
// blocks in the same sstable.
//
// When a sstable writer chooses not to store a value together with the key,
// it can call the ShortAttributeExtractor to extract the attribute and store
// it together with the key. This allows for cheap retrieval of
// AttributeAndLen on the read-path, without doing a more expensive retrieval
// of the value.
//
// In general, the extraction code may want to also look at the key to decide
// how to treat the value. Our current needs can be satisfied by configuring a
// keyspan for which the ShortAttributeExtractor should be used, so we do not
// expose the key to the extractor.
//
// Write path performance: The ShortAttributeExtractor func cannot be inlined,
// so we will pay the cost of this function call. However, we will only pay
// this when (a) the value is not being stored together with the key, and (b)
// the key-value pair is being initially written to the DB, or a compaction is
// transitioning the key-value pair from being stored together to being stored
// separately.
// ShortAttribute encodes a user-specified attribute of the value.
type ShortAttribute uint8
// MaxShortAttribute is the maximum value of the short attribute (3 bits).
const MaxShortAttribute = 7
// ShortAttributeExtractor is an extractor that given the value, will return
// the ShortAttribute.
type ShortAttributeExtractor func(value []byte) (ShortAttribute, error)
// AttributeAndLen represents the pair of value length and the short
// attribute.
type AttributeAndLen struct {
ValueLen int32
ShortAttribute ShortAttribute
}
// LazyValue represents a value that may not already have been extracted.
// Currently, it can represent either an in-place value (stored with the key)
// or a value stored in the value section. However, the interface is general
// enough to support values that are stored in separate files.
//
// LazyValue is used in the InternalIterator interface, such that all
// positioning calls return (*InternalKey, LazyValue). It is also exposed via
// the public Iterator for callers that need to remember a recent but not
// necessarily latest LazyValue, in case they need the actual value in the
// future. An example is a caller that is iterating in reverse and looking for
// the latest MVCC version for a key -- it cannot identify the latest MVCC
// version without stepping to the previous key-value pair e.g.
// storage.pebbleMVCCScanner in CockroachDB.
//
// Performance note: It is important for this struct to not exceed a sizeof 32
// bytes, for optimizing the common case of the in-place value. Prior to
// introducing LazyValue, we were passing around a []byte which is 24 bytes.
// Passing a 40 byte or larger struct causes performance to drop by 75% on
// some benchmarks that do tight iteration loops.
//
// Memory management:
// This is subtle, but important for performance.
//
// - A LazyValue returned by an InternalIterator or Iterator is unstable in
// that repositioning the iterator will invalidate the memory inside it. A
// caller wishing to maintain that LazyValue needs to call
// LazyValue.Clone(). Note that this does not fetch the value if it is not
// in-place. Additionally, Clone() *must* only be called if
// LazyValue.Value() has *not* been called, since (a) it only makes a
// promise about stability of LazyValue and not the underlying value, (b) if
// LazyValue.Value() has been called already, the caller has the value
// []byte slice so it is unnecessary to fiddle around with LazyValue.
//
// - A user of an iterator that calls LazyValue.Value() wants as much as
// possible for the returned value []byte to point to iterator owned memory.
// - [P1] The underlying iterator that owns that memory also needs a promise
// from that user that at any time there is at most one value []byte slice
// that the caller is expecting it to maintain. Otherwise, the underlying
// iterator has to maintain multiple such []byte slices which results in
// more complicated and inefficient code.
// - [P2] The underlying iterator, in order to make the promise that it is
// maintaining the one value []byte slice, also needs a way to know when
// it is relieved of that promise. One way it is relieved of that promise
// is by being told that it is being repositioned. Typically, the owner of
// the value []byte slice is a sstable iterator, and it will know that it
// is relieved of the promise when it is repositioned. However, consider
// the case where the caller has used LazyValue.Clone() and repositioned
// the iterator (which is actually a tree of iterators). In this case the
// underlying sstable iterator may not even be open. LazyValue.Value()
// will still work (at a higher cost), but since the sstable iterator is
// not open, it does not have a mechanism to know when the retrieved value
// is no longer in use. We refer to this situation as "not satisfying P2".
// To handle this situation, the LazyValue.Value() method accepts a caller
// owned buffer, that the callee will use if needed. The callee explicitly
// tells the caller whether the []byte slice for the value is now owned by
// the caller. This will be true if the callee attempted to use buf and
// either successfully used it or allocated a new []byte slice.
//
// To ground the above in reality, we consider three examples of callers of
// LazyValue.Value():
// - Iterator: it calls LazyValue.Value for its own use when merging values.
// When merging during reverse iteration, it may have cloned the LazyValue.
// In this case it calls LazyValue.Value() on the cloned value, merges it,
// and then calls LazyValue.Value() on the current iterator position and
// merges it. So it is honoring P1.
// - Iterator on behalf of Iterator clients: The Iterator.Value() method
// needs to call LazyValue.Value(). The client of Iterator is satisfying P1
// because of the inherent Iterator interface constraint, i.e., it is calling
// Iterator.Value() on the current Iterator position. It is possible that
// the Iterator has cloned this LazyValue (for the reverse iteration case),
// which the client is unaware of, so the underlying sstable iterator may
// not be able to satisfy P2. This is ok because Iterator will call
// LazyValue.Value with its (reusable) owned buffer.
// - CockroachDB's pebbleMVCCScanner: This will use LazyValues from Iterator
// since during reverse iteration in order to find the highest version that
// satisfies a read it needs to clone the LazyValue, step back the iterator
// and then decide whether it needs the value from the previously cloned
// LazyValue. The pebbleMVCCScanner will satisfy P1. The P2 story is
// similar to the previous case in that it will call LazyValue.Value with
// its (reusable) owned buffer.
//
// Corollary: callers that directly use InternalIterator can know that they
// have done nothing to interfere with promise P2 can pass in a nil buf and be
// sure that it will not trigger an allocation.
//
// Repeated calling of LazyValue.Value:
// This is ok as long as the caller continues to satisfy P1. The previously
// fetched value will be remembered inside LazyValue to avoid fetching again.
// So if the caller's buffer is used the first time the value was fetched, it
// is still in use.
//
// LazyValue fields are visible outside the package for use in
// InternalIterator implementations and in Iterator, but not meant for direct
// use by users of Pebble.
type LazyValue struct {
// ValueOrHandle represents a value, or a handle to be passed to ValueFetcher.
// - Fetcher == nil: ValueOrHandle is a value.
// - Fetcher != nil: ValueOrHandle is a handle and Fetcher.Attribute is
// initialized.
// The ValueOrHandle exposed by InternalIterator or Iterator may not be stable
// if the iterator is stepped. To make it stable, make a copy using Clone.
ValueOrHandle []byte
// Fetcher provides support for fetching an actually lazy value.
Fetcher *LazyFetcher
}
// LazyFetcher supports fetching a lazy value.
type LazyFetcher struct {
// ValueFetcher, given a handle, returns the value. It is acceptable to call
// the ValueFetcher as long as the DB is open. However, one should assume
// there is a fast-path when the iterator tree has not moved off the sstable
// iterator that initially provided this LazyValue. Hence, to utilize this
// fast-path the caller should try to decide whether it needs the value or
// not as soon as possible, with minimal possible stepping of the iterator.
//
// buf will be used if the fetcher cannot satisfy P2 (see earlier comment).
// If the fetcher attempted to use buf *and* len(buf) was insufficient, it
// will allocate a new slice for the value. In either case it will set
// callerOwned to true.
ValueFetcher func(handle []byte, buf []byte) (val []byte, callerOwned bool, err error)
// Attribute includes the short attribute and value length.
Attribute AttributeAndLen
fetched bool
value []byte
callerOwned bool
err error
}
// Value returns the underlying value.
func (lv *LazyValue) Value(buf []byte) (val []byte, callerOwned bool, err error) {
if lv.Fetcher == nil {
return lv.ValueOrHandle, false, nil
}
// Do the rest of the work in a separate method to attempt mid-stack
// inlining of Value(). Unfortunately, this still does not inline since the
// cost of 85 exceeds the budget of 80.
//
// TODO(sumeer): Packing the return values into a struct{[]byte error bool}
// causes it to be below the budget. Consider this if we need to recover
// more performance. I suspect that inlining this only matters in
// micro-benchmarks, and in actual use cases in CockroachDB it will not
// matter because there is substantial work done with a fetched value.
return lv.fetchValue(buf)
}
// INVARIANT: lv.Fetcher != nil
func (lv *LazyValue) fetchValue(buf []byte) (val []byte, callerOwned bool, err error) {
f := lv.Fetcher
if !f.fetched {
f.fetched = true
f.value, f.callerOwned, f.err = lv.Fetcher.ValueFetcher(lv.ValueOrHandle, buf)
}
return f.value, f.callerOwned, f.err
}
// InPlaceValue returns the value under the assumption that it is in-place.
// This is for Pebble-internal code.
func (lv *LazyValue) InPlaceValue() []byte {
if invariants.Enabled && lv.Fetcher != nil {
panic("value must be in-place")
}
return lv.ValueOrHandle
}
// Len returns the length of the value.
func (lv *LazyValue) Len() int {
if lv.Fetcher == nil {
return len(lv.ValueOrHandle)
}
return int(lv.Fetcher.Attribute.ValueLen)
}
// Clone creates a stable copy of the LazyValue, by appending bytes to buf.
// REQUIRES: LazyValue.Value() has not been called.
// Ensure that this is only called before LazyValue.Value is called. We don't
// actually care if it was in-place but we don't want confusing things about whether
// we need to clone the value inside the fetcher.
func (lv *LazyValue) Clone(buf []byte) (LazyValue, []byte) {
if invariants.Enabled && lv.Fetcher != nil && lv.Fetcher.fetched {
panic("value has already been fetched")
}
// INVARIANT: LazyFetcher.value is nil, so there is nothing to copy there.
vLen := len(lv.ValueOrHandle)
lvCopy := LazyValue{Fetcher: lv.Fetcher}
if vLen == 0 {
return lvCopy, buf
}
bufLen := len(buf)
buf = append(buf, lv.ValueOrHandle...)
lvCopy.ValueOrHandle = buf[bufLen : bufLen+vLen]
return lvCopy, buf
}
// MakeInPlaceValue constructs an in-place value.
func MakeInPlaceValue(val []byte) LazyValue {
return LazyValue{ValueOrHandle: val}
}
// TODO(sumeer): test that callers are adhering to promise P1. In the sstable
// iterators, when invariants are enabled, create a testingFetcher struct that
// contains the sstable name and the []byte backing for the last fetched
// value. The sstable iterator, instead of returning an in-place value, will
// return a copy of the key in ValueOrHandle, and a ValueFetcher implemented
// by testingFetcher.valueFetcher. Each time this method is called, it will
// mangle the last fetched value []byte slice, allocate a new one, open the
// sstable, seek to the key, and copy the value into this new byte slice and
// return.

View File

@@ -29,6 +29,13 @@ func (tm *ThroughputMetric) Merge(x ThroughputMetric) {
tm.IdleDuration += x.IdleDuration
}
// Subtract subtracts the information from another ThroughputMetric
func (tm *ThroughputMetric) Subtract(x ThroughputMetric) {
tm.Bytes -= x.Bytes
tm.WorkDuration -= x.WorkDuration
tm.IdleDuration -= x.IdleDuration
}
// PeakRate returns the approximate peak rate if there was no idling.
func (tm *ThroughputMetric) PeakRate() int64 {
if tm.Bytes == 0 {
@@ -67,6 +74,12 @@ func (gsm *GaugeSampleMetric) Merge(x GaugeSampleMetric) {
gsm.count += x.count
}
// Subtract subtracts the information from another gauge metric.
func (gsm *GaugeSampleMetric) Subtract(x GaugeSampleMetric) {
gsm.sampleSum -= x.sampleSum
gsm.count -= x.count
}
// Mean returns the mean value.
func (gsm *GaugeSampleMetric) Mean() float64 {
if gsm.count == 0 {

View File

@@ -47,7 +47,31 @@ func (it *Iterator) Close() error {
// entry and false otherwise. Note that SeekGE only checks the upper bound. It
// is up to the caller to ensure that key is greater than or equal to the lower
// bound.
func (it *Iterator) SeekGE(key []byte) *base.InternalKey {
func (it *Iterator) SeekGE(key []byte, flags base.SeekGEFlags) *base.InternalKey {
if flags.TrySeekUsingNext() {
if it.nd == it.list.tail {
// Iterator is done.
return nil
}
less := it.list.cmp(it.key.UserKey, key) < 0
// Arbitrary constant. By measuring the seek cost as a function of the
// number of elements in the skip list, and fitting to a model, we
// could adjust the number of nexts based on the current size of the
// skip list.
const numNexts = 5
for i := 0; less && i < numNexts; i++ {
k := it.Next()
if k == nil {
// Iterator is done.
return nil
}
less = it.list.cmp(k.UserKey, key) < 0
}
if !less {
return &it.key
}
}
_, it.nd = it.seekForBaseSplice(key, it.list.abbreviatedKey(key))
if it.nd == it.list.tail {
return nil

View File

@@ -0,0 +1,265 @@
// Copyright 2022 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
package keyspan
import "github.com/cockroachdb/pebble/internal/base"
// TODO(jackson): Consider removing this type and adding bounds enforcement
// directly to the MergingIter. This type is probably too lightweight to warrant
// its own type, but for now we implement it separately for expediency.
// boundedIterPos records the position of the BoundedIter relative to the
// underlying iterator's position. It's used to avoid Next/Prev-ing the iterator
// if there can't possibly be another span within bounds, because the current
// span overlaps the bound.
//
// Imagine bounds [a,c) and an iterator that seeks to a span [b,d). The span
// [b,d) overlaps some portion of the iterator bounds, so the iterator must
// return it. If the iterator is subsequently Nexted, Next can tell that the
// iterator is exhausted without advancing the underlying iterator because the
// current span's end bound of d is ≥ the upper bound of c. In this case, the
// bounded iterator returns nil and records i.pos as posAtUpperLimit to remember
// that the underlying iterator position does not match the current BoundedIter
// position.
type boundedIterPos int8
const (
posAtLowerLimit boundedIterPos = -1
posAtIterSpan boundedIterPos = 0
posAtUpperLimit boundedIterPos = +1
)
// BoundedIter implements FragmentIterator and enforces bounds.
//
// Like the point InternalIterator interface, the bounded iterator's forward
// positioning routines (SeekGE, First, and Next) only check the upper bound.
// The reverse positioning routines (SeekLT, Last, and Prev) only check the
// lower bound. It is up to the caller to ensure that the forward positioning
// routines respect the lower bound and the reverse positioning routines respect
// the upper bound (i.e. calling SeekGE instead of First if there is a lower
// bound, and SeekLT instead of Last if there is an upper bound).
//
// When the hasPrefix parameter indicates that the iterator is in prefix
// iteration mode, BoundedIter elides any spans that do not overlap with the
// prefix's keyspace. In prefix iteration mode, reverse iteration is disallowed,
// except for an initial SeekLT with a seek key greater than or equal to the
// prefix. In prefix iteration mode, the first seek must position the iterator
// at or immediately before the first fragment covering a key greater than or
// equal to the prefix.
type BoundedIter struct {
iter FragmentIterator
iterSpan *Span
cmp base.Compare
split base.Split
lower []byte
upper []byte
hasPrefix *bool
prefix *[]byte
pos boundedIterPos
}
// Init initializes the bounded iterator.
//
// In addition to the iterator bounds, Init takes pointers to a boolean
// indicating whether the iterator is in prefix iteration mode and the prefix
// key if it is. This is used to exclude spans that are outside the iteration
// prefix.
func (i *BoundedIter) Init(
cmp base.Compare,
split base.Split,
iter FragmentIterator,
lower, upper []byte,
hasPrefix *bool,
prefix *[]byte,
) {
*i = BoundedIter{
iter: iter,
cmp: cmp,
split: split,
lower: lower,
upper: upper,
hasPrefix: hasPrefix,
prefix: prefix,
}
}
var _ FragmentIterator = (*BoundedIter)(nil)
// Seek calls.
//
// Seek calls check iterator bounds in the direction of the seek. Additionally,
// if the iterator is in prefix iteration mode, seek calls check both start and
// end bounds against the prefix's bounds. We check both bounds for defense in
// depth. This optimization has been a source of various bugs due to various
// other prefix iteration optimizations that can result in seek keys that don't
// respect the prefix bounds.
// SeekGE implements FragmentIterator.
func (i *BoundedIter) SeekGE(key []byte) *Span {
s := i.iter.SeekGE(key)
s = i.checkPrefixSpanStart(s)
s = i.checkPrefixSpanEnd(s)
return i.checkForwardBound(s)
}
// SeekLT implements FragmentIterator.
func (i *BoundedIter) SeekLT(key []byte) *Span {
s := i.iter.SeekLT(key)
s = i.checkPrefixSpanStart(s)
s = i.checkPrefixSpanEnd(s)
return i.checkBackwardBound(s)
}
// First implements FragmentIterator.
func (i *BoundedIter) First() *Span {
s := i.iter.First()
s = i.checkPrefixSpanStart(s)
return i.checkForwardBound(s)
}
// Last implements FragmentIterator.
func (i *BoundedIter) Last() *Span {
s := i.iter.Last()
s = i.checkPrefixSpanEnd(s)
return i.checkBackwardBound(s)
}
// Next implements FragmentIterator.
func (i *BoundedIter) Next() *Span {
switch i.pos {
case posAtLowerLimit:
// The BoundedIter had previously returned nil, because it knew from
// i.iterSpan's bounds that there was no previous span. To Next, we only
// need to return the current iter span and reset i.pos to reflect that
// we're no longer positioned at the limit.
i.pos = posAtIterSpan
return i.iterSpan
case posAtIterSpan:
// If the span at the underlying iterator position extends to or beyond the
// upper bound, we can avoid advancing because the next span is necessarily
// out of bounds.
if i.iterSpan != nil && i.upper != nil && i.cmp(i.iterSpan.End, i.upper) >= 0 {
i.pos = posAtUpperLimit
return nil
}
// Similarly, if the span extends to the next prefix and we're in prefix
// iteration mode, we can avoid advancing.
if i.iterSpan != nil && *i.hasPrefix {
ei := i.split(i.iterSpan.End)
if i.cmp(i.iterSpan.End[:ei], *i.prefix) > 0 {
i.pos = posAtUpperLimit
return nil
}
}
return i.checkForwardBound(i.checkPrefixSpanStart(i.iter.Next()))
case posAtUpperLimit:
// Already exhausted.
return nil
default:
panic("unreachable")
}
}
// Prev implements FragmentIterator.
func (i *BoundedIter) Prev() *Span {
switch i.pos {
case posAtLowerLimit:
// Already exhausted.
return nil
case posAtIterSpan:
// If the span at the underlying iterator position extends to or beyond
// the lower bound, we can avoid advancing because the previous span is
// necessarily out of bounds.
if i.iterSpan != nil && i.lower != nil && i.cmp(i.iterSpan.Start, i.lower) <= 0 {
i.pos = posAtLowerLimit
return nil
}
// Similarly, if the span extends to or beyond the current prefix and
// we're in prefix iteration mode, we can avoid advancing.
if i.iterSpan != nil && *i.hasPrefix {
si := i.split(i.iterSpan.Start)
if i.cmp(i.iterSpan.Start[:si], *i.prefix) < 0 {
i.pos = posAtLowerLimit
return nil
}
}
return i.checkBackwardBound(i.checkPrefixSpanEnd(i.iter.Prev()))
case posAtUpperLimit:
// The BoundedIter had previously returned nil, because it knew from
// i.iterSpan's bounds that there was no next span. To Prev, we only
// need to return the current iter span and reset i.pos to reflect that
// we're no longer positioned at the limit.
i.pos = posAtIterSpan
return i.iterSpan
default:
panic("unreachable")
}
}
// Error implements FragmentIterator.
func (i *BoundedIter) Error() error {
return i.iter.Error()
}
// Close implements FragmentIterator.
func (i *BoundedIter) Close() error {
return i.iter.Close()
}
// SetBounds modifies the FragmentIterator's bounds.
func (i *BoundedIter) SetBounds(lower, upper []byte) {
i.lower, i.upper = lower, upper
}
func (i *BoundedIter) checkPrefixSpanStart(span *Span) *Span {
// Compare to the prefix's bounds, if in prefix iteration mode.
if span != nil && *i.hasPrefix {
si := i.split(span.Start)
if i.cmp(span.Start[:si], *i.prefix) > 0 {
// This span starts at a prefix that sorts after our current prefix.
span = nil
}
}
return span
}
// checkForwardBound enforces the upper bound, returning nil if the provided
// span is wholly outside the upper bound. It also updates i.pos and i.iterSpan
// to reflect the new iterator position.
func (i *BoundedIter) checkForwardBound(span *Span) *Span {
// Compare to the upper bound.
if span != nil && i.upper != nil && i.cmp(span.Start, i.upper) >= 0 {
span = nil
}
i.iterSpan = span
if i.pos != posAtIterSpan {
i.pos = posAtIterSpan
}
return span
}
func (i *BoundedIter) checkPrefixSpanEnd(span *Span) *Span {
// Compare to the prefix's bounds, if in prefix iteration mode.
if span != nil && *i.hasPrefix && i.cmp(span.End, *i.prefix) <= 0 {
// This span ends before the current prefix.
span = nil
}
return span
}
// checkBackward enforces the lower bound, returning nil if the provided span is
// wholly outside the lower bound. It also updates i.pos and i.iterSpan to
// reflect the new iterator position.
func (i *BoundedIter) checkBackwardBound(span *Span) *Span {
// Compare to the lower bound.
if span != nil && i.lower != nil && i.cmp(span.End, i.lower) <= 0 {
span = nil
}
i.iterSpan = span
if i.pos != posAtIterSpan {
i.pos = posAtIterSpan
}
return span
}

View File

@@ -18,15 +18,33 @@ const bufferReuseMaxCapacity = 10 << 10 // 10 KB
// DefragmentMethod configures the defragmentation performed by the
// DefragmentingIter.
type DefragmentMethod func(base.Compare, Span, Span) bool
type DefragmentMethod interface {
// ShouldDefragment takes two abutting spans and returns whether the two
// spans should be combined into a single, defragmented Span.
ShouldDefragment(equal base.Equal, left, right *Span) bool
}
// The DefragmentMethodFunc type is an adapter to allow the use of ordinary
// functions as DefragmentMethods. If f is a function with the appropriate
// signature, DefragmentMethodFunc(f) is a DefragmentMethod that calls f.
type DefragmentMethodFunc func(equal base.Equal, left, right *Span) bool
// ShouldDefragment calls f(equal, left, right).
func (f DefragmentMethodFunc) ShouldDefragment(equal base.Equal, left, right *Span) bool {
return f(equal, left, right)
}
// DefragmentInternal configures a DefragmentingIter to defragment spans
// only if they have identical keys.
// only if they have identical keys. It requires spans' keys to be sorted in
// trailer descending order.
//
// This defragmenting method is intended for use in compactions that may see
// internal range keys fragments that may now be joined, because the state that
// required their fragmentation has been dropped.
var DefragmentInternal DefragmentMethod = func(cmp base.Compare, a, b Span) bool {
var DefragmentInternal DefragmentMethod = DefragmentMethodFunc(func(equal base.Equal, a, b *Span) bool {
if a.KeysOrder != ByTrailerDesc || b.KeysOrder != ByTrailerDesc {
panic("pebble: span keys unexpectedly not in trailer descending order")
}
if len(a.Keys) != len(b.Keys) {
return false
}
@@ -34,7 +52,7 @@ var DefragmentInternal DefragmentMethod = func(cmp base.Compare, a, b Span) bool
if a.Keys[i].Trailer != b.Keys[i].Trailer {
return false
}
if cmp(a.Keys[i].Suffix, b.Keys[i].Suffix) != 0 {
if !equal(a.Keys[i].Suffix, b.Keys[i].Suffix) {
return false
}
if !bytes.Equal(a.Keys[i].Value, b.Keys[i].Value) {
@@ -42,7 +60,7 @@ var DefragmentInternal DefragmentMethod = func(cmp base.Compare, a, b Span) bool
}
}
return true
}
})
// DefragmentReducer merges the current and next Key slices, returning a new Key
// slice.
@@ -100,9 +118,10 @@ const (
// defragments in the iteration direction, ensuring it's found a whole
// defragmented span.
type DefragmentingIter struct {
cmp base.Compare
comparer *base.Comparer
equal base.Equal
iter FragmentIterator
iterSpan Span
iterSpan *Span
iterPos iterPos
// curr holds the span at the current iterator position. currBuf is a buffer
@@ -119,10 +138,10 @@ type DefragmentingIter struct {
keysBuf []Key
keyBuf []byte
// equal is a comparison function for two spans. equal is called when two
// method is a comparison function for two spans. method is called when two
// spans are abutting to determine whether they may be defragmented.
// equal does not itself check for adjacency for the two spans.
equal DefragmentMethod
// method does not itself check for adjacency for the two spans.
method DefragmentMethod
// reduce is the reducer function used to collect Keys across all spans that
// constitute a defragmented span.
@@ -135,13 +154,14 @@ var _ FragmentIterator = (*DefragmentingIter)(nil)
// Init initializes the defragmenting iter using the provided defragment
// method.
func (i *DefragmentingIter) Init(
cmp base.Compare, iter FragmentIterator, equal DefragmentMethod, reducer DefragmentReducer,
comparer *base.Comparer, iter FragmentIterator, equal DefragmentMethod, reducer DefragmentReducer,
) {
*i = DefragmentingIter{
cmp: cmp,
iter: iter,
equal: equal,
reduce: reducer,
comparer: comparer,
equal: comparer.Equal,
iter: iter,
method: equal,
reduce: reducer,
}
}
@@ -157,16 +177,19 @@ func (i *DefragmentingIter) Close() error {
// SeekGE seeks the iterator to the first span with a start key greater than or
// equal to key and returns it.
func (i *DefragmentingIter) SeekGE(key []byte) Span {
func (i *DefragmentingIter) SeekGE(key []byte) *Span {
i.iterSpan = i.iter.SeekGE(key)
if !i.iterSpan.Valid() {
if i.iterSpan == nil {
i.iterPos = iterPosCurr
return nil
} else if i.iterSpan.Empty() {
i.iterPos = iterPosCurr
return i.iterSpan
}
// Save the current span and peek backwards.
i.saveCurrent(i.iterSpan)
i.saveCurrent()
i.iterSpan = i.iter.Prev()
if i.cmp(i.curr.Start, i.iterSpan.End) == 0 && i.checkEqual(i.iterSpan, i.curr) {
if i.iterSpan != nil && i.equal(i.curr.Start, i.iterSpan.End) && i.checkEqual(i.iterSpan, &i.curr) {
// A continuation. The span we originally landed on and defragmented
// backwards has a true Start key < key. To obey the FragmentIterator
// contract, we must not return this defragmented span. Defragment
@@ -187,8 +210,15 @@ func (i *DefragmentingIter) SeekGE(key []byte) Span {
// SeekLT seeks the iterator to the last span with a start key less than
// key and returns it.
func (i *DefragmentingIter) SeekLT(key []byte) Span {
func (i *DefragmentingIter) SeekLT(key []byte) *Span {
i.iterSpan = i.iter.SeekLT(key)
if i.iterSpan == nil {
i.iterPos = iterPosCurr
return nil
} else if i.iterSpan.Empty() {
i.iterPos = iterPosCurr
return i.iterSpan
}
// Defragment forward to find the end of the defragmented span.
i.defragmentForward()
if i.iterPos == iterPosNext {
@@ -200,19 +230,27 @@ func (i *DefragmentingIter) SeekLT(key []byte) Span {
}
// First seeks the iterator to the first span and returns it.
func (i *DefragmentingIter) First() Span {
func (i *DefragmentingIter) First() *Span {
i.iterSpan = i.iter.First()
if i.iterSpan == nil {
i.iterPos = iterPosCurr
return nil
}
return i.defragmentForward()
}
// Last seeks the iterator to the last span and returns it.
func (i *DefragmentingIter) Last() Span {
func (i *DefragmentingIter) Last() *Span {
i.iterSpan = i.iter.Last()
if i.iterSpan == nil {
i.iterPos = iterPosCurr
return nil
}
return i.defragmentBackward()
}
// Next advances to the next span and returns it.
func (i *DefragmentingIter) Next() Span {
func (i *DefragmentingIter) Next() *Span {
switch i.iterPos {
case iterPosPrev:
// Switching directions; The iterator is currently positioned over the
@@ -228,7 +266,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 !i.iterSpan.Valid() {
if invariants.Enabled && i.iterSpan == nil {
panic("pebble: invariant violation: no next span while switching directions")
}
// We're now positioned on the first span that was defragmented into the
@@ -236,6 +274,10 @@ func (i *DefragmentingIter) Next() Span {
// position's constitutent fragments. In the above example, this would
// land on the first 'z'.
i.defragmentForward()
if i.iterSpan == nil {
i.iterPos = iterPosCurr
return nil
}
// Now that we're positioned over the first of the next set of
// fragments, defragment forward.
@@ -243,14 +285,21 @@ func (i *DefragmentingIter) Next() Span {
case iterPosCurr:
// iterPosCurr is only used when the iter is exhausted or when the iterator
// is at an empty span.
if invariants.Enabled && i.iterSpan.Valid() && !i.iterSpan.Empty() {
if invariants.Enabled && i.iterSpan != nil && !i.iterSpan.Empty() {
panic("pebble: invariant violation: iterPosCurr with valid iterSpan")
}
i.iterSpan = i.iter.Next()
if i.iterSpan == nil {
return nil
}
return i.defragmentForward()
case iterPosNext:
// Already at the next span.
if i.iterSpan == nil {
i.iterPos = iterPosCurr
return nil
}
return i.defragmentForward()
default:
panic("unreachable")
@@ -258,19 +307,26 @@ func (i *DefragmentingIter) Next() Span {
}
// Prev steps back to the previous span and returns it.
func (i *DefragmentingIter) Prev() Span {
func (i *DefragmentingIter) Prev() *Span {
switch i.iterPos {
case iterPosPrev:
// Already at the previous span.
if i.iterSpan == nil {
i.iterPos = iterPosCurr
return nil
}
return i.defragmentBackward()
case iterPosCurr:
// iterPosCurr is only used when the iter is exhausted or when the iterator
// is at an empty span.
if invariants.Enabled && i.iterSpan.Valid() && !i.iterSpan.Empty() {
if invariants.Enabled && i.iterSpan != nil && !i.iterSpan.Empty() {
panic("pebble: invariant violation: iterPosCurr with valid iterSpan")
}
i.iterSpan = i.iter.Prev()
if i.iterSpan == nil {
return nil
}
return i.defragmentBackward()
case iterPosNext:
// Switching directions; The iterator is currently positioned over the
@@ -286,7 +342,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 !i.iterSpan.Valid() {
if invariants.Enabled && i.iterSpan == nil {
panic("pebble: invariant violation: no previous span while switching directions")
}
// We're now positioned on the last span that was defragmented into the
@@ -297,6 +353,10 @@ func (i *DefragmentingIter) Prev() Span {
// Now that we're positioned over the last of the prev set of
// fragments, defragment backward.
if i.iterSpan == nil {
i.iterPos = iterPosCurr
return nil
}
return i.defragmentBackward()
default:
panic("unreachable")
@@ -306,17 +366,14 @@ func (i *DefragmentingIter) Prev() Span {
// checkEqual checks the two spans for logical equivalence. It uses the passed-in
// DefragmentMethod and ensures both spans are NOT empty; not defragmenting empty
// spans is an optimization that lets us load fewer sstable blocks.
func (i *DefragmentingIter) checkEqual(left, right Span) bool {
return i.equal(i.cmp, i.iterSpan, i.curr) && !(left.Empty() && right.Empty())
func (i *DefragmentingIter) checkEqual(left, right *Span) bool {
return (!left.Empty() && !right.Empty()) && i.method.ShouldDefragment(i.equal, i.iterSpan, &i.curr)
}
// defragmentForward defragments spans in the forward direction, starting from
// i.iter's current position.
func (i *DefragmentingIter) defragmentForward() Span {
if !i.iterSpan.Valid() {
i.iterPos = iterPosCurr
return i.iterSpan
}
// i.iter's current position. The span at the current position must be non-nil,
// but may be Empty().
func (i *DefragmentingIter) defragmentForward() *Span {
if i.iterSpan.Empty() {
// An empty span will never be equal to another span; see checkEqual for
// why. To avoid loading non-empty range keys further ahead by calling Next,
@@ -324,16 +381,16 @@ func (i *DefragmentingIter) defragmentForward() Span {
i.iterPos = iterPosCurr
return i.iterSpan
}
i.saveCurrent(i.iterSpan)
i.saveCurrent()
i.iterPos = iterPosNext
i.iterSpan = i.iter.Next()
for i.iterSpan.Valid() {
if i.cmp(i.curr.End, i.iterSpan.Start) != 0 {
for i.iterSpan != nil {
if !i.equal(i.curr.End, i.iterSpan.Start) {
// Not a continuation.
break
}
if !i.checkEqual(i.iterSpan, i.curr) {
if !i.checkEqual(i.iterSpan, &i.curr) {
// Not a continuation.
break
}
@@ -343,16 +400,13 @@ func (i *DefragmentingIter) defragmentForward() Span {
i.iterSpan = i.iter.Next()
}
i.curr.Keys = i.keysBuf
return i.curr
return &i.curr
}
// defragmentBackward defragments spans in the backward direction, starting from
// i.iter's current position.
func (i *DefragmentingIter) defragmentBackward() Span {
if !i.iterSpan.Valid() {
i.iterPos = iterPosCurr
return i.iterSpan
}
// i.iter's current position. The span at the current position must be non-nil,
// but may be Empty().
func (i *DefragmentingIter) defragmentBackward() *Span {
if i.iterSpan.Empty() {
// An empty span will never be equal to another span; see checkEqual for
// why. To avoid loading non-empty range keys further ahead by calling Next,
@@ -360,16 +414,16 @@ func (i *DefragmentingIter) defragmentBackward() Span {
i.iterPos = iterPosCurr
return i.iterSpan
}
i.saveCurrent(i.iterSpan)
i.saveCurrent()
i.iterPos = iterPosPrev
i.iterSpan = i.iter.Prev()
for i.iterSpan.Valid() {
if i.cmp(i.curr.Start, i.iterSpan.End) != 0 {
for i.iterSpan != nil {
if !i.equal(i.curr.Start, i.iterSpan.End) {
// Not a continuation.
break
}
if !i.checkEqual(i.iterSpan, i.curr) {
if !i.checkEqual(i.iterSpan, &i.curr) {
// Not a continuation.
break
}
@@ -379,10 +433,10 @@ func (i *DefragmentingIter) defragmentBackward() Span {
i.iterSpan = i.iter.Prev()
}
i.curr.Keys = i.keysBuf
return i.curr
return &i.curr
}
func (i *DefragmentingIter) saveCurrent(span Span) {
func (i *DefragmentingIter) saveCurrent() {
i.currBuf = i.currBuf[:0]
i.keysBuf = i.keysBuf[:0]
i.keyBuf = i.keyBuf[:0]
@@ -392,15 +446,19 @@ func (i *DefragmentingIter) saveCurrent(span Span) {
if cap(i.keyBuf) > bufferReuseMaxCapacity {
i.keyBuf = nil
}
i.curr = Span{
Start: i.saveBytes(span.Start),
End: i.saveBytes(span.End),
if i.iterSpan == nil {
return
}
for j := range span.Keys {
i.curr = Span{
Start: i.saveBytes(i.iterSpan.Start),
End: i.saveBytes(i.iterSpan.End),
KeysOrder: i.iterSpan.KeysOrder,
}
for j := range i.iterSpan.Keys {
i.keysBuf = append(i.keysBuf, Key{
Trailer: span.Keys[j].Trailer,
Suffix: i.saveBytes(span.Keys[j].Suffix),
Value: i.saveBytes(span.Keys[j].Value),
Trailer: i.iterSpan.Keys[j].Trailer,
Suffix: i.saveBytes(i.iterSpan.Keys[j].Suffix),
Value: i.saveBytes(i.iterSpan.Keys[j].Value),
})
}
i.curr.Keys = i.keysBuf

View File

@@ -10,7 +10,7 @@ package keyspan
// the output Span, for example, to elice certain keys, or update the Span's
// bounds if so desired. The output Span's Keys slice may be reused to reduce
// allocations.
type FilterFunc func(in Span, out *Span) (keep bool)
type FilterFunc func(in *Span, out *Span) (keep bool)
// filteringIter is a FragmentIterator that uses a FilterFunc to select which
// Spans from the input iterator are returned in the output.
@@ -37,32 +37,32 @@ func Filter(iter FragmentIterator, filter FilterFunc) FragmentIterator {
}
// SeekGE implements FragmentIterator.
func (i *filteringIter) SeekGE(key []byte) Span {
func (i *filteringIter) SeekGE(key []byte) *Span {
return i.filter(i.iter.SeekGE(key), +1)
}
// SeekLT implements FragmentIterator.
func (i *filteringIter) SeekLT(key []byte) Span {
func (i *filteringIter) SeekLT(key []byte) *Span {
return i.filter(i.iter.SeekLT(key), -1)
}
// First implements FragmentIterator.
func (i *filteringIter) First() Span {
func (i *filteringIter) First() *Span {
return i.filter(i.iter.First(), +1)
}
// Last implements FragmentIterator.
func (i *filteringIter) Last() Span {
func (i *filteringIter) Last() *Span {
return i.filter(i.iter.Last(), -1)
}
// Next implements FragmentIterator.
func (i *filteringIter) Next() Span {
func (i *filteringIter) Next() *Span {
return i.filter(i.iter.Next(), +1)
}
// Prev implements FragmentIterator.
func (i *filteringIter) Prev() Span {
func (i *filteringIter) Prev() *Span {
return i.filter(i.iter.Prev(), -1)
}
@@ -80,18 +80,18 @@ func (i *filteringIter) Close() error {
// given Span. If the current Span is to be skipped, the iterator continues
// iterating in the given direction until it lands on a Span that should be
// returned, or the iterator becomes invalid.
func (i *filteringIter) filter(span Span, dir int8) Span {
func (i *filteringIter) filter(span *Span, dir int8) *Span {
if i.filterFn == nil {
return span
}
for i.Error() == nil && span.Valid() {
for i.Error() == nil && span != nil {
if keep := i.filterFn(span, &i.span); keep {
return i.span
return &i.span
}
if dir == +1 {
span = i.Next()
span = i.iter.Next()
} else {
span = i.Prev()
span = i.iter.Prev()
}
}
return span

View File

@@ -183,9 +183,13 @@ func (f *Fragmenter) checkInvariants(buf []Span) {
// deletion and range key blocks, so these keys are stable. Because of this key
// stability, typically callers only need to perform a shallow clone of the Span
// before Add-ing it to the fragmenter.
//
// Add requires the provided span's keys are sorted in Trailer descending order.
func (f *Fragmenter) Add(s Span) {
if f.finished {
panic("pebble: span fragmenter already finished")
} else if s.KeysOrder != ByTrailerDesc {
panic("pebble: span keys unexpectedly not in trailer descending order")
}
if f.flushedKey != nil {
switch c := f.Cmp(s.Start, f.flushedKey); {
@@ -248,7 +252,7 @@ func (f *Fragmenter) Covers(key base.InternalKey, snapshot uint64) bool {
// NB: A range deletion tombstone does not delete a point operation
// at the same sequence number, and broadly a span is not considered
// to cover a point operation at the same sequence number.
if s.Visible(snapshot).Covers(seqNum) {
if s.CoversAt(snapshot, seqNum) {
return true
}
}

View File

@@ -11,7 +11,7 @@ import "github.com/cockroachdb/pebble/internal/base"
// parameter controls the visibility of spans (only spans older than the
// snapshot sequence number are visible). The iterator must contain
// fragmented spans: no span may overlap another.
func Get(cmp base.Compare, iter FragmentIterator, key []byte, snapshot uint64) Span {
func Get(cmp base.Compare, iter FragmentIterator, key []byte) *Span {
// NB: We use SeekLT in order to land on the proper span for a search
// key that resides in the middle of a span. Consider the scenario:
//
@@ -25,15 +25,15 @@ func Get(cmp base.Compare, iter FragmentIterator, key []byte, snapshot uint64) S
// happens for the search key `e`. In that case SeekLT will land us
// on the span [a,e) and we'll have to move forward.
iterSpan := iter.SeekLT(key)
if !iterSpan.Valid() {
if iterSpan == nil {
iterSpan = iter.Next()
if !iterSpan.Valid() {
if iterSpan == nil {
// The iterator is empty.
return Span{}
return nil
}
if cmp(key, iterSpan.Start) < 0 {
// The search key lies before the first span.
return Span{}
return nil
}
}
@@ -43,11 +43,11 @@ func Get(cmp base.Compare, iter FragmentIterator, key []byte, snapshot uint64) S
// once to potentially land on a key with a start key exactly equal to
// key. (See the comment at the beginning of this function.)
iterSpan = iter.Next()
if !iterSpan.Valid() || cmp(key, iterSpan.Start) < 0 {
if iterSpan == nil || cmp(key, iterSpan.Start) < 0 {
// We've run out of spans or we've moved on to a span which
// starts after our search key.
return Span{}
return nil
}
}
return iterSpan.Visible(snapshot)
return iterSpan
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ import "github.com/cockroachdb/pebble/internal/base"
// keyspan.FragmentIterator with point keys.
type InternalIteratorShim struct {
miter MergingIter
span Span
span *Span
iterKey base.InternalKey
}
@@ -31,62 +31,64 @@ func (i *InternalIteratorShim) Init(cmp base.Compare, iters ...FragmentIterator)
// Span returns the span containing the full set of keys over the key span at
// the current iterator position.
func (i *InternalIteratorShim) Span() Span {
func (i *InternalIteratorShim) Span() *Span {
return i.span
}
// SeekGE implements (base.InternalIterator).SeekGE.
func (i *InternalIteratorShim) SeekGE(
key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
// SeekPrefixGE implements (base.InternalIterator).SeekPrefixGE.
func (i *InternalIteratorShim) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
// SeekLT implements (base.InternalIterator).SeekLT.
func (i *InternalIteratorShim) SeekLT(key []byte) (*base.InternalKey, []byte) {
func (i *InternalIteratorShim) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
// First implements (base.InternalIterator).First.
func (i *InternalIteratorShim) First() (*base.InternalKey, []byte) {
func (i *InternalIteratorShim) First() (*base.InternalKey, base.LazyValue) {
i.span = i.miter.First()
for i.span.Valid() && i.span.Empty() {
for i.span != nil && i.span.Empty() {
i.span = i.miter.Next()
}
if !i.span.Valid() {
return nil, nil
if i.span == nil {
return nil, base.LazyValue{}
}
i.iterKey = base.InternalKey{UserKey: i.span.Start, Trailer: i.span.Keys[0].Trailer}
return &i.iterKey, i.span.End
return &i.iterKey, base.MakeInPlaceValue(i.span.End)
}
// Last implements (base.InternalIterator).Last.
func (i *InternalIteratorShim) Last() (*base.InternalKey, []byte) {
func (i *InternalIteratorShim) Last() (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
// Next implements (base.InternalIterator).Next.
func (i *InternalIteratorShim) Next() (*base.InternalKey, []byte) {
func (i *InternalIteratorShim) Next() (*base.InternalKey, base.LazyValue) {
i.span = i.miter.Next()
for i.span.Valid() && i.span.Empty() {
for i.span != nil && i.span.Empty() {
i.span = i.miter.Next()
}
if !i.span.Valid() {
return nil, nil
if i.span == nil {
return nil, base.LazyValue{}
}
i.iterKey = base.InternalKey{UserKey: i.span.Start, Trailer: i.span.Keys[0].Trailer}
return &i.iterKey, i.span.End
return &i.iterKey, base.MakeInPlaceValue(i.span.End)
}
// Prev implements (base.InternalIterator).Prev.
func (i *InternalIteratorShim) Prev() (*base.InternalKey, []byte) {
func (i *InternalIteratorShim) Prev() (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}

View File

@@ -20,17 +20,17 @@ import (
type FragmentIterator interface {
// SeekGE moves the iterator to the first span whose start key is greater
// than or equal to the given key.
SeekGE(key []byte) Span
SeekGE(key []byte) *Span
// SeekLT moves the iterator to the last span whose start key is less than
// the given key.
SeekLT(key []byte) Span
SeekLT(key []byte) *Span
// First moves the iterator to the first span.
First() Span
First() *Span
// Last moves the iterator to the last span.
Last() Span
Last() *Span
// Next moves the iterator to the next span.
//
@@ -38,7 +38,7 @@ type FragmentIterator interface {
// key/value pair due to either a prior call to SeekLT or Prev which
// returned an invalid span. It is not allowed to call Next when the
// previous call to SeekGE, SeekPrefixGE or Next returned an invalid span.
Next() Span
Next() *Span
// Prev moves the iterator to the previous span.
//
@@ -46,7 +46,7 @@ type FragmentIterator interface {
// key/value pair due to either a prior call to SeekGE or Next which
// returned an invalid span. It is not allowed to call Prev when the
// previous call to SeekLT or Prev returned an invalid span.
Prev() Span
Prev() *Span
// Error returns any accumulated error.
Error() error
@@ -81,7 +81,19 @@ var _ FragmentIterator = (*Iter)(nil)
// NewIter returns a new iterator over a set of fragmented spans.
func NewIter(cmp base.Compare, spans []Span) *Iter {
return &Iter{
i := &Iter{}
i.Init(cmp, spans)
return i
}
// Count returns the number of spans contained by Iter.
func (i *Iter) Count() int {
return len(i.spans)
}
// Init initializes an Iter with the provided spans.
func (i *Iter) Init(cmp base.Compare, spans []Span) {
*i = Iter{
cmp: cmp,
spans: spans,
index: -1,
@@ -89,7 +101,7 @@ func NewIter(cmp base.Compare, spans []Span) *Iter {
}
// SeekGE implements FragmentIterator.SeekGE.
func (i *Iter) SeekGE(key []byte) Span {
func (i *Iter) SeekGE(key []byte) *Span {
// NB: manually inlined sort.Search is ~5% faster.
//
// Define f(-1) == false and f(n) == true.
@@ -108,13 +120,13 @@ func (i *Iter) SeekGE(key []byte) Span {
// i.index == upper, f(i.index-1) == false, and f(upper) (= f(i.index)) ==
// true => answer is i.index.
if i.index >= len(i.spans) {
return Span{}
return nil
}
return i.spans[i.index]
return &i.spans[i.index]
}
// SeekLT implements FragmentIterator.SeekLT.
func (i *Iter) SeekLT(key []byte) Span {
func (i *Iter) SeekLT(key []byte) *Span {
// NB: manually inlined sort.Search is ~5% faster.
//
// Define f(-1) == false and f(n) == true.
@@ -137,51 +149,51 @@ func (i *Iter) SeekLT(key []byte) Span {
// the largest whose key is < the key sought.
i.index--
if i.index < 0 {
return Span{}
return nil
}
return i.spans[i.index]
return &i.spans[i.index]
}
// First implements FragmentIterator.First.
func (i *Iter) First() Span {
func (i *Iter) First() *Span {
if len(i.spans) == 0 {
return Span{}
return nil
}
i.index = 0
return i.spans[i.index]
return &i.spans[i.index]
}
// Last implements FragmentIterator.Last.
func (i *Iter) Last() Span {
func (i *Iter) Last() *Span {
if len(i.spans) == 0 {
return Span{}
return nil
}
i.index = len(i.spans) - 1
return i.spans[i.index]
return &i.spans[i.index]
}
// Next implements FragmentIterator.Next.
func (i *Iter) Next() Span {
func (i *Iter) Next() *Span {
if i.index >= len(i.spans) {
return Span{}
return nil
}
i.index++
if i.index >= len(i.spans) {
return Span{}
return nil
}
return i.spans[i.index]
return &i.spans[i.index]
}
// Prev implements FragmentIterator.Prev.
func (i *Iter) Prev() Span {
func (i *Iter) Prev() *Span {
if i.index < 0 {
return Span{}
return nil
}
i.index--
if i.index < 0 {
return Span{}
return nil
}
return i.spans[i.index]
return &i.spans[i.index]
}
// Error implements FragmentIterator.Error.

View File

@@ -40,8 +40,8 @@ type LevelIter struct {
// key spaces. This is an optimization to avoid unnecessarily loading files
// in cases where range keys are sparse and rare. dir is set by every
// positioning operation, straddleDir is set to dir whenever a straddling
// Span is synthesized, and straddle is Valid() whenever the last positioning
// operation returned a synthesized straddle span.
// Span is synthesized and the last positioning operation returned a
// synthesized straddle span.
//
// Note that when a straddle span is initialized, iterFile is modified to
// point to the next file in the straddleDir direction. A change of direction
@@ -53,7 +53,7 @@ type LevelIter struct {
// The iter for the current file. It is nil under any of the following conditions:
// - files.Current() == nil
// - err != nil
// - straddle.Valid(), in which case iterFile is not nil and points to the
// - straddleDir != 0, in which case iterFile is not nil and points to the
// next file (in the straddleDir direction).
// - some other constraint, like the bounds in opts, caused the file at index to not
// be relevant to the iteration.
@@ -178,7 +178,7 @@ func (l *LevelIter) loadFile(file *manifest.FileMetadata, dir int) loadFileRetur
return noFileLoaded
}
if indicator != fileAlreadyLoaded {
l.iter, l.err = l.newIter(l.files.Current(), &l.tableOpts)
l.iter, l.err = l.newIter(file, &l.tableOpts)
indicator = newFileLoaded
}
if l.err != nil {
@@ -188,123 +188,149 @@ func (l *LevelIter) loadFile(file *manifest.FileMetadata, dir int) loadFileRetur
}
// SeekGE implements keyspan.FragmentIterator.
func (l *LevelIter) SeekGE(key []byte) Span {
func (l *LevelIter) SeekGE(key []byte) *Span {
l.dir = +1
l.straddle = Span{}
l.straddleDir = 0
l.err = nil // clear cached iteration error
f := l.findFileGE(key)
if f != nil && l.keyType == manifest.KeyTypeRange && l.cmp(key, f.SmallestRangeKey.UserKey) < 0 {
// Return a straddling key instead of loading the file.
l.iterFile = f
l.iter = nil
l.straddleDir = +1
// The synthetic span that we are creating starts at the seeked key. This
// is an optimization as it prevents us from loading the adjacent file's
// bounds, at the expense of this iterator appearing "inconsistent" to its
// callers i.e.:
//
// SeekGE(bb) -> {bb-c, empty}
// Next() -> {c-d, RANGEKEYSET}
// Prev() -> {a-c, empty}
//
// Seeing as the inconsistency will only be around empty spans, which are
// expected to be elided by one of the higher-level iterators (either
// top-level Iterator or the defragmenting iter), the entire iterator should
// still appear consistent to the user.
l.straddle = Span{
Start: key,
End: f.SmallestRangeKey.UserKey,
Keys: nil,
prevFile := l.files.Prev()
if prevFile != nil {
// We could unconditionally return an empty span between the seek key and
// f.SmallestRangeKey, however if this span is to the left of all range
// keys on this level, it could lead to inconsistent behaviour in relative
// positioning operations. Consider this example, with a b-c range key:
//
// SeekGE(a) -> a-b:{}
// Next() -> b-c{(#5,RANGEKEYSET,@4,foo)}
// Prev() -> nil
//
// Iterators higher up in the iterator stack rely on this sort of relative
// positioning consistency.
//
// TODO(bilal): Investigate ways to be able to return straddle spans in
// cases similar to the above, while still retaining correctness.
l.files.Next()
// Return a straddling key instead of loading the file.
l.iterFile = f
if err := l.Close(); err != nil {
return nil
}
l.straddleDir = +1
l.straddle = Span{
Start: prevFile.LargestRangeKey.UserKey,
End: f.SmallestRangeKey.UserKey,
Keys: nil,
}
return &l.straddle
}
return l.straddle
}
loadFileIndicator := l.loadFile(f, +1)
if loadFileIndicator == noFileLoaded {
return Span{}
return nil
}
if span := l.iter.SeekGE(key); span.Valid() {
if span := l.iter.SeekGE(key); span != nil {
return span
}
return l.skipEmptyFileForward()
}
// SeekLT implements keyspan.FragmentIterator.
func (l *LevelIter) SeekLT(key []byte) Span {
func (l *LevelIter) SeekLT(key []byte) *Span {
l.dir = -1
l.straddle = Span{}
l.straddleDir = 0
l.err = nil // clear cached iteration error
f := l.findFileLT(key)
if f != nil && l.keyType == manifest.KeyTypeRange && l.cmp(f.LargestRangeKey.UserKey, key) < 0 {
// Return a straddling key instead of loading the file.
l.iterFile = f
l.iter = nil
l.straddleDir = -1
// The synthetic span that we are creating ends at the seeked key. This
// is an optimization as it prevents us from loading the adjacent file's
// bounds, at the expense of this iterator appearing "inconsistent" to its
// callers i.e.:
//
// SeekLT(dd) -> {d-dd, empty}
// Prev() -> {c-d, RANGEKEYSET}
// Next() -> {d-e, empty}
//
// Seeing as the inconsistency will only be around empty spans, which are
// expected to be elided by one of the higher-level iterators (either
// top-level Iterator or the defragmenting iter), the entire iterator should
// still appear consistent to the user.
l.straddle = Span{
Start: f.LargestRangeKey.UserKey,
End: key,
Keys: nil,
nextFile := l.files.Next()
if nextFile != nil {
// We could unconditionally return an empty span between f.LargestRangeKey
// and the seek key, however if this span is to the right of all range keys
// on this level, it could lead to inconsistent behaviour in relative
// positioning operations. Consider this example, with a b-c range key:
//
// SeekLT(d) -> c-d:{}
// Prev() -> b-c{(#5,RANGEKEYSET,@4,foo)}
// Next() -> nil
//
// Iterators higher up in the iterator stack rely on this sort of relative
// positioning consistency.
//
// TODO(bilal): Investigate ways to be able to return straddle spans in
// cases similar to the above, while still retaining correctness.
l.files.Prev()
// Return a straddling key instead of loading the file.
l.iterFile = f
if err := l.Close(); err != nil {
return nil
}
l.straddleDir = -1
l.straddle = Span{
Start: f.LargestRangeKey.UserKey,
End: nextFile.SmallestRangeKey.UserKey,
Keys: nil,
}
return &l.straddle
}
return l.straddle
}
if l.loadFile(l.findFileLT(key), -1) == noFileLoaded {
return Span{}
return nil
}
if span := l.iter.SeekLT(key); span.Valid() {
if span := l.iter.SeekLT(key); span != nil {
return span
}
return l.skipEmptyFileBackward()
}
// First implements keyspan.FragmentIterator.
func (l *LevelIter) First() Span {
func (l *LevelIter) First() *Span {
l.dir = +1
l.straddle = Span{}
l.straddleDir = 0
l.err = nil // clear cached iteration error
if l.loadFile(l.files.First(), +1) == noFileLoaded {
return Span{}
return nil
}
if span := l.iter.First(); span.Valid() {
if span := l.iter.First(); span != nil {
return span
}
return l.skipEmptyFileForward()
}
// Last implements keyspan.FragmentIterator.
func (l *LevelIter) Last() Span {
func (l *LevelIter) Last() *Span {
l.dir = -1
l.straddle = Span{}
l.straddleDir = 0
l.err = nil // clear cached iteration error
if l.loadFile(l.files.Last(), -1) == noFileLoaded {
return Span{}
return nil
}
if span := l.iter.Last(); span.Valid() {
if span := l.iter.Last(); span != nil {
return span
}
return l.skipEmptyFileBackward()
}
// Next implements keyspan.FragmentIterator.
func (l *LevelIter) Next() Span {
l.dir = +1
if l.err != nil || (l.iter == nil && l.iterFile == nil) {
return Span{}
func (l *LevelIter) Next() *Span {
if l.err != nil || (l.iter == nil && l.iterFile == nil && l.dir > 0) {
return nil
}
if l.iter == nil && l.iterFile == nil {
// l.dir <= 0
return l.First()
}
l.dir = +1
if l.iter != nil {
if span := l.iter.Next(); span.Valid() {
if span := l.iter.Next(); span != nil {
return span
}
}
@@ -312,22 +338,26 @@ func (l *LevelIter) Next() Span {
}
// Prev implements keyspan.FragmentIterator.
func (l *LevelIter) Prev() Span {
l.dir = -1
if l.err != nil || (l.iter == nil && l.iterFile == nil) {
return Span{}
func (l *LevelIter) Prev() *Span {
if l.err != nil || (l.iter == nil && l.iterFile == nil && l.dir < 0) {
return nil
}
if l.iter == nil && l.iterFile == nil {
// l.dir >= 0
return l.Last()
}
l.dir = -1
if l.iter != nil {
if span := l.iter.Prev(); span.Valid() {
if span := l.iter.Prev(); span != nil {
return span
}
}
return l.skipEmptyFileBackward()
}
func (l *LevelIter) skipEmptyFileForward() Span {
if !l.straddle.Valid() && l.keyType == manifest.KeyTypeRange &&
func (l *LevelIter) skipEmptyFileForward() *Span {
if l.straddleDir == 0 && l.keyType == manifest.KeyTypeRange &&
l.iterFile != nil && l.iter != nil {
// We were at a file that had spans. Check if the next file that has
// spans is not directly adjacent to the current file i.e. there is a
@@ -337,7 +367,7 @@ func (l *LevelIter) skipEmptyFileForward() Span {
// Straddle spans are not created in rangedel mode.
if err := l.Close(); err != nil {
l.err = err
return Span{}
return nil
}
startKey := l.iterFile.LargestRangeKey.UserKey
// Resetting l.iterFile without loading the file into l.iter is okay and
@@ -345,7 +375,7 @@ func (l *LevelIter) skipEmptyFileForward() Span {
// which it should be due to the Close() call above.
l.iterFile = l.files.Next()
if l.iterFile == nil {
return Span{}
return nil
}
endKey := l.iterFile.SmallestRangeKey.UserKey
if l.cmp(startKey, endKey) < 0 {
@@ -355,17 +385,18 @@ func (l *LevelIter) skipEmptyFileForward() Span {
Start: startKey,
End: endKey,
}
l.straddleDir = l.dir
return l.straddle
l.straddleDir = +1
return &l.straddle
}
} else if l.straddle.Valid() && l.straddleDir < 0 {
} else if l.straddleDir < 0 {
// We were at a straddle key, but are now changing directions. l.iterFile
// was already moved backward by skipEmptyFileBackward, so advance it
// forward.
l.iterFile = l.files.Next()
}
l.straddle = Span{}
var span Span
l.straddleDir = 0
var span *Span
for span.Empty() {
fileToLoad := l.iterFile
if l.keyType == manifest.KeyTypePoint {
@@ -374,7 +405,7 @@ func (l *LevelIter) skipEmptyFileForward() Span {
fileToLoad = l.files.Next()
}
if l.loadFile(fileToLoad, +1) == noFileLoaded {
return Span{}
return nil
}
span = l.iter.First()
// In rangedel mode, we can expect to get empty files that we'd need to
@@ -386,18 +417,18 @@ func (l *LevelIter) skipEmptyFileForward() Span {
return span
}
func (l *LevelIter) skipEmptyFileBackward() Span {
func (l *LevelIter) skipEmptyFileBackward() *Span {
// We were at a file that had spans. Check if the previous file that has
// spans is not directly adjacent to the current file i.e. there is a
// gap in the span keyspace between the two files. In that case, synthesize
// a "straddle span" in l.straddle and return that.
//
// Straddle spans are not created in rangedel mode.
if !l.straddle.Valid() && l.keyType == manifest.KeyTypeRange &&
if l.straddleDir == 0 && l.keyType == manifest.KeyTypeRange &&
l.iterFile != nil && l.iter != nil {
if err := l.Close(); err != nil {
l.err = err
return Span{}
return nil
}
endKey := l.iterFile.SmallestRangeKey.UserKey
// Resetting l.iterFile without loading the file into l.iter is okay and
@@ -405,7 +436,7 @@ func (l *LevelIter) skipEmptyFileBackward() Span {
// which it should be due to the Close() call above.
l.iterFile = l.files.Prev()
if l.iterFile == nil {
return Span{}
return nil
}
startKey := l.iterFile.LargestRangeKey.UserKey
if l.cmp(startKey, endKey) < 0 {
@@ -415,24 +446,25 @@ func (l *LevelIter) skipEmptyFileBackward() Span {
Start: startKey,
End: endKey,
}
l.straddleDir = l.dir
return l.straddle
l.straddleDir = -1
return &l.straddle
}
} else if l.straddle.Valid() && l.straddleDir > 0 {
} else if l.straddleDir > 0 {
// We were at a straddle key, but are now changing directions. l.iterFile
// was already advanced forward by skipEmptyFileForward, so move it
// backward.
l.iterFile = l.files.Prev()
}
l.straddle = Span{}
var span Span
l.straddleDir = 0
var span *Span
for span.Empty() {
fileToLoad := l.iterFile
if l.keyType == manifest.KeyTypePoint {
fileToLoad = l.files.Prev()
}
if l.loadFile(fileToLoad, -1) == noFileLoaded {
return Span{}
return nil
}
span = l.iter.Last()
// In rangedel mode, we can expect to get empty files that we'd need to

View File

@@ -11,6 +11,7 @@ import (
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/invariants"
"github.com/cockroachdb/pebble/internal/manifest"
)
// TODO(jackson): Consider implementing an optimization to seek lower levels
@@ -20,27 +21,47 @@ import (
// seeks would require introducing key comparisons to switchTo{Min,Max}Heap
// where there currently are none.
// Transform defines a transform function to be applied to a Span. A Transform
// takes a Span as input and writes the transformed Span to the provided output
// *Span pointer. The output Span's Keys slice may be reused by Transform to
// reduce allocations.
type Transform func(cmp base.Compare, in Span, out *Span) error
// Transformer defines a transformation to be applied to a Span.
type Transformer interface {
// Transform takes a Span as input and writes the transformed Span to the
// provided output *Span pointer. The output Span's Keys slice may be reused
// by Transform to reduce allocations.
Transform(cmp base.Compare, in Span, out *Span) error
}
func noopTransform(_ base.Compare, s Span, dst *Span) error {
// The TransformerFunc type is an adapter to allow the use of ordinary functions
// as Transformers. If f is a function with the appropriate signature,
// TransformerFunc(f) is a Transformer that calls f.
type TransformerFunc func(base.Compare, Span, *Span) error
// Transform calls f(cmp, in, out).
func (tf TransformerFunc) Transform(cmp base.Compare, in Span, out *Span) error {
return tf(cmp, in, out)
}
var noopTransform Transformer = TransformerFunc(func(_ base.Compare, s Span, dst *Span) error {
dst.Start, dst.End = s.Start, s.End
dst.Keys = append(dst.Keys[:0], s.Keys...)
return nil
}
})
// visibleTransform filters keys that are invisible at the provided snapshot
// sequence number.
func visibleTransform(snapshot uint64) Transform {
return func(_ base.Compare, s Span, dst *Span) error {
s = s.Visible(snapshot)
func visibleTransform(snapshot uint64) Transformer {
return TransformerFunc(func(_ base.Compare, s Span, dst *Span) error {
dst.Start, dst.End = s.Start, s.End
dst.Keys = append(dst.Keys[:0], s.Keys...)
dst.Keys = dst.Keys[:0]
for _, k := range s.Keys {
// NB: The InternalKeySeqNumMax value is used for the batch snapshot
// because a batch's visible span keys are filtered when they're
// fragmented. There's no requirement to enforce visibility at
// iteration time.
if base.Visible(k.SeqNum(), snapshot, base.InternalKeySeqNumMax) {
dst.Keys = append(dst.Keys, k)
}
}
return nil
}
})
}
// MergingIter merges spans across levels of the LSM, exposing an iterator over
@@ -51,6 +72,10 @@ func visibleTransform(snapshot uint64) Transform {
// overlapping keys are surfaced in a single Span. Key spans from one child
// iterator may overlap key spans from another child iterator arbitrarily.
//
// The spans combined by MergingIter will return spans with keys sorted by
// trailer descending. If the MergingIter is configured with a Transformer, it's
// permitted to modify the ordering of the spans' keys returned by MergingIter.
//
// Algorithm
//
// The merging iterator wraps child iterators, merging and fragmenting spans
@@ -193,17 +218,17 @@ type MergingIter struct {
// buf is a buffer used to save [start, end) boundary keys.
buf []byte
// keys holds all of the keys across all levels that overlap the key span
// [start, end), sorted by sequence number and kind descending. This slice
// is reconstituted in synthesizeKeys from each mergingIterLevel's keys
// every time the [start, end) bounds change.
// [start, end), sorted by Trailer descending. This slice is reconstituted
// in synthesizeKeys from each mergingIterLevel's keys every time the
// [start, end) bounds change.
//
// Each element points into a child iterator's memory, so the keys may not
// be directly modified.
keys keysBySeqNumKind
// transform defines a function to be applied to a span before it's yielded
// to the user. A transform may filter individual keys contained within the
// span.
transform Transform
// transformer defines a transformation to be applied to a span before it's
// yielded to the user. Transforming may filter individual keys contained
// within the span.
transformer Transformer
// span holds the iterator's current span. This span is used as the
// destination for transforms. Every tranformed span overwrites the
// previous.
@@ -211,6 +236,17 @@ type MergingIter struct {
err error
dir int8
// alloc preallocates mergingIterLevel and mergingIterItems for use by the
// merging iterator. As long as the merging iterator is used with
// manifest.NumLevels+3 and fewer fragment iterators, the merging iterator
// will not need to allocate upon initialization. The value NumLevels+3
// mirrors the preallocated levels in iterAlloc used for point iterators.
// Invariant: cap(levels) == cap(items)
alloc struct {
levels [manifest.NumLevels + 3]mergingIterLevel
items [manifest.NumLevels + 3]mergingIterItem
}
}
// MergingIter implements the FragmentIterator interface.
@@ -225,45 +261,82 @@ type mergingIterLevel struct {
func (l *mergingIterLevel) next() {
if l.heapKey.kind == boundKindFragmentStart {
l.heapKey = endBound(l.heapKey.span)
l.heapKey = boundKey{
kind: boundKindFragmentEnd,
key: l.heapKey.span.End,
span: l.heapKey.span,
}
return
}
l.heapKey = startBound(l.iter.Next())
if s := l.iter.Next(); s == nil {
l.heapKey = boundKey{kind: boundKindInvalid}
} else {
l.heapKey = boundKey{
kind: boundKindFragmentStart,
key: s.Start,
span: s,
}
}
}
func (l *mergingIterLevel) prev() {
if l.heapKey.kind == boundKindFragmentEnd {
l.heapKey = startBound(l.heapKey.span)
l.heapKey = boundKey{
kind: boundKindFragmentStart,
key: l.heapKey.span.Start,
span: l.heapKey.span,
}
return
}
l.heapKey = endBound(l.iter.Prev())
if s := l.iter.Prev(); s == nil {
l.heapKey = boundKey{kind: boundKindInvalid}
} else {
l.heapKey = boundKey{
kind: boundKindFragmentEnd,
key: s.End,
span: s,
}
}
}
// Init initializes the merging iterator with the provided fragment iterators.
func (m *MergingIter) Init(cmp base.Compare, transform Transform, iters ...FragmentIterator) {
func (m *MergingIter) Init(cmp base.Compare, transformer Transformer, iters ...FragmentIterator) {
levels, items := m.levels, m.heap.items
*m = MergingIter{
heap: mergingIterHeap{cmp: cmp},
transform: transform,
heap: mergingIterHeap{cmp: cmp},
transformer: transformer,
}
// Invariant: cap(levels) == cap(items)
if cap(levels) < len(iters) {
m.levels = make([]mergingIterLevel, len(iters))
m.heap.items = make([]mergingIterItem, 0, len(iters))
} else {
// Invariant: cap(levels) >= cap(items)
// Invariant: cap(alloc.levels) == cap(alloc.items)
if len(iters) <= len(m.alloc.levels) {
// The slices allocated on the MergingIter struct are large enough.
m.levels = m.alloc.levels[:len(iters)]
m.heap.items = m.alloc.items[:0]
} else if len(iters) <= cap(levels) {
// The existing heap-allocated slices are large enough, so reuse them.
m.levels = levels[:len(iters)]
m.heap.items = items[:0]
} else {
// Heap allocate new slices.
m.levels = make([]mergingIterLevel, len(iters))
m.heap.items = make([]mergingIterItem, 0, len(iters))
}
for i := range m.levels {
m.levels[i] = mergingIterLevel{}
m.levels[i].iter = iters[i]
m.levels[i] = mergingIterLevel{iter: iters[i]}
}
}
// AddLevel adds a new level to the bottom of the merging iterator. AddLevel
// must be called after Init and before any other method.
func (m *MergingIter) AddLevel(iter FragmentIterator) {
m.levels = append(m.levels, mergingIterLevel{iter: iter})
}
// SeekGE moves the iterator to the first span with a start key greater than or
// equal to key.
func (m *MergingIter) SeekGE(key []byte) Span {
func (m *MergingIter) SeekGE(key []byte) *Span {
m.invalidate() // clear state about current position
for i := range m.levels {
l := &m.levels[i]
@@ -277,24 +350,35 @@ func (m *MergingIter) SeekGE(key []byte) Span {
// Otherwise we use the start boundary of the next span which
// necessarily has a start ≥ key.
s := l.iter.SeekLT(key)
if s.Valid() && m.cmp(s.End, key) >= 0 {
if s != nil && m.cmp(s.End, key) >= 0 {
// s.End ≥ key
// We need to use this span's end bound.
l.heapKey = endBound(s)
l.heapKey = boundKey{
kind: boundKindFragmentEnd,
key: s.End,
span: s,
}
continue
}
// s.End < key
// The span `s` ends before key. Next to the first span with a Start ≥
// key, and use that.
s = l.iter.Next()
l.heapKey = startBound(s)
if s = l.iter.Next(); s == nil {
l.heapKey = boundKey{kind: boundKindInvalid}
} else {
l.heapKey = boundKey{
kind: boundKindFragmentStart,
key: s.Start,
span: s,
}
}
}
m.initMinHeap()
return m.findNextFragmentSet()
}
// SeekLT moves the iterator to the last span with a start key less than key.
func (m *MergingIter) SeekLT(key []byte) Span {
func (m *MergingIter) SeekLT(key []byte) *Span {
// TODO(jackson): Evaluate whether there's an implementation of SeekLT
// independent of SeekGE that is more efficient. It's tricky, because the
// span we should return might straddle `key` itself.
@@ -314,42 +398,56 @@ func (m *MergingIter) SeekLT(key []byte) Span {
// Start user key < key: [b,l)#1. This requires examining bounds both < 'c'
// (the 'b' of [b,m)#1's start key) and bounds ≥ 'c' (the 'l' of ([a,l)#2's
// end key).
if s := m.SeekGE(key); !s.Valid() && m.err != nil {
return Span{}
if s := m.SeekGE(key); s == nil && m.err != nil {
return nil
}
// Prev to the previous span.
return m.Prev()
}
// First seeks the iterator to the first span.
func (m *MergingIter) First() Span {
func (m *MergingIter) First() *Span {
m.invalidate() // clear state about current position
for i := range m.levels {
l := &m.levels[i]
l.heapKey = startBound(l.iter.First())
if s := m.levels[i].iter.First(); s == nil {
m.levels[i].heapKey = boundKey{kind: boundKindInvalid}
} else {
m.levels[i].heapKey = boundKey{
kind: boundKindFragmentStart,
key: s.Start,
span: s,
}
}
}
m.initMinHeap()
return m.findNextFragmentSet()
}
// Last seeks the iterator to the last span.
func (m *MergingIter) Last() Span {
func (m *MergingIter) Last() *Span {
m.invalidate() // clear state about current position
for i := range m.levels {
l := &m.levels[i]
l.heapKey = endBound(l.iter.Last())
if s := m.levels[i].iter.Last(); s == nil {
m.levels[i].heapKey = boundKey{kind: boundKindInvalid}
} else {
m.levels[i].heapKey = boundKey{
kind: boundKindFragmentEnd,
key: s.End,
span: s,
}
}
}
m.initMaxHeap()
return m.findPrevFragmentSet()
}
// Next advances the iterator to the next span.
func (m *MergingIter) Next() Span {
func (m *MergingIter) Next() *Span {
if m.err != nil {
return Span{}
return nil
}
if m.dir == +1 && (m.end == nil || m.start == nil) {
return Span{}
return nil
}
if m.dir != +1 {
m.switchToMinHeap()
@@ -358,12 +456,12 @@ func (m *MergingIter) Next() Span {
}
// Prev advances the iterator to the previous span.
func (m *MergingIter) Prev() Span {
func (m *MergingIter) Prev() *Span {
if m.err != nil {
return Span{}
return nil
}
if m.dir == -1 && (m.end == nil || m.start == nil) {
return Span{}
return nil
}
if m.dir != -1 {
m.switchToMaxHeap()
@@ -533,7 +631,7 @@ func (m *MergingIter) cmp(a, b []byte) int {
return m.heap.cmp(a, b)
}
func (m *MergingIter) findNextFragmentSet() Span {
func (m *MergingIter) findNextFragmentSet() *Span {
// Each iteration of this loop considers a new merged span between unique
// user keys. An iteration may find that there exists no overlap for a given
// span, (eg, if the spans [a,b), [d, e) exist within level iterators, the
@@ -601,16 +699,16 @@ func (m *MergingIter) findNextFragmentSet() Span {
// we elide empty spans created by the mergingIter itself that don't overlap
// with any child iterator returned spans (i.e. empty spans that bridge two
// distinct child-iterator-defined spans).
if found, s := m.synthesizeKeys(+1); found && s.Valid() {
if found, s := m.synthesizeKeys(+1); found && s != nil {
return s
}
}
// Exhausted.
m.clear()
return Span{}
return nil
}
func (m *MergingIter) findPrevFragmentSet() Span {
func (m *MergingIter) findPrevFragmentSet() *Span {
// Each iteration of this loop considers a new merged span between unique
// user keys. An iteration may find that there exists no overlap for a given
// span, (eg, if the spans [a,b), [d, e) exist within level iterators, the
@@ -677,13 +775,13 @@ func (m *MergingIter) findPrevFragmentSet() Span {
// we elide empty spans created by the mergingIter itself that don't overlap
// with any child iterator returned spans (i.e. empty spans that bridge two
// distinct child-iterator-defined spans).
if found, s := m.synthesizeKeys(-1); found && s.Valid() {
if found, s := m.synthesizeKeys(-1); found && s != nil {
return s
}
}
// Exhausted.
m.clear()
return Span{}
return nil
}
func (m *MergingIter) heapRoot() []byte {
@@ -703,7 +801,7 @@ func (m *MergingIter) heapRoot() []byte {
//
// The boolean return value, `found`, is true if the returned span overlaps
// with a span returned by a child iterator.
func (m *MergingIter) synthesizeKeys(dir int8) (bool, Span) {
func (m *MergingIter) synthesizeKeys(dir int8) (bool, *Span) {
if invariants.Enabled {
if m.cmp(m.start, m.end) >= 0 {
panic(fmt.Sprintf("pebble: invariant violation: span start ≥ end: %s >= %s", m.start, m.end))
@@ -719,19 +817,26 @@ func (m *MergingIter) synthesizeKeys(dir int8) (bool, Span) {
found = true
}
}
// TODO(jackson): We should be able to remove this sort and instead
// guarantee that we'll return keys in the order of the levels they're from.
// With careful iterator construction, this would guarantee that they're
// sorted by trailer descending for the range key iteration use case.
sort.Sort(&m.keys)
// Apply the configured transform. See VisibleTransform.
// Apply the configured transform. See visibleTransform.
s := Span{
Start: m.start,
End: m.end,
Keys: m.keys,
Start: m.start,
End: m.end,
Keys: m.keys,
KeysOrder: ByTrailerDesc,
}
if err := m.transform(m.cmp, s, &m.span); err != nil {
// NB: m.heap.cmp is a base.Compare, whereas m.cmp is a method on
// MergingIter.
if err := m.transformer.Transform(m.heap.cmp, s, &m.span); err != nil {
m.err = err
return false, Span{}
return false, nil
}
return found, m.span
return found, &m.span
}
func (m *MergingIter) invalidate() {
@@ -885,28 +990,6 @@ func (h *mergingIterHeap) down(i0, n int) bool {
return i > i0
}
func startBound(s Span) boundKey {
if !s.Valid() {
return boundKey{kind: boundKindInvalid}
}
return boundKey{
kind: boundKindFragmentStart,
key: s.Start,
span: s,
}
}
func endBound(s Span) boundKey {
if !s.Valid() {
return boundKey{kind: boundKindInvalid}
}
return boundKey{
kind: boundKindFragmentEnd,
key: s.End,
span: s,
}
}
type boundKind int8
const (
@@ -922,7 +1005,7 @@ type boundKey struct {
//
// If kind is boundKindFragmentStart, then key is span.Start. If kind is
// boundKindFragmentEnd, then key is span.End.
span Span
span *Span
}
func (k boundKey) valid() bool {

View File

@@ -6,13 +6,9 @@ package keyspan
import "github.com/cockroachdb/pebble/internal/base"
// TODO(jackson): Move snapshot visiblity outside of SeekGE/SeekLE and into the
// callsite. This should simplify the code some more.
// SeekGE seeks to the span that contains the target key or the first span past
// the target key. The snapshot parameter controls the visibility of keys (only
// spans older than the snapshot sequence number are visible).
func SeekGE(cmp base.Compare, iter FragmentIterator, key []byte, snapshot uint64) Span {
// the target key.
func SeekGE(cmp base.Compare, iter FragmentIterator, key []byte) *Span {
// NB: We use SeekLT in order to land on the proper span for a search
// key that resides in the middle of a span. Consider the scenario:
//
@@ -29,27 +25,17 @@ func SeekGE(cmp base.Compare, iter FragmentIterator, key []byte, snapshot uint64
// Invariant: key > iterSpan.Start
if !iterSpan.Valid() || cmp(key, iterSpan.End) >= 0 {
if iterSpan == nil || cmp(key, iterSpan.End) >= 0 {
// The current span lies entirely before the search key, or the iterator
// is exhausted. Advance the iterator to the next span which is
// guaranteed to lie at or past the search key.
iterSpan = iter.Next()
}
// The iterator is positioned at a valid iterSpan which is the first
// iterator position that satisfies the requirement that it contains or is
// past the target key. It may not be visible based on the snapshot. So now
// we only need to move forward and return the first span with visible keys.
iterSpan = iterSpan.Visible(snapshot)
for iterSpan.Valid() && iterSpan.Empty() {
iterSpan = iter.Next().Visible(snapshot)
}
return iterSpan
}
// SeekLE seeks to the span that contains or is before the target key. The
// snapshot parameter controls the visibility of keys (only spans older than the
// snapshot sequence number are visible).
func SeekLE(cmp base.Compare, iter FragmentIterator, key []byte, snapshot uint64) Span {
// SeekLE seeks to the span that contains or is before the target key.
func SeekLE(cmp base.Compare, iter FragmentIterator, key []byte) *Span {
// NB: We use SeekLT in order to land on the proper span for a search
// key that resides in the middle of a span. Consider the scenario:
//
@@ -64,13 +50,13 @@ func SeekLE(cmp base.Compare, iter FragmentIterator, key []byte, snapshot uint64
// and we'll have to move forward.
iterSpan := iter.SeekLT(key)
if !iterSpan.Valid() {
if iterSpan == nil {
// Advance the iterator once to see if the next span has a start key
// equal to key.
iterSpan = iter.Next()
if !iterSpan.Valid() || cmp(key, iterSpan.Start) < 0 {
if iterSpan == nil || cmp(key, iterSpan.Start) < 0 {
// The iterator is exhausted or we've hit the next span.
return Span{}
return nil
}
} else {
// Invariant: key > iterSpan.Start
@@ -79,20 +65,12 @@ func SeekLE(cmp base.Compare, iter FragmentIterator, key []byte, snapshot uint64
// the next span contains the search key. If it doesn't, we'll backup
// and return to our earlier candidate.
iterSpan = iter.Next()
if !iterSpan.Valid() || cmp(key, iterSpan.Start) < 0 {
if iterSpan == nil || cmp(key, iterSpan.Start) < 0 {
// The next span is past our search key or there is no next span. Go
// back.
iterSpan = iter.Prev()
}
}
}
// We're positioned at a span that contains or is before the search key. It
// may not be visible based on the snapshot. So now we only need to move
// backward and return the first span with visible keys.
iterSpan = iterSpan.Visible(snapshot)
for iterSpan.Valid() && iterSpan.Empty() {
iterSpan = iter.Prev().Visible(snapshot)
}
return iterSpan
}

View File

@@ -5,6 +5,7 @@
package keyspan // import "github.com/cockroachdb/pebble/internal/keyspan"
import (
"bytes"
"fmt"
"sort"
"strconv"
@@ -30,16 +31,30 @@ type Span struct {
// non-nil, or both nil if representing an invalid Span.
Start, End []byte
// Keys holds the set of keys applied over the [Start, End) user key range.
// Keys is sorted by (SeqNum, Kind) descending. If SeqNum and Kind are
// equal, the order of Keys is undefined. Keys may be empty, even if Start
// and End are non-nil.
// Keys is sorted by (SeqNum, Kind) descending, unless otherwise specified
// by the context. If SeqNum and Kind are equal, the order of Keys is
// undefined. Keys may be empty, even if Start and End are non-nil.
//
// Keys are a decoded representation of the internal keys stored in batches
// or sstable blocks. A single internal key in a range key block may produce
// several decoded Keys.
Keys []Key
Keys []Key
KeysOrder KeysOrder
}
// KeysOrder describes the ordering of Keys within a Span.
type KeysOrder int8
const (
// ByTrailerDesc indicates a Span's keys are sorted by Trailer descending.
// This is the default ordering, and the ordering used during physical
// storage.
ByTrailerDesc KeysOrder = iota
// BySuffixAsc indicates a Span's keys are sorted by Suffix ascending. This
// ordering is used during user iteration of range keys.
BySuffixAsc
)
// Key represents a single key applied over a span of user keys. A Key is
// contained by a Span which specifies the span of user keys over which the Key
// is applied.
@@ -65,8 +80,17 @@ func (k Key) Kind() base.InternalKeyKind {
return base.InternalKeyKind(k.Trailer & 0xff)
}
// Equal returns true if this Key is equal to the given key. Two keys are said
// to be equal if the two Keys have equal trailers, suffix and value. Suffix
// comparison uses the provided base.Compare func. Value comparison is bytewise.
func (k Key) Equal(equal base.Equal, b Key) bool {
return k.Trailer == b.Trailer &&
equal(k.Suffix, b.Suffix) &&
bytes.Equal(k.Value, b.Value)
}
// Valid returns true if the span is defined.
func (s Span) Valid() bool {
func (s *Span) Valid() bool {
return s.Start != nil && s.End != nil
}
@@ -75,15 +99,18 @@ func (s Span) Valid() bool {
//
// An Empty span may be produced by Visible, or be produced by iterators in
// order to surface the gaps between keys.
func (s Span) Empty() bool {
return len(s.Keys) == 0
func (s *Span) Empty() bool {
return s == nil || len(s.Keys) == 0
}
// SmallestKey returns the smallest internal key defined by the span's keys.
// It panics if the span contains no keys.
func (s Span) SmallestKey() base.InternalKey {
// It requires the Span's keys be in ByTrailerDesc order. It panics if the span
// contains no keys or its keys are sorted in a different order.
func (s *Span) SmallestKey() base.InternalKey {
if len(s.Keys) == 0 {
panic("pebble: Span contains no keys")
} else if s.KeysOrder != ByTrailerDesc {
panic("pebble: span's keys unexpectedly not in trailer order")
}
// The first key has the highest (sequence number,kind) tuple.
return base.InternalKey{
@@ -98,10 +125,13 @@ func (s Span) SmallestKey() base.InternalKey {
// with the maximal sequence number, ensuring all InternalKeys with the same
// user key sort after the sentinel key.
//
// It panics if the span contains no keys.
func (s Span) LargestKey() base.InternalKey {
// It requires the Span's keys be in ByTrailerDesc order. It panics if the span
// contains no keys or its keys are sorted in a different order.
func (s *Span) LargestKey() base.InternalKey {
if len(s.Keys) == 0 {
panic("pebble: Span contains no keys")
} else if s.KeysOrder != ByTrailerDesc {
panic("pebble: span's keys unexpectedly not in trailer order")
}
// The last key has the lowest (sequence number,kind) tuple.
kind := s.Keys[len(s.Keys)-1].Kind()
@@ -109,43 +139,144 @@ func (s Span) LargestKey() base.InternalKey {
}
// SmallestSeqNum returns the smallest sequence number of a key contained within
// the span. It panics if the span contains no keys.
func (s Span) SmallestSeqNum() uint64 {
// the span. It requires the Span's keys be in ByTrailerDesc order. It panics if
// the span contains no keys or its keys are sorted in a different order.
func (s *Span) SmallestSeqNum() uint64 {
if len(s.Keys) == 0 {
panic("pebble: Span contains no keys")
} else if s.KeysOrder != ByTrailerDesc {
panic("pebble: span's keys unexpectedly not in trailer order")
}
return s.Keys[len(s.Keys)-1].SeqNum()
}
// LargestSeqNum returns the largest sequence number of a key contained within
// the span. It panics if the span contains no keys.
func (s Span) LargestSeqNum() uint64 {
// the span. It requires the Span's keys be in ByTrailerDesc order. It panics if
// the span contains no keys or its keys are sorted in a different order.
func (s *Span) LargestSeqNum() uint64 {
if len(s.Keys) == 0 {
panic("pebble: Span contains no keys")
} else if s.KeysOrder != ByTrailerDesc {
panic("pebble: span's keys unexpectedly not in trailer order")
}
return s.Keys[0].SeqNum()
}
// TODO(jackson): Replace most of the calls to Visible with more targeted calls
// that avoid the need to construct a new Span.
// Visible returns a span with the subset of keys visible at the provided
// sequence number.
func (s Span) Visible(seqNum uint64) Span {
// sequence number. It requires the Span's keys be in ByTrailerDesc order. It
// panics if the span's keys are sorted in a different order.
//
// Visible may incur an allocation, so callers should prefer targeted,
// non-allocating methods when possible.
func (s Span) Visible(snapshot uint64) Span {
if s.KeysOrder != ByTrailerDesc {
panic("pebble: span's keys unexpectedly not in trailer order")
}
ret := Span{Start: s.Start, End: s.End}
if len(s.Keys) == 0 {
return ret
}
// Keys from indexed batches may force an allocation. The Keys slice is
// ordered by sequence number, so ordinarily we can return the trailing
// subslice containing keys with sequence numbers less than `seqNum`.
//
// However, batch keys are special. Only visible batch keys are included
// when an Iterator's batch spans are fragmented. They must always be
// visible.
//
// Batch keys can create a sandwich of visible batch keys at the beginning
// of the slice and visible committed keys at the end of the slice, forcing
// us to allocate a new slice and copy the contents.
//
// Care is taking to only incur an allocation only when batch keys and
// visible keys actually sandwich non-visible keys.
// lastBatchIdx and lastNonVisibleIdx are set to the last index of a batch
// key and a non-visible key respectively.
lastBatchIdx := -1
lastNonVisibleIdx := -1
for i := range s.Keys {
if base.Visible(s.Keys[i].SeqNum(), seqNum) {
ret.Keys = s.Keys[i:]
break
if seqNum := s.Keys[i].SeqNum(); seqNum&base.InternalKeySeqNumBatch != 0 {
// Batch key. Always visible.
lastBatchIdx = i
} else if seqNum >= snapshot {
// This key is not visible.
lastNonVisibleIdx = i
}
}
// In the following comments: b = batch, h = hidden, v = visible (committed).
switch {
case lastNonVisibleIdx == -1:
// All keys are visible.
//
// [b b b], [v v v] and [b b b v v v]
ret.Keys = s.Keys
case lastBatchIdx == -1:
// There are no batch keys, so we can return the continuous subslice
// starting after the last non-visible Key.
//
// h h h [v v v]
ret.Keys = s.Keys[lastNonVisibleIdx+1:]
case lastNonVisibleIdx == len(s.Keys)-1:
// While we have a batch key and non-visible keys, there are no
// committed visible keys. The 'sandwich' is missing the bottom layer,
// so we can return the continuous sublice at the beginning.
//
// [b b b] h h h
ret.Keys = s.Keys[0 : lastBatchIdx+1]
default:
// This is the problematic sandwich case. Allocate a new slice, copying
// the batch keys and the visible keys into it.
//
// [b b b] h h h [v v v]
ret.Keys = make([]Key, (lastBatchIdx+1)+(len(s.Keys)-lastNonVisibleIdx-1))
copy(ret.Keys, s.Keys[:lastBatchIdx+1])
copy(ret.Keys[lastBatchIdx+1:], s.Keys[lastNonVisibleIdx+1:])
}
return ret
}
// VisibleAt returns true if the span contains a key visible at the provided
// snapshot. Keys with sequence numbers with the batch bit set are treated as
// always visible.
//
// VisibleAt requires the Span's keys be in ByTrailerDesc order. It panics if
// the span's keys are sorted in a different order.
func (s *Span) VisibleAt(snapshot uint64) bool {
if s.KeysOrder != ByTrailerDesc {
panic("pebble: span's keys unexpectedly not in trailer order")
}
if len(s.Keys) == 0 {
return false
} else if first := s.Keys[0].SeqNum(); first&base.InternalKeySeqNumBatch != 0 {
// Only visible batch keys are included when an Iterator's batch spans
// are fragmented. They must always be visible.
return true
} else {
// Otherwise we check the last key. Since keys are ordered decreasing in
// sequence number, the last key has the lowest sequence number of any
// of the span's keys. If any of the keys are visible, the last key must
// be visible. Or put differently: if the last key is not visible, then
// no key is visible.
return s.Keys[len(s.Keys)-1].SeqNum() < snapshot
}
}
// ShallowClone returns the span with a Keys slice owned by the span itself.
// None of the key byte slices are cloned (see Span.DeepClone).
func (s Span) ShallowClone() Span {
func (s *Span) ShallowClone() Span {
c := Span{
Start: s.Start,
End: s.End,
Keys: make([]Key, len(s.Keys)),
Start: s.Start,
End: s.End,
Keys: make([]Key, len(s.Keys)),
KeysOrder: s.KeysOrder,
}
copy(c.Keys, s.Keys)
return c
@@ -154,11 +285,12 @@ func (s Span) ShallowClone() Span {
// DeepClone clones the span, creating copies of all contained slices. DeepClone
// is intended for non-production code paths like tests, the level checker, etc
// because it is allocation heavy.
func (s Span) DeepClone() Span {
func (s *Span) DeepClone() Span {
c := Span{
Start: make([]byte, len(s.Start)),
End: make([]byte, len(s.End)),
Keys: make([]Key, len(s.Keys)),
Start: make([]byte, len(s.Start)),
End: make([]byte, len(s.End)),
Keys: make([]Key, len(s.Keys)),
KeysOrder: s.KeysOrder,
}
copy(c.Start, s.Start)
copy(c.End, s.End)
@@ -177,15 +309,48 @@ func (s Span) DeepClone() Span {
}
// Contains returns true if the specified key resides within the span's bounds.
func (s Span) Contains(cmp base.Compare, key []byte) bool {
func (s *Span) Contains(cmp base.Compare, key []byte) bool {
return cmp(s.Start, key) <= 0 && cmp(key, s.End) < 0
}
// Covers returns true if the span covers keys at seqNum.
//
// Covers requires the Span's keys be in ByTrailerDesc order. It panics if the
// span's keys are sorted in a different order.
func (s Span) Covers(seqNum uint64) bool {
if s.KeysOrder != ByTrailerDesc {
panic("pebble: span's keys unexpectedly not in trailer order")
}
return !s.Empty() && s.Keys[0].SeqNum() > seqNum
}
// CoversAt returns true if the span contains a key that is visible at the
// provided snapshot sequence number, and that key's sequence number is higher
// than seqNum.
//
// Keys with sequence numbers with the batch bit set are treated as always
// visible.
//
// CoversAt requires the Span's keys be in ByTrailerDesc order. It panics if the
// span's keys are sorted in a different order.
func (s *Span) CoversAt(snapshot, seqNum uint64) bool {
if s.KeysOrder != ByTrailerDesc {
panic("pebble: span's keys unexpectedly not in trailer order")
}
// NB: A key is visible at `snapshot` if its sequence number is strictly
// less than `snapshot`. See base.Visible.
for i := range s.Keys {
if kseq := s.Keys[i].SeqNum(); kseq&base.InternalKeySeqNumBatch != 0 {
// Only visible batch keys are included when an Iterator's batch spans
// are fragmented. They must always be visible.
return kseq > seqNum
} else if kseq < snapshot {
return kseq > seqNum
}
}
return false
}
// String returns a string representation of the span.
func (s Span) String() string {
return fmt.Sprint(prettySpan{Span: s, formatKey: base.DefaultFormatter})
@@ -223,10 +388,12 @@ func (s prettySpan) Format(fs fmt.State, c rune) {
fmt.Fprintf(fs, "}")
}
// SortKeys sorts a keys slice by trailer.
func SortKeys(keys []Key) {
sorted := keysBySeqNumKind(keys)
sort.Sort(&sorted)
// SortKeysByTrailer sorts a keys slice by trailer.
func SortKeysByTrailer(keys *[]Key) {
// NB: keys is a pointer to a slice instead of a slice to avoid `sorted`
// escaping to the heap.
sorted := (*keysBySeqNumKind)(keys)
sort.Sort(sorted)
}
// ParseSpan parses the string representation of a Span. It's intended for

View File

@@ -12,7 +12,7 @@ import "github.com/cockroachdb/pebble/internal/base"
func Truncate(
cmp base.Compare, iter FragmentIterator, lower, upper []byte, start, end *base.InternalKey,
) FragmentIterator {
return Filter(iter, func(in Span, out *Span) (keep bool) {
return Filter(iter, func(in *Span, out *Span) (keep bool) {
out.Start, out.End = in.Start, in.End
out.Keys = append(out.Keys[:0], in.Keys...)

View File

@@ -714,7 +714,17 @@ func (s *L0Sublevels) InitCompactingFileInfo(inProgress []L0Compaction) {
iter := s.levelMetadata.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
if !f.Compacting {
if invariants.Enabled {
if !bytes.Equal(s.orderedIntervals[f.minIntervalIndex].startKey.key, f.Smallest.UserKey) {
panic(fmt.Sprintf("f.minIntervalIndex in FileMetadata out of sync with intervals in L0Sublevels: %s != %s",
s.formatKey(s.orderedIntervals[f.minIntervalIndex].startKey.key), s.formatKey(f.Smallest.UserKey)))
}
if !bytes.Equal(s.orderedIntervals[f.maxIntervalIndex+1].startKey.key, f.Largest.UserKey) {
panic(fmt.Sprintf("f.maxIntervalIndex in FileMetadata out of sync with intervals in L0Sublevels: %s != %s",
s.formatKey(s.orderedIntervals[f.maxIntervalIndex+1].startKey.key), s.formatKey(f.Smallest.UserKey)))
}
}
if !f.IsCompacting() {
continue
}
for i := f.minIntervalIndex; i <= f.maxIntervalIndex; i++ {
@@ -795,7 +805,7 @@ func (s *L0Sublevels) describe(verbose bool) string {
}
sumIntervals += intervals
totalBytes += f.Size
if f.Compacting {
if f.IsCompacting() {
numCompactingFiles++
}
}
@@ -1042,7 +1052,7 @@ func (s *L0Sublevels) checkCompaction(c *L0CompactionFiles) error {
fmt.Fprintf(&buf, "bug %t, seed interval: %d: level %d, sl index %d, f.index %d, min %d, max %d, pre-min %d, pre-max %d, f.min %d, f.max %d, filenum: %d, isCompacting: %t\n%s\n",
c.isIntraL0, c.seedInterval, level, index, f.L0Index, min, max, c.preExtensionMinInterval, c.preExtensionMaxInterval,
f.minIntervalIndex, f.maxIntervalIndex,
f.FileNum, f.Compacting, s)
f.FileNum, f.IsCompacting(), s)
fmt.Fprintf(&buf, "files included:\n")
for _, f := range c.Files {
fmt.Fprintf(&buf, "filenum: %d, sl: %d, index: %d, [%d, %d]\n",
@@ -1386,7 +1396,7 @@ func (s *L0Sublevels) PickBaseCompaction(
return nil, errors.New("no seed file found in sublevel intervals")
}
consideredIntervals.markBits(f.minIntervalIndex, f.maxIntervalIndex+1)
if f.Compacting {
if f.IsCompacting() {
if f.IsIntraL0Compacting {
// If we're picking a base compaction and we came across a
// seed file candidate that's being intra-L0 compacted, skip
@@ -1418,7 +1428,7 @@ func (s *L0Sublevels) PickBaseCompaction(
if cmp > 0 || (cmp == 0 && !s.orderedIntervals[c.maxIntervalIndex+1].startKey.isLargest) {
break
}
baseCompacting = baseCompacting || m.Compacting
baseCompacting = baseCompacting || m.IsCompacting()
}
if baseCompacting {
continue
@@ -1539,7 +1549,7 @@ func (s *L0Sublevels) extendFiles(
if f.minIntervalIndex > cFiles.maxIntervalIndex {
break
}
if f.Compacting {
if f.IsCompacting() {
return false
}
// Skip over files that are newer than earliestUnflushedSeqNum. This is
@@ -1590,7 +1600,7 @@ func (s *L0Sublevels) PickIntraL0Compaction(
stackDepthReduction := scoredInterval.score
for i := len(interval.files) - 1; i >= 0; i-- {
f = interval.files[i]
if f.Compacting {
if f.IsCompacting() {
break
}
consideredIntervals.markBits(f.minIntervalIndex, f.maxIntervalIndex+1)
@@ -1614,7 +1624,7 @@ func (s *L0Sublevels) PickIntraL0Compaction(
if f == nil {
return nil, errors.New("no seed file found in sublevel intervals")
}
if f.Compacting {
if f.IsCompacting() {
// This file could be in a concurrent intra-L0 or base compaction.
// Try another interval.
continue
@@ -1666,7 +1676,7 @@ func (s *L0Sublevels) intraL0CompactionUsingSeed(
for ; slIndex >= 0; slIndex-- {
f2 := interval.files[slIndex]
sl := f2.SubLevel
if f2.Compacting {
if f2.IsCompacting() {
break
}
c.seedIntervalStackDepthReduction++
@@ -1936,7 +1946,7 @@ func (s *L0Sublevels) extendCandidateToRectangle(
candidateHasAlreadyPickedFiles := false
for index = firstIndex; index <= lastIndex; index++ {
f := files[index]
if f.Compacting {
if f.IsCompacting() {
if nonCompactingFirst != -1 {
last := index - 1
// Prioritize runs of consecutive non-compacting files that
@@ -1988,7 +1998,7 @@ func (s *L0Sublevels) extendCandidateToRectangle(
}
for index := candidateNonCompactingFirst; index <= candidateNonCompactingLast; index++ {
f := files[index]
if f.Compacting {
if f.IsCompacting() {
// TODO(bilal): Do a logger.Fatalf instead of a panic, for
// cleaner unwinding and error messages.
panic(fmt.Sprintf("expected %s to not be compacting", f.FileNum))

View File

@@ -481,6 +481,21 @@ func (i *LevelIterator) SeekGE(cmp Compare, userKey []byte) *FileMetadata {
meta := i.seek(func(m *FileMetadata) bool {
return cmp(m.Largest.UserKey, userKey) >= 0
})
for meta != nil {
switch i.filter {
case KeyTypePointAndRange:
return meta
case KeyTypePoint:
if meta.HasPointKeys && cmp(meta.LargestPointKey.UserKey, userKey) >= 0 {
return meta
}
case KeyTypeRange:
if meta.HasRangeKeys && cmp(meta.LargestRangeKey.UserKey, userKey) >= 0 {
return meta
}
}
meta = i.Next()
}
return i.filteredNextFile(meta)
}
@@ -497,6 +512,21 @@ func (i *LevelIterator) SeekLT(cmp Compare, userKey []byte) *FileMetadata {
return cmp(m.Smallest.UserKey, userKey) >= 0
})
meta := i.Prev()
for meta != nil {
switch i.filter {
case KeyTypePointAndRange:
return meta
case KeyTypePoint:
if meta.HasPointKeys && cmp(meta.SmallestPointKey.UserKey, userKey) < 0 {
return meta
}
case KeyTypeRange:
if meta.HasRangeKeys && cmp(meta.SmallestRangeKey.UserKey, userKey) < 0 {
return meta
}
}
meta = i.Prev()
}
return i.filteredPrevFile(meta)
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/invariants"
"github.com/cockroachdb/pebble/vfs"
)
@@ -43,15 +44,12 @@ type TableInfo struct {
// TableStats contains statistics on a table used for compaction heuristics.
type TableStats struct {
// Valid true if stats have been loaded for the table. The rest of the
// structure is populated only if true.
Valid bool
// The total number of entries in the table.
NumEntries uint64
// The number of point and range deletion entries in the table.
NumDeletions uint64
// NumRangeKeys is the total number of range keys in the table.
NumRangeKeys uint64
// NumRangeKeySets is the total number of range key sets in the table.
NumRangeKeySets uint64
// Estimate of the total disk space that may be dropped by this table's
// point deletions by compacting them.
PointDeletionsBytesEstimate uint64
@@ -77,6 +75,48 @@ const (
boundTypeRangeKey
)
// CompactionState is the compaction state of a file.
//
// The following shows the valid state transitions:
//
// NotCompacting --> Compacting --> Compacted
// ^ |
// | |
// +-------<-------+
//
// Input files to a compaction transition to Compacting when a compaction is
// picked. A file that has finished compacting typically transitions into the
// Compacted state, at which point it is effectively obsolete ("zombied") and
// will eventually be removed from the LSM. A file that has been move-compacted
// will transition from Compacting back into the NotCompacting state, signaling
// that the file may be selected for a subsequent compaction. A failed
// compaction will result in all input tables transitioning from Compacting to
// NotCompacting.
//
// This state is in-memory only. It is not persisted to the manifest.
type CompactionState uint8
// CompactionStates.
const (
CompactionStateNotCompacting CompactionState = iota
CompactionStateCompacting
CompactionStateCompacted
)
// String implements fmt.Stringer.
func (s CompactionState) String() string {
switch s {
case CompactionStateNotCompacting:
return "NotCompacting"
case CompactionStateCompacting:
return "Compacting"
case CompactionStateCompacted:
return "Compacted"
default:
panic(fmt.Sprintf("pebble: unknown compaction state %d", s))
}
}
// FileMetadata holds the metadata for an on-disk table.
type FileMetadata struct {
// Atomic contains fields which are accessed atomically. Go allocations
@@ -90,6 +130,10 @@ type FileMetadata struct {
// in pebble.Iterator after every after every positioning operation
// that returns a user key (eg. Next, Prev, SeekGE, SeekLT, etc).
AllowedSeeks int64
// statsValid is 1 if stats have been loaded for the table. The
// TableStats structure is populated only if valid is 1.
statsValid uint32
}
// InitAllowedSeeks is the inital value of allowed seeks. This is used
@@ -146,12 +190,11 @@ type FileMetadata struct {
// pick L0 compactions. Only accurate for the most recent Version.
//
// IsIntraL0Compacting is set to True if this file is part of an intra-L0
// compaction. When it's true, Compacting must also be true. If Compacting
// is true and IsIntraL0Compacting is false for an L0 file, the file must
// be part of a compaction to Lbase.
// compaction. When it's true, IsCompacting must also return true. If
// Compacting is true and IsIntraL0Compacting is false for an L0 file, the
// file must be part of a compaction to Lbase.
IsIntraL0Compacting bool
// True if the file is actively being compacted. Protected by DB.mu.
Compacting bool
CompactionState CompactionState
// True if compaction of this file has been explicitly requested.
// Previously, RocksDB and earlier versions of Pebble allowed this
// flag to be set by a user table property collector. Some earlier
@@ -169,9 +212,11 @@ type FileMetadata struct {
//
// Protected by DB.mu.
MarkedForCompaction bool
// HasPointKeys and HasRangeKeys track whether the table contains point and
// range keys, respectively.
// HasPointKeys tracks whether the table contains point keys (including
// RANGEDELs). If a table contains only range deletions, HasPointsKeys is
// still true.
HasPointKeys bool
// HasRangeKeys tracks whether the table contains any range keys.
HasRangeKeys bool
// smallestSet and largestSet track whether the overall bounds have been set.
boundsSet bool
@@ -181,6 +226,59 @@ type FileMetadata struct {
boundTypeSmallest, boundTypeLargest boundType
}
// SetCompactionState transitions this file's compaction state to the given
// state. Protected by DB.mu.
func (m *FileMetadata) SetCompactionState(to CompactionState) {
if invariants.Enabled {
transitionErr := func() error {
return errors.Newf("pebble: invalid compaction state transition: %s -> %s", m.CompactionState, to)
}
switch m.CompactionState {
case CompactionStateNotCompacting:
if to != CompactionStateCompacting {
panic(transitionErr())
}
case CompactionStateCompacting:
if to != CompactionStateCompacted && to != CompactionStateNotCompacting {
panic(transitionErr())
}
case CompactionStateCompacted:
panic(transitionErr())
default:
panic(fmt.Sprintf("pebble: unknown compaction state: %d", m.CompactionState))
}
}
m.CompactionState = to
}
// IsCompacting returns true if this file's compaction state is
// CompactionStateCompacting. Protected by DB.mu.
func (m *FileMetadata) IsCompacting() bool {
return m.CompactionState == CompactionStateCompacting
}
// StatsValid returns true if the table stats have been populated. If StatValid
// returns true, the Stats field may be read (with or without holding the
// database mutex).
func (m *FileMetadata) StatsValid() bool {
return atomic.LoadUint32(&m.Atomic.statsValid) == 1
}
// StatsValidLocked returns true if the table stats have been populated.
// StatsValidLocked requires DB.mu is held when it's invoked, and it avoids the
// overhead of an atomic load. This is possible because table stats validity is
// only set while DB.mu is held.
func (m *FileMetadata) StatsValidLocked() bool {
return m.Atomic.statsValid == 1
}
// StatsMarkValid marks the TableStats as valid. The caller must hold DB.mu
// while populating TableStats and calling StatsMarkValud. Once stats are
// populated, they must not be mutated.
func (m *FileMetadata) StatsMarkValid() {
atomic.StoreUint32(&m.Atomic.statsValid, 1)
}
// ExtendPointKeyBounds attempts to extend the lower and upper point key bounds
// and overall table bounds with the given smallest and largest keys. The
// smallest and largest bounds may not be extended if the table already has a
@@ -654,6 +752,11 @@ type Version struct {
Levels [NumLevels]LevelMetadata
// RangeKeyLevels holds a subset of the same files as Levels that contain range
// keys (i.e. fileMeta.HasRangeKeys == true). The memory amplification of this
// duplication should be minimal, as range keys are expected to be rare.
RangeKeyLevels [NumLevels]LevelMetadata
// The callback to invoke when the last reference to a version is
// removed. Will be called with list.mu held.
Deleted func(obsolete []*FileMetadata)
@@ -787,6 +890,9 @@ func (v *Version) unrefFiles() []*FileMetadata {
for _, lm := range v.Levels {
obsolete = append(obsolete, lm.release()...)
}
for _, lm := range v.RangeKeyLevels {
obsolete = append(obsolete, lm.release()...)
}
return obsolete
}

View File

@@ -648,6 +648,11 @@ func (b *BulkVersionEdit) Apply(
} else {
v.Levels[level] = curr.Levels[level].clone()
}
if curr == nil || curr.RangeKeyLevels[level].tree.root == nil {
v.RangeKeyLevels[level] = makeLevelMetadata(cmp, level, nil /* files */)
} else {
v.RangeKeyLevels[level] = curr.RangeKeyLevels[level].clone()
}
if len(b.Added[level]) == 0 && len(b.Deleted[level]) == 0 {
// There are no edits on this level.
@@ -667,6 +672,7 @@ func (b *BulkVersionEdit) Apply(
// Some edits on this level.
lm := &v.Levels[level]
lmRange := &v.RangeKeyLevels[level]
addedFiles := b.Added[level]
deletedMap := b.Deleted[level]
if n := v.Levels[level].Len() + len(addedFiles); n == 0 {
@@ -688,6 +694,16 @@ func (b *BulkVersionEdit) Apply(
err := errors.Errorf("pebble: internal error: file L%d.%s obsolete during B-Tree removal", level, f.FileNum)
return nil, nil, err
}
if f.HasRangeKeys {
if obsolete := v.RangeKeyLevels[level].tree.delete(f); obsolete {
// Deleting a file from the B-Tree may decrement its
// reference count. However, because we cloned the
// previous level's B-Tree, this should never result in a
// file's reference count dropping to zero.
err := errors.Errorf("pebble: internal error: file L%d.%s obsolete during range-key B-Tree removal", level, f.FileNum)
return nil, nil, err
}
}
}
var sm, la *FileMetadata
@@ -714,6 +730,12 @@ func (b *BulkVersionEdit) Apply(
if err != nil {
return nil, nil, errors.Wrap(err, "pebble")
}
if f.HasRangeKeys {
err = lmRange.tree.insert(f)
if err != nil {
return nil, nil, errors.Wrap(err, "pebble")
}
}
removeZombie(f.FileNum)
// Track the keys with the smallest and largest keys, so that we can
// check consistency of the modified span.

View File

@@ -13,7 +13,7 @@ import (
// closure with the encoded internal keys that represent the Span's state. The
// keys and values passed to emit are only valid until the closure returns. If
// emit returns an error, Encode stops and returns the error.
func Encode(s keyspan.Span, emit func(k base.InternalKey, v []byte) error) error {
func Encode(s *keyspan.Span, emit func(k base.InternalKey, v []byte) error) error {
for _, k := range s.Keys {
if k.Kind() != base.InternalKeyKindRangeDelete {
return base.CorruptionErrorf("pebble: rangedel.Encode cannot encode %s key", k.Kind())

View File

@@ -11,117 +11,162 @@ import (
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/invariants"
"github.com/cockroachdb/pebble/internal/keyspan"
"github.com/cockroachdb/pebble/internal/manifest"
)
// InitUserIteration 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 removes all
// Keys other than RangeKeySets describing the current state of range keys.
// UserIteratorConfig holds state for constructing the range key iterator stack
// for user iteration.
type UserIteratorConfig struct {
snapshot uint64
comparer *base.Comparer
miter keyspan.MergingIter
biter keyspan.BoundedIter
diter keyspan.DefragmentingIter
liters [manifest.NumLevels]keyspan.LevelIter
litersUsed int
sortBuf keysBySuffix
}
// 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 removes all Keys other than
// RangeKeySets describing the current state of range keys. The resulting spans
// contain Keys sorted by Suffix.
//
// The snapshot sequence number parameter determines which keys are visible. Any
// keys not visible at the provided snapshot are ignored.
func InitUserIteration(
cmp base.Compare,
func (ui *UserIteratorConfig) Init(
comparer *base.Comparer,
snapshot uint64,
miter *keyspan.MergingIter,
diter *keyspan.DefragmentingIter,
levelIters ...keyspan.FragmentIterator,
lower, upper []byte,
hasPrefix *bool,
prefix *[]byte,
iters ...keyspan.FragmentIterator,
) keyspan.FragmentIterator {
miter.Init(cmp, userIterationTransform(snapshot), levelIters...)
diter.Init(cmp, miter, userIterationDefragmenter(), keyspan.StaticDefragmentReducer)
return diter
ui.snapshot = snapshot
ui.comparer = comparer
ui.miter.Init(comparer.Compare, ui, iters...)
ui.biter.Init(comparer.Compare, comparer.Split, &ui.miter, lower, upper, hasPrefix, prefix)
ui.diter.Init(comparer, &ui.biter, ui, keyspan.StaticDefragmentReducer)
ui.litersUsed = 0
return &ui.diter
}
// userIterationTransform returns a keyspan.Transform for use with a
// keyspan.MergingIter that transforms spans by resolving range keys at the
// AddLevel adds a new level to the bottom of the iterator stack. AddLevel
// must be called after Init and before any other method on the iterator.
func (ui *UserIteratorConfig) AddLevel(iter keyspan.FragmentIterator) {
ui.miter.AddLevel(iter)
}
// NewLevelIter returns a pointer to a newly allocated or reused
// keyspan.LevelIter. The caller is responsible for calling Init() on this
// instance.
func (ui *UserIteratorConfig) NewLevelIter() *keyspan.LevelIter {
if ui.litersUsed >= len(ui.liters) {
return &keyspan.LevelIter{}
}
ui.litersUsed++
return &ui.liters[ui.litersUsed-1]
}
// SetBounds propagates bounds to the iterator stack. The fragment iterator
// interface ordinarily doesn't enforce bounds, so this is exposed as an
// explicit method on the user iterator config.
func (ui *UserIteratorConfig) SetBounds(lower, upper []byte) {
ui.biter.SetBounds(lower, upper)
}
// Transform implements the keyspan.Transformer interface for use with a
// keyspan.MergingIter. It transforms spans by resolving range keys at the
// provided snapshot sequence number. Shadowing of keys is resolved (eg, removal
// 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.
func userIterationTransform(snapshot uint64) keyspan.Transform {
return func(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error {
// Apply shadowing of keys.
if err := Coalesce(cmp, s.Visible(snapshot), dst); err != nil {
return err
}
// During user iteration over range keys, unsets and deletes don't
// matter. Remove them. This step helps logical defragmentation during
// iteration.
keys := dst.Keys
dst.Keys = dst.Keys[:0]
for i := range keys {
switch keys[i].Kind() {
case base.InternalKeyKindRangeKeySet:
dst.Keys = append(dst.Keys, keys[i])
case base.InternalKeyKindRangeKeyUnset:
// Skip.
continue
case base.InternalKeyKindRangeKeyDelete:
// Skip.
continue
default:
return base.CorruptionErrorf("pebble: unrecognized range key kind %s", keys[i].Kind())
}
}
return nil
// sequence number, and hold their Keys sorted by Suffix.
func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error {
// Apply shadowing of keys.
dst.Start = s.Start
dst.End = s.End
ui.sortBuf = keysBySuffix{
cmp: cmp,
keys: dst.Keys[:0],
}
if err := coalesce(&ui.sortBuf, s.Visible(ui.snapshot).Keys, &dst.Keys); err != nil {
return err
}
// During user iteration over range keys, unsets and deletes don't
// matter. Remove them. This step helps logical defragmentation during
// iteration.
keys := dst.Keys
dst.Keys = dst.Keys[:0]
for i := range keys {
switch keys[i].Kind() {
case base.InternalKeyKindRangeKeySet:
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")
}
dst.Keys = append(dst.Keys, keys[i])
case base.InternalKeyKindRangeKeyUnset:
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")
}
// Skip.
continue
case base.InternalKeyKindRangeKeyDelete:
// Skip.
continue
default:
return base.CorruptionErrorf("pebble: unrecognized range key kind %s", keys[i].Kind())
}
}
// coalesce results in dst.Keys being sorted by Suffix.
dst.KeysOrder = keyspan.BySuffixAsc
return nil
}
// userIterationDefragmenter constructs a DefragmentMethod that configures a
// ShouldDefragment implements the DefragmentMethod interface and configures a
// DefragmentingIter to defragment spans of range keys if their user-visible
// state is identical. This defragmenting method assumes the provided spans have
// already been transformed through UserIterationTransform, so all RangeKeySets
// are user-visible sets. This defragmenter checks for equality between set
// suffixes and values (ignoring sequence numbers). It's intended for use during
// user iteration, when the wrapped keyspan iterator is merging spans across all
// levels of the LSM.
//
// The returned defragmenting method is stateful, and must not be used on
// multiple DefragmentingIters concurrently.
func userIterationDefragmenter() keyspan.DefragmentMethod {
var bufA keysBySuffix
var bufB keysBySuffix
return func(cmp base.Compare, a, b keyspan.Span) bool {
// UserIterationDefragmenter must only be used on spans that have
// transformed by UserIterationTransform. The transform applies
// shadowing and removes all keys besides the resulting Sets. Since
// shadowing has been applied, each Set must set a unique suffix. If the
// two spans are equivalent, they must have the same number of range key
// sets.
if len(a.Keys) != len(b.Keys) {
return false
}
// already been transformed through (UserIterationConfig).Transform, so all
// RangeKeySets are user-visible sets and are already in Suffix order. This
// defragmenter checks for equality between set suffixes and values (ignoring
// 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 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
// applied, each Set must set a unique suffix. If the two spans are
// equivalent, they must have the same number of range key sets.
if len(a.Keys) != len(b.Keys) || len(a.Keys) == 0 {
return false
}
if a.KeysOrder != keyspan.BySuffixAsc || b.KeysOrder != keyspan.BySuffixAsc {
panic("pebble: range key span's keys unexpectedly not in ascending suffix order")
}
// The keys in both spans must be all sets. In order to be equivalent
// during user iteration, they must set identical suffix-value mappings.
// They do not need to have the same sequence number.
//
// Currently, the Keys are sorted by Trailer (as per the keyspan.Span
// and keyspan.FragmentIterator interfaces). Collect all the keys in
// both spans into buffers and sort them by suffix for comparison.
bufA.cmp = cmp
bufA.keys = append(bufA.keys[:0], a.Keys...)
sort.Sort(bufA)
bufB.cmp = cmp
bufB.keys = append(bufB.keys[:0], b.Keys...)
sort.Sort(bufB)
for i := range bufA.keys {
if bufA.keys[i].Kind() != base.InternalKeyKindRangeKeySet ||
bufB.keys[i].Kind() != base.InternalKeyKindRangeKeySet {
ret := true
for i := range a.Keys {
if invariants.Enabled {
if a.Keys[i].Kind() != base.InternalKeyKindRangeKeySet ||
b.Keys[i].Kind() != base.InternalKeyKindRangeKeySet {
panic("pebble: unexpected non-RangeKeySet during defragmentation")
}
if cmp(bufA.keys[i].Suffix, bufB.keys[i].Suffix) != 0 {
return false
}
if !bytes.Equal(bufA.keys[i].Value, bufB.keys[i].Value) {
return false
if i > 0 && (ui.comparer.Compare(a.Keys[i].Suffix, a.Keys[i-1].Suffix) < 0 ||
ui.comparer.Compare(b.Keys[i].Suffix, b.Keys[i-1].Suffix) < 0) {
panic("pebble: range keys not ordered by suffix during defragmentation")
}
}
return true
if !equal(a.Keys[i].Suffix, b.Keys[i].Suffix) {
ret = false
break
}
if !bytes.Equal(a.Keys[i].Value, b.Keys[i].Value) {
ret = false
break
}
}
return ret
}
// Coalesce imposes range key semantics and coalesces range keys with the same
@@ -157,18 +202,28 @@ func userIterationDefragmenter() keyspan.DefragmentMethod {
// keys do not affect one another. Ingested sstables are expected to be
// consistent with respect to the set/unset suffixes: A given suffix should be
// set or unset but not both.
func Coalesce(cmp base.Compare, span keyspan.Span, dst *keyspan.Span) error {
//
// The resulting dst Keys slice is sorted by Trailer.
func Coalesce(cmp base.Compare, keys []keyspan.Key, dst *[]keyspan.Key) error {
// TODO(jackson): Currently, Coalesce doesn't actually perform the sequence
// number promotion described in the comment above.
keysBySuffix := keysBySuffix{
cmp: cmp,
keys: dst.Keys[:0],
keys: (*dst)[:0],
}
if err := coalesce(&keysBySuffix, keys, dst); err != nil {
return err
}
// coalesce left the keys in *dst sorted by suffix. Re-sort them by trailer.
keyspan.SortKeysByTrailer(dst)
return nil
}
func coalesce(keysBySuffix *keysBySuffix, keys []keyspan.Key, dst *[]keyspan.Key) error {
var deleted bool
for i := 0; i < len(span.Keys) && !deleted; i++ {
k := span.Keys[i]
if invariants.Enabled && i > 0 && k.Trailer > span.Keys[i-1].Trailer {
for i := 0; i < len(keys) && !deleted; i++ {
k := keys[i]
if invariants.Enabled && i > 0 && k.Trailer > keys[i-1].Trailer {
panic("pebble: invariant violation: span keys unordered")
}
@@ -210,26 +265,13 @@ func Coalesce(cmp base.Compare, span keyspan.Span, dst *keyspan.Span) error {
}
}
// Update the span with the (potentially reduced) keys slice, and re-sort it
// by Trailer.
*dst = keyspan.Span{
Start: span.Start,
End: span.End,
Keys: keysBySuffix.keys,
}
keyspan.SortKeys(dst.Keys)
// Update the span with the (potentially reduced) keys slice.
// NB: We don't re-sort by Trailer. The exported Coalesce function however
// will.
*dst = keysBySuffix.keys
return nil
}
// SortBySuffix sorts the provided keys by suffix.
func SortBySuffix(cmp base.Compare, keys []keyspan.Key) {
bySuffix := keysBySuffix{
cmp: cmp,
keys: keys,
}
sort.Sort(bySuffix)
}
type keysBySuffix struct {
cmp base.Compare
keys []keyspan.Key
@@ -238,7 +280,7 @@ type keysBySuffix struct {
// get searches for suffix among the first n keys in keys. If the suffix is
// found, it returns the index of the item with the suffix. If the suffix is not
// found, it returns n.
func (s keysBySuffix) get(n int, suffix []byte) (i int) {
func (s *keysBySuffix) get(n int, suffix []byte) (i int) {
// Binary search for the suffix to see if there's an existing key with the
// suffix. Only binary search among the first n items. get is called while
// appending new keys with suffixes that may sort before existing keys.
@@ -254,6 +296,6 @@ func (s keysBySuffix) get(n int, suffix []byte) (i int) {
return n
}
func (s keysBySuffix) Len() int { return len(s.keys) }
func (s keysBySuffix) Less(i, j int) bool { return s.cmp(s.keys[i].Suffix, s.keys[j].Suffix) < 0 }
func (s keysBySuffix) Swap(i, j int) { s.keys[i], s.keys[j] = s.keys[j], s.keys[i] }
func (s *keysBySuffix) Len() int { return len(s.keys) }
func (s *keysBySuffix) Less(i, j int) bool { return s.cmp(s.keys[i].Suffix, s.keys[j].Suffix) < 0 }
func (s *keysBySuffix) Swap(i, j int) { s.keys[i], s.keys[j] = s.keys[j], s.keys[i] }

View File

@@ -61,7 +61,7 @@ import (
// closure with the encoded internal keys that represent the Span's state. The
// keys and values passed to emit are only valid until the closure returns.
// If emit returns an error, Encode stops and returns the error.
func Encode(s keyspan.Span, emit func(k base.InternalKey, v []byte) error) error {
func Encode(s *keyspan.Span, emit func(k base.InternalKey, v []byte) error) error {
enc := Encoder{Emit: emit}
return enc.Encode(s)
}
@@ -82,7 +82,7 @@ type Encoder struct {
//
// The encoded key-value pair passed to Emit is only valid until the closure
// completes.
func (e *Encoder) Encode(s keyspan.Span) error {
func (e *Encoder) Encode(s *keyspan.Span) error {
if s.Empty() {
return nil
}
@@ -127,7 +127,7 @@ func (e *Encoder) Encode(s keyspan.Span) error {
// flush constructs internal keys for accumulated key state, and emits the
// internal keys.
func (e *Encoder) flush(s keyspan.Span, seqNum uint64, del bool) error {
func (e *Encoder) flush(s *keyspan.Span, seqNum uint64, del bool) error {
if len(e.sets) > 0 {
ik := base.MakeInternalKey(s.Start, seqNum, base.InternalKeyKindRangeKeySet)
l := EncodedSetValueLen(s.End, e.sets)

View File

@@ -0,0 +1,121 @@
/*
Copyright 2013 The Perkeep Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testkeys
import (
"strconv"
"github.com/cockroachdb/errors"
)
// parseUintBytes is like strconv.ParseUint, but using a []byte. Use of this
// function avoids an allocation when parsing an integer out of a []byte.
//
// This function is copied from go4.org/strconv.
func parseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) {
var cutoff, maxVal uint64
if bitSize == 0 {
bitSize = int(strconv.IntSize)
}
s0 := s
switch {
case len(s) < 1:
err = strconv.ErrSyntax
goto Error
case 2 <= base && base <= 36:
// valid base; nothing to do
case base == 0:
// Look for octal, hex prefix.
switch {
case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
base = 16
s = s[2:]
if len(s) < 1 {
err = strconv.ErrSyntax
goto Error
}
case s[0] == '0':
base = 8
default:
base = 10
}
default:
err = errors.New("invalid base " + strconv.Itoa(base))
goto Error
}
n = 0
cutoff = cutoff64(base)
maxVal = 1<<uint(bitSize) - 1
for i := 0; i < len(s); i++ {
var v byte
d := s[i]
switch {
case '0' <= d && d <= '9':
v = d - '0'
case 'a' <= d && d <= 'z':
v = d - 'a' + 10
case 'A' <= d && d <= 'Z':
v = d - 'A' + 10
default:
n = 0
err = strconv.ErrSyntax
goto Error
}
if int(v) >= base {
n = 0
err = strconv.ErrSyntax
goto Error
}
if n >= cutoff {
// n*base overflows
n = 1<<64 - 1
err = strconv.ErrRange
goto Error
}
n *= uint64(base)
n1 := n + uint64(v)
if n1 < n || n1 > maxVal {
// n+v overflows
n = 1<<64 - 1
err = strconv.ErrRange
goto Error
}
n = n1
}
return n, nil
Error:
return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
}
// Return the first number n such that n*base >= 1<<64.
func cutoff64(base int) uint64 {
if base < 2 {
return 0
}
return (1<<64-1)/uint64(base) + 1
}

View File

@@ -0,0 +1,349 @@
// Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
// Package testkeys provides facilities for generating and comparing
// human-readable test keys for use in tests and benchmarks. This package
// provides a single Comparer implementation that compares all keys generated
// by this package.
//
// Keys generated by this package may optionally have a 'suffix' encoding an
// MVCC timestamp. This suffix is of the form "@<integer>". Comparisons on the
// suffix are performed using integer value, not the byte representation.
package testkeys
import (
"bytes"
"fmt"
"math"
"strconv"
"strings"
"github.com/cockroachdb/pebble/internal/base"
)
const alpha = "abcdefghijklmnopqrstuvwxyz"
const suffixDelim = '@'
// MaxSuffixLen is the maximum length of a suffix generated by this package.
var MaxSuffixLen = 1 + len(fmt.Sprintf("%d", math.MaxInt64))
// Comparer is the comparer for test keys generated by this package.
var Comparer *base.Comparer = &base.Comparer{
Compare: compare,
Equal: func(a, b []byte) bool { return compare(a, b) == 0 },
AbbreviatedKey: func(k []byte) uint64 {
return base.DefaultComparer.AbbreviatedKey(k[:split(k)])
},
FormatKey: base.DefaultFormatter,
Separator: func(dst, a, b []byte) []byte {
ai := split(a)
if ai == len(a) {
return append(dst, a...)
}
bi := split(b)
if bi == len(b) {
return append(dst, a...)
}
// If the keys are the same just return a.
if bytes.Equal(a[:ai], b[:bi]) {
return append(dst, a...)
}
n := len(dst)
dst = base.DefaultComparer.Separator(dst, a[:ai], b[:bi])
// Did it pick a separator different than a[:ai] -- if not we can't do better than a.
buf := dst[n:]
if bytes.Equal(a[:ai], buf) {
return append(dst[:n], a...)
}
// The separator is > a[:ai], so return it
return dst
},
Successor: func(dst, a []byte) []byte {
ai := split(a)
if ai == len(a) {
return append(dst, a...)
}
n := len(dst)
dst = base.DefaultComparer.Successor(dst, a[:ai])
// Did it pick a successor different than a[:ai] -- if not we can't do better than a.
buf := dst[n:]
if bytes.Equal(a[:ai], buf) {
return append(dst[:n], a...)
}
// The successor is > a[:ai], so return it.
return dst
},
ImmediateSuccessor: func(dst, a []byte) []byte {
// TODO(jackson): Consider changing this Comparer to only support
// representable prefix keys containing characters a-z.
ai := split(a)
if ai != len(a) {
panic("pebble: ImmediateSuccessor invoked with a non-prefix key")
}
return append(append(dst, a...), 0x00)
},
Split: split,
Name: "pebble.internal.testkeys",
}
func compare(a, b []byte) int {
ai, bi := split(a), split(b)
if v := bytes.Compare(a[:ai], b[:bi]); v != 0 {
return v
}
if len(a[ai:]) == 0 {
if len(b[bi:]) == 0 {
return 0
}
return -1
} else if len(b[bi:]) == 0 {
return +1
}
return compareTimestamps(a[ai:], b[bi:])
}
func split(a []byte) int {
i := bytes.LastIndexByte(a, suffixDelim)
if i >= 0 {
return i
}
return len(a)
}
func compareTimestamps(a, b []byte) int {
ai, err := parseUintBytes(bytes.TrimPrefix(a, []byte{suffixDelim}), 10, 64)
if err != nil {
panic(fmt.Sprintf("invalid test mvcc timestamp %q", a))
}
bi, err := parseUintBytes(bytes.TrimPrefix(b, []byte{suffixDelim}), 10, 64)
if err != nil {
panic(fmt.Sprintf("invalid test mvcc timestamp %q", b))
}
switch {
case ai < bi:
return +1
case ai > bi:
return -1
default:
return 0
}
}
// Keyspace describes a finite keyspace of unsuffixed test keys.
type Keyspace interface {
// Count returns the number of keys that exist within this keyspace.
Count() int
// MaxLen returns the maximum length, in bytes, of a key within this
// keyspace. This is only guaranteed to return an upper bound.
MaxLen() int
// Slice returns the sub-keyspace from index i, inclusive, to index j,
// exclusive. The receiver is unmodified.
Slice(i, j int) Keyspace
// EveryN returns a key space that includes 1 key for every N keys in the
// original keyspace. The receiver is unmodified.
EveryN(n int) Keyspace
key(buf []byte, i int) int
}
// Divvy divides the provided keyspace into N equal portions, containing
// disjoint keys evenly distributed across the keyspace.
func Divvy(ks Keyspace, n int) []Keyspace {
ret := make([]Keyspace, n)
for i := 0; i < n; i++ {
ret[i] = ks.Slice(i, ks.Count()).EveryN(n)
}
return ret
}
// Alpha constructs a keyspace consisting of all keys containing characters a-z,
// with at most `maxLength` characters.
func Alpha(maxLength int) Keyspace {
return alphabet{
alphabet: []byte(alpha),
maxLength: maxLength,
increment: 1,
}
}
// KeyAt returns the i-th key within the keyspace with a suffix encoding the
// timestamp t.
func KeyAt(k Keyspace, i int, t int) []byte {
b := make([]byte, k.MaxLen()+MaxSuffixLen)
return b[:WriteKeyAt(b, k, i, t)]
}
// WriteKeyAt writes the i-th key within the keyspace to the buffer dst, with a
// suffix encoding the timestamp t suffix. It returns the number of bytes
// written.
func WriteKeyAt(dst []byte, k Keyspace, i int, t int) int {
n := WriteKey(dst, k, i)
n += WriteSuffix(dst[n:], t)
return n
}
// Suffix returns the test keys suffix representation of timestamp t.
func Suffix(t int) []byte {
b := make([]byte, MaxSuffixLen)
return b[:WriteSuffix(b, t)]
}
// SuffixLen returns the exact length of the given suffix when encoded.
func SuffixLen(t int) int {
// Begin at 1 for the '@' delimiter, 1 for a single digit.
n := 2
t /= 10
for t > 0 {
t /= 10
n++
}
return n
}
// ParseSuffix returns the integer representation of the encoded suffix.
func ParseSuffix(s []byte) (int, error) {
return strconv.Atoi(strings.TrimPrefix(string(s), string(suffixDelim)))
}
// WriteSuffix writes the test keys suffix representation of timestamp t to dst,
// returning the number of bytes written.
func WriteSuffix(dst []byte, t int) int {
dst[0] = suffixDelim
n := 1
n += len(strconv.AppendInt(dst[n:n], int64(t), 10))
return n
}
// Key returns the i-th unsuffixed key within the keyspace.
func Key(k Keyspace, i int) []byte {
b := make([]byte, k.MaxLen())
return b[:k.key(b, i)]
}
// WriteKey writes the i-th unsuffixed key within the keyspace to the buffer dst. It
// returns the number of bytes written.
func WriteKey(dst []byte, k Keyspace, i int) int {
return k.key(dst, i)
}
type alphabet struct {
alphabet []byte
maxLength int
headSkip int
tailSkip int
increment int
}
func (a alphabet) Count() int {
// Calculate the total number of keys, ignoring the increment.
total := (keyCount(len(a.alphabet), a.maxLength) - a.headSkip - a.tailSkip)
// The increment dictates that we take every N keys, where N = a.increment.
// Consider a total containing the 5 keys:
// a b c d e
// ^ ^ ^
// If the increment is 2, this keyspace includes 'a', 'c' and 'e'. After
// dividing by the increment, there may be remainder. If there is, there's
// one additional key in the alphabet.
count := total / a.increment
if total%a.increment > 0 {
count++
}
return count
}
func (a alphabet) MaxLen() int {
return a.maxLength
}
func (a alphabet) Slice(i, j int) Keyspace {
s := a
s.headSkip += i
s.tailSkip += a.Count() - j
return s
}
func (a alphabet) EveryN(n int) Keyspace {
s := a
s.increment *= n
return s
}
func keyCount(n, l int) int {
// The number of representable keys in the keyspace is a function of the
// length of the alphabet n and the max key length l. Consider how the
// number of representable keys grows as l increases:
//
// l = 1: n
// l = 2: n + n^2
// l = 3: n + n^2 + n^3
// ...
// Σ i=(1...l) n^i = n*(n^l - 1)/(n-1)
return (n * (int(math.Pow(float64(n), float64(l))) - 1)) / (n - 1)
}
func (a alphabet) key(buf []byte, idx int) int {
// This function generates keys of length 1..maxKeyLength, pulling
// characters from the alphabet. The idx determines which key to generate,
// generating the i-th lexicographically next key.
//
// The index to use is advanced by `headSkip`, allowing a keyspace to encode
// a subregion of the keyspace.
//
// Eg, alphabet = `ab`, maxKeyLength = 3:
//
// aaa aab aba abb baa bab bba bbb
// aa ab ba bb
// a b
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13
//
return generateAlphabetKey(buf, a.alphabet, (idx*a.increment)+a.headSkip,
keyCount(len(a.alphabet), a.maxLength))
}
func generateAlphabetKey(buf, alphabet []byte, i, keyCount int) int {
if keyCount == 0 || i > keyCount || i < 0 {
return 0
}
// Of the keyCount keys in the generative keyspace, how many are there
// starting with a particular character?
keysPerCharacter := keyCount / len(alphabet)
// Find the character that the key at index i starts with and set it.
characterIdx := i / keysPerCharacter
buf[0] = alphabet[characterIdx]
// Consider characterIdx = 0, pointing to 'a'.
//
// aaa aab aba abb baa bab bba bbb
// aa ab ba bb
// a b
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13
// \_________________________/
// |keysPerCharacter| keys
//
// In our recursive call, we reduce the problem to:
//
// aaa aab aba abb
// aa ab
// 0 1 2 3 4 5
// \________________________/
// |keysPerCharacter-1| keys
//
// In the subproblem, there are keysPerCharacter-1 keys (eliminating the
// just 'a' key, plus any keys beginning with any other character).
//
// The index i is also offset, reduced by the count of keys beginning with
// characters earlier in the alphabet (keysPerCharacter*characterIdx) and
// the key consisting of just the 'a' (-1).
i = i - keysPerCharacter*characterIdx - 1
return 1 + generateAlphabetKey(buf[1:], alphabet, i, keysPerCharacter-1)
}

File diff suppressed because it is too large Load Diff

View File

@@ -47,13 +47,13 @@ import (
// The per-level structure used by simpleMergingIter.
type simpleMergingIterLevel struct {
iter internalIterator
rangeDelIter keyspan.FragmentIterator
smallestUserKey []byte
iter internalIterator
rangeDelIter keyspan.FragmentIterator
levelIterBoundaryContext
iterKey *InternalKey
iterValue []byte
tombstone keyspan.Span
iterValue base.LazyValue
tombstone *keyspan.Span
}
type simpleMergingIter struct {
@@ -117,7 +117,7 @@ func (m *simpleMergingIter) positionRangeDels() {
if l.rangeDelIter == nil {
continue
}
l.tombstone = keyspan.SeekGE(m.heap.cmp, l.rangeDelIter, item.key.UserKey, m.snapshot)
l.tombstone = keyspan.SeekGE(m.heap.cmp, l.rangeDelIter, item.key.UserKey)
}
}
@@ -129,7 +129,7 @@ func (m *simpleMergingIter) step() bool {
item := &m.heap.items[0]
l := &m.levels[item.index]
// Sentinels are not relevant for this point checking.
if !item.key.IsExclusiveSentinel() && item.key.Visible(m.snapshot) {
if !item.key.IsExclusiveSentinel() && item.key.Visible(m.snapshot, base.InternalKeySeqNumMax) {
m.numPoints++
keyChanged := m.heap.cmp(item.key.UserKey, m.lastKey.UserKey) != 0
if !keyChanged {
@@ -157,6 +157,11 @@ func (m *simpleMergingIter) step() bool {
}
m.valueMerger = nil
}
itemValue, _, err := item.value.Value(nil)
if err != nil {
m.err = err
return false
}
if m.valueMerger != nil {
// Ongoing series of MERGE records.
switch item.key.Kind() {
@@ -168,7 +173,7 @@ func (m *simpleMergingIter) step() bool {
}
m.valueMerger = nil
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
m.err = m.valueMerger.MergeOlder(item.value)
m.err = m.valueMerger.MergeOlder(itemValue)
if m.err == nil {
var closer io.Closer
_, closer, m.err = m.valueMerger.Finish(true /* includesBase */)
@@ -178,7 +183,7 @@ func (m *simpleMergingIter) step() bool {
}
m.valueMerger = nil
case InternalKeyKindMerge:
m.err = m.valueMerger.MergeOlder(item.value)
m.err = m.valueMerger.MergeOlder(itemValue)
default:
m.err = errors.Errorf("pebble: invalid internal key kind %s in %s",
item.key.Pretty(m.formatKey),
@@ -187,7 +192,7 @@ func (m *simpleMergingIter) step() bool {
}
} else if item.key.Kind() == InternalKeyKindMerge && m.err == nil {
// New series of MERGE records.
m.valueMerger, m.err = m.merge(item.key.UserKey, item.value)
m.valueMerger, m.err = m.merge(item.key.UserKey, itemValue)
}
if m.err != nil {
m.err = errors.Wrapf(m.err, "merge processing error on key %s in %s",
@@ -207,7 +212,7 @@ func (m *simpleMergingIter) step() bool {
}
if (lvl.smallestUserKey == nil || m.heap.cmp(lvl.smallestUserKey, item.key.UserKey) <= 0) &&
lvl.tombstone.Contains(m.heap.cmp, item.key.UserKey) {
if lvl.tombstone.Covers(item.key.SeqNum()) {
if lvl.tombstone.CoversAt(m.snapshot, item.key.SeqNum()) {
m.err = errors.Errorf("tombstone %s in %s deletes key %s in %s",
lvl.tombstone.Pretty(m.formatKey), lvl.iter, item.key.Pretty(m.formatKey),
l.iter)
@@ -373,7 +378,7 @@ func checkRangeTombstones(c *checkConfig) error {
lf := files.Take()
atomicUnit, _ := expandToAtomicUnit(c.cmp, lf.Slice(), true /* disableIsCompacting */)
lower, upper := manifest.KeyRange(c.cmp, atomicUnit.Iter())
iterToClose, iter, err := c.newIters(lf.FileMetadata, nil, nil)
iterToClose, iter, err := c.newIters(lf.FileMetadata, nil, internalIterOpts{})
if err != nil {
return err
}
@@ -452,8 +457,8 @@ func addTombstonesFromIter(
}()
var prevTombstone keyspan.Span
for t := iter.First(); t.Valid(); t = iter.Next() {
t = t.Visible(seqNum)
for tomb := iter.First(); tomb != nil; tomb = iter.Next() {
t := tomb.Visible(seqNum)
if t.Empty() {
continue
}
@@ -634,9 +639,9 @@ func checkLevelsInternal(c *checkConfig) (err error) {
iterOpts := IterOptions{logger: c.logger}
li := &levelIter{}
li.init(iterOpts, c.cmp, nil /* split */, c.newIters, manifestIter,
manifest.L0Sublevel(sublevel), nil)
manifest.L0Sublevel(sublevel), internalIterOpts{})
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
li.initSmallestLargestUserKey(&mlevelAlloc[0].smallestUserKey, nil, nil)
li.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
mlevelAlloc[0].iter = li
mlevelAlloc = mlevelAlloc[1:]
}
@@ -648,9 +653,9 @@ func checkLevelsInternal(c *checkConfig) (err error) {
iterOpts := IterOptions{logger: c.logger}
li := &levelIter{}
li.init(iterOpts, c.cmp, nil /* split */, c.newIters,
current.Levels[level].Iter(), manifest.Level(level), nil)
current.Levels[level].Iter(), manifest.Level(level), internalIterOpts{})
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
li.initSmallestLargestUserKey(&mlevelAlloc[0].smallestUserKey, nil, nil)
li.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
mlevelAlloc[0].iter = li
mlevelAlloc = mlevelAlloc[1:]
}

View File

@@ -7,20 +7,30 @@ package pebble
import (
"fmt"
"runtime/debug"
"unsafe"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/invariants"
"github.com/cockroachdb/pebble/internal/keyspan"
"github.com/cockroachdb/pebble/internal/manifest"
"github.com/cockroachdb/pebble/sstable"
)
// tableNewIters creates a new point and range-del iterator for the given file
// number. If bytesIterated is specified, it is incremented as the given file is
// iterated through.
type tableNewIters func(
file *manifest.FileMetadata, opts *IterOptions, bytesIterated *uint64,
file *manifest.FileMetadata,
opts *IterOptions,
internalOpts internalIterOpts,
) (internalIterator, keyspan.FragmentIterator, error)
type internalIterOpts struct {
bytesIterated *uint64
stats *base.InternalIteratorStats
boundLimitedFilter sstable.BoundLimitedBlockPropertyFilter
}
// levelIter provides a merged view of the sstables in a level.
//
// levelIter is used during compaction and as part of the Iterator
@@ -62,6 +72,16 @@ type levelIter struct {
// (i.e. arrived at with Prev), or largest (arrived at with Next).
smallestBoundary *InternalKey
largestBoundary *InternalKey
// combinedIterState may be set when a levelIter is used during user
// iteration. Although levelIter only iterates over point keys, it's also
// responsible for lazily constructing the combined range & point iterator
// when it observes a file containing range keys. If the combined iter
// state's initialized field is true, the iterator is already using combined
// iterator, OR the iterator is not configured to use combined iteration. If
// it's false, the levelIter must set the `triggered` and `key` fields when
// the levelIter passes over a file containing range keys. See the
// lazyCombinedIter for more details.
combinedIterState *combinedIterState
// A synthetic boundary key to return when SeekPrefixGE finds an sstable
// which doesn't contain the search key, but which does contain range
// tombstones.
@@ -71,9 +91,19 @@ type levelIter struct {
// - err != nil
// - some other constraint, like the bounds in opts, caused the file at index to not
// be relevant to the iteration.
iter internalIteratorWithStats
iter internalIterator
iterFile *fileMetadata
newIters tableNewIters
// filteredIter is an optional interface that may be implemented by internal
// iterators that perform filtering of keys. When a new file's iterator is
// opened, it's tested to see if it implements filteredIter. If it does,
// it's stored here to allow the level iterator to recognize when keys were
// omitted from iteration results due to filtering. This is important when a
// file contains range deletions that may delete keys from other files. The
// levelIter must not advance to the next file until the mergingIter has
// advanced beyond the file's bounds. See
// levelIterBoundaryContext.isIgnorableBoundaryKey.
filteredIter filteredIter
newIters tableNewIters
// When rangeDelIterPtr != nil, the caller requires that *rangeDelIterPtr must
// point to a range del iterator corresponding to the current file. When this
// iterator returns nil, *rangeDelIterPtr should also be set to nil. Whenever
@@ -87,10 +117,8 @@ type levelIter struct {
rangeDelIterCopy keyspan.FragmentIterator
files manifest.LevelIterator
err error
// stats accumulates the stats of iters that have been closed.
stats InternalIteratorStats
// Pointer into this level's entry in `mergingIterLevel::smallestUserKey,largestUserKey`.
// Pointer into this level's entry in `mergingIterLevel::levelIterBoundaryContext`.
// We populate it with the corresponding bounds for the currently opened file. It is used for
// two purposes (described for forward iteration. The explanation for backward iteration is
// similar.)
@@ -136,17 +164,11 @@ type levelIter struct {
// - `*Boundary` is only populated when the iterator is positioned exactly on the sentinel key.
// - `*Boundary` can hold either the lower- or upper-bound, depending on the iterator direction.
// - `*Boundary` is not exposed to the next higher-level iterator, i.e., `mergingIter`.
smallestUserKey, largestUserKey *[]byte
isLargestUserKeyRangeDelSentinel *bool
boundaryContext *levelIterBoundaryContext
// Set to true iff the key returned by this iterator is a synthetic key
// derived from the iterator bounds. This is used to prevent the
// mergingIter from being stuck at such a synthetic key if it becomes the
// top element of the heap.
isSyntheticIterBoundsKey *bool
// bytesIterated keeps track of the number of bytes iterated during compaction.
bytesIterated *uint64
// internalOpts holds the internal iterator options to pass to the table
// cache when constructing new table iterators.
internalOpts internalIterOpts
// Disable invariant checks even if they are otherwise enabled. Used by tests
// which construct "impossible" situations (e.g. seeking to a key before the
@@ -154,6 +176,29 @@ type levelIter struct {
disableInvariants bool
}
// filteredIter is an additional interface implemented by iterators that may
// skip over point keys during iteration. The sstable.Iterator implements this
// interface.
type filteredIter interface {
// MaybeFilteredKeys may be called when an iterator is exhausted, indicating
// whether or not the iterator's last positioning method may have skipped
// any keys due to low-level filters.
//
// When an iterator is configured to use block-property filters, the
// low-level iterator may skip over blocks or whole sstables of keys.
// Implementations that implement skipping must implement this interface.
// Higher-level iterators require it to preserve invariants (eg, a levelIter
// used in a mergingIter must keep the file's range-del iterator open until
// the mergingIter has moved past the file's bounds, even if all of the
// file's point keys were filtered).
//
// MaybeFilteredKeys may always return false positives, that is it may
// return true when no keys were filtered. It should only be called when the
// iterator is exhausted. It must never return false negatives when the
// iterator is exhausted.
MaybeFilteredKeys() bool
}
// levelIter implements the base.InternalIterator interface.
var _ base.InternalIterator = (*levelIter)(nil)
@@ -169,7 +214,7 @@ func newLevelIter(
bytesIterated *uint64,
) *levelIter {
l := &levelIter{}
l.init(opts, cmp, split, newIters, files, level, bytesIterated)
l.init(opts, cmp, split, newIters, files, level, internalIterOpts{bytesIterated: bytesIterated})
return l
}
@@ -180,7 +225,7 @@ func (l *levelIter) init(
newIters tableNewIters,
files manifest.LevelIterator,
level manifest.Level,
bytesIterated *uint64,
internalOpts internalIterOpts,
) {
l.err = nil
l.level = level
@@ -195,51 +240,272 @@ func (l *levelIter) init(
l.split = split
l.iterFile = nil
l.newIters = newIters
l.files = files.Filter(manifest.KeyTypePoint)
l.bytesIterated = bytesIterated
l.files = files
l.internalOpts = internalOpts
}
func (l *levelIter) initRangeDel(rangeDelIter *keyspan.FragmentIterator) {
l.rangeDelIterPtr = rangeDelIter
}
func (l *levelIter) initSmallestLargestUserKey(
smallestUserKey, largestUserKey *[]byte, isLargestUserKeyRangeDelSentinel *bool,
) {
l.smallestUserKey = smallestUserKey
l.largestUserKey = largestUserKey
l.isLargestUserKeyRangeDelSentinel = isLargestUserKeyRangeDelSentinel
func (l *levelIter) initBoundaryContext(context *levelIterBoundaryContext) {
l.boundaryContext = context
}
func (l *levelIter) initIsSyntheticIterBoundsKey(isSyntheticIterBoundsKey *bool) {
l.isSyntheticIterBoundsKey = isSyntheticIterBoundsKey
func (l *levelIter) initCombinedIterState(state *combinedIterState) {
l.combinedIterState = state
}
func (l *levelIter) findFileGE(key []byte) *fileMetadata {
// Find the earliest file whose largest key is >= ikey.
func (l *levelIter) maybeTriggerCombinedIteration(file *fileMetadata, dir int) {
// If we encounter a file that contains range keys, we may need to
// trigger a switch to combined range-key and point-key iteration,
// if the *pebble.Iterator is configured for it. This switch is done
// lazily because range keys are intended to be rare, and
// constructing the range-key iterator substantially adds to the
// cost of iterator construction and seeking.
//
// If the earliest file has its largest key == ikey and that largest key is a
// range deletion sentinel, we know that we manufactured this sentinel to convert
// the exclusive range deletion end key into an inclusive key (reminder: [start, end)#seqnum
// is the form of a range deletion sentinel which can contribute a largest key = end#sentinel).
// In this case we don't return this as the earliest file since there is nothing actually
// equal to key in it.
// If l.combinedIterState.initialized is already true, either the
// iterator is already using combined iteration or the iterator is not
// configured to observe range keys. Either way, there's nothing to do.
// If false, trigger the switch to combined iteration, using the the
// file's bounds to seek the range-key iterator appropriately.
//
// Additionally, this prevents loading untruncated range deletions from a table which can't
// possibly contain the target key and is required for correctness by mergingIter.SeekGE
// (see the comment in that function).
// We only need to trigger combined iteration if the file contains
// RangeKeySets: if there are only Unsets and Dels, the user will observe no
// range keys regardless. If this file has table stats available, they'll
// tell us whether the file has any RangeKeySets. Otherwise, we must
// fallback to assuming it does if HasRangeKeys=true.
if file != nil && file.HasRangeKeys && l.combinedIterState != nil && !l.combinedIterState.initialized &&
(l.upper == nil || l.cmp(file.SmallestRangeKey.UserKey, l.upper) < 0) &&
(l.lower == nil || l.cmp(file.LargestRangeKey.UserKey, l.lower) > 0) &&
(!file.StatsValid() || file.Stats.NumRangeKeySets > 0) {
// The file contains range keys, and we're not using combined iteration yet.
// Trigger a switch to combined iteration. It's possible that a switch has
// already been triggered if multiple levels encounter files containing
// range keys while executing a single mergingIter operation. In this case,
// we need to compare the existing key recorded to l.combinedIterState.key,
// adjusting it if our key is smaller (forward iteration) or larger
// (backward iteration) than the existing key.
//
// These key comparisons are only required during a single high-level
// iterator operation. When the high-level iter op completes,
// iinitialized will be true, and future calls to this function will be
// no-ops.
switch dir {
case +1:
if !l.combinedIterState.triggered {
l.combinedIterState.triggered = true
l.combinedIterState.key = file.SmallestRangeKey.UserKey
} else if l.cmp(l.combinedIterState.key, file.SmallestRangeKey.UserKey) > 0 {
l.combinedIterState.key = file.SmallestRangeKey.UserKey
}
case -1:
if !l.combinedIterState.triggered {
l.combinedIterState.triggered = true
l.combinedIterState.key = file.LargestRangeKey.UserKey
} else if l.cmp(l.combinedIterState.key, file.LargestRangeKey.UserKey) < 0 {
l.combinedIterState.key = file.LargestRangeKey.UserKey
}
}
}
}
m := l.files.SeekGE(l.cmp, key)
for m != nil && m.LargestPointKey.IsExclusiveSentinel() &&
l.cmp(m.LargestPointKey.UserKey, key) == 0 {
m = l.files.Next()
func (l *levelIter) findFileGE(key []byte, flags base.SeekGEFlags) *fileMetadata {
// Find the earliest file whose largest key is >= key.
if invariants.Enabled && flags.TrySeekUsingNext() && disableSeekOpt(key, uintptr(unsafe.Pointer(l))) {
flags = flags.DisableTrySeekUsingNext()
}
// Ordinarily we seek the LevelIterator using SeekGE. In some instances, we
// Next instead. In other instances, we try Next-ing first, falling back to
// seek:
// a) flags.TrySeekUsingNext(): The top-level Iterator knows we're seeking
// to a key later than the current iterator position. We don't know how
// much later the seek key is, so it's possible there are many sstables
// between the current position and the seek key. However in most real-
// world use cases, the seek key is likely to be nearby. Rather than
// performing a log(N) seek through the file metadata, we next a few
// times from from our existing location. If we don't find a file whose
// largest is >= key within a few nexts, we fall back to seeking.
//
// Note that in this case, the file returned by findFileGE may be
// different than the file returned by a raw binary search (eg, when
// TrySeekUsingNext=false). This is possible because the most recent
// positioning operation may have already determined that previous
// files' keys that are ≥ key are all deleted. This information is
// encoded within the iterator's current iterator position and is
// unavailable to a fresh binary search.
//
// b) flags.RelativeSeek(): The merging iterator decided to re-seek this
// level according to a range tombstone. When lazy combined iteration
// is enabled, the level iterator is responsible for watching for
// files containing range keys and triggering the switch to combined
// iteration when such a file is observed. If a range deletion was
// observed in a higher level causing the merging iterator to seek the
// level to the range deletion's end key, we need to check whether all
// of the files between the old position and the new position contain
// any range keys.
//
// In this scenario, we don't seek the LevelIterator and instead we
// Next it, one file at a time, checking each for range keys. The
// merging iterator sets this flag to inform us that we're moving
// forward relative to the existing position and that we must examine
// each intermediate sstable's metadata for lazy-combined iteration.
// In this case, we only Next and never Seek. We set nextsUntilSeek=-1
// to signal this intention.
//
// NB: At most one of flags.RelativeSeek() and flags.TrySeekUsingNext() may
// be set, because the merging iterator re-seeks relative seeks with
// explicitly only the RelativeSeek flag set.
var nextsUntilSeek int
var nextInsteadOfSeek bool
if flags.TrySeekUsingNext() {
nextInsteadOfSeek = true
nextsUntilSeek = 4 // arbitrary
}
if flags.RelativeSeek() && l.combinedIterState != nil && !l.combinedIterState.initialized {
nextInsteadOfSeek = true
nextsUntilSeek = -1
}
var m *fileMetadata
if nextInsteadOfSeek {
m = l.iterFile
} else {
m = l.files.SeekGE(l.cmp, key)
}
// The below loop has a bit of an unusual organization. There are several
// conditions under which we need to Next to a later file. If none of those
// conditions are met, the file in `m` is okay to return. The loop body is
// structured with a series of if statements, each of which may continue the
// loop to the next file. If none of the statements are met, the end of the
// loop body is a break.
for m != nil {
if m.HasRangeKeys {
l.maybeTriggerCombinedIteration(m, +1)
// Some files may only contain range keys, which we can skip.
// NB: HasPointKeys=true if the file contains any points or range
// deletions (which delete points).
if !m.HasPointKeys {
m = l.files.Next()
continue
}
}
// This file has point keys.
//
// However, there are a couple reasons why `m` may not be positioned ≥
// `key` yet:
//
// 1. If SeekGE(key) landed on a file containing range keys, the file
// may contain range keys ≥ `key` but no point keys ≥ `key`.
// 2. When nexting instead of seeking, we must check to see whether
// we've nexted sufficiently far, or we need to next again.
//
// If the file does not contain point keys ≥ `key`, next to continue
// looking for a file that does.
if (m.HasRangeKeys || nextInsteadOfSeek) && l.cmp(m.LargestPointKey.UserKey, key) < 0 {
// If nextInsteadOfSeek is set and nextsUntilSeek is non-negative,
// the iterator has been nexting hoping to discover the relevant
// file without seeking. It's exhausted the allotted nextsUntilSeek
// and should seek to the sought key.
if nextInsteadOfSeek && nextsUntilSeek == 0 {
nextInsteadOfSeek = false
m = l.files.SeekGE(l.cmp, key)
continue
} else if nextsUntilSeek > 0 {
nextsUntilSeek--
}
m = l.files.Next()
continue
}
// This file has a point key bound ≥ `key`. But the largest point key
// bound may still be a range deletion sentinel, which is exclusive. In
// this case, the file doesn't actually contain any point keys equal to
// `key`. We next to keep searching for a file that actually contains
// point keys ≥ key.
//
// Additionally, this prevents loading untruncated range deletions from
// a table which can't possibly contain the target key and is required
// for correctness by mergingIter.SeekGE (see the comment in that
// function).
if m.LargestPointKey.IsExclusiveSentinel() && l.cmp(m.LargestPointKey.UserKey, key) == 0 {
m = l.files.Next()
continue
}
// This file contains point keys ≥ `key`. Break and return it.
break
}
return m
}
func (l *levelIter) findFileLT(key []byte) *fileMetadata {
func (l *levelIter) findFileLT(key []byte, flags base.SeekLTFlags) *fileMetadata {
// Find the last file whose smallest key is < ikey.
return l.files.SeekLT(l.cmp, key)
// Ordinarily we seek the LevelIterator using SeekLT.
//
// When lazy combined iteration is enabled, there's a complication. The
// level iterator is responsible for watching for files containing range
// keys and triggering the switch to combined iteration when such a file is
// observed. If a range deletion was observed in a higher level causing the
// merging iterator to seek the level to the range deletion's start key, we
// need to check whether all of the files between the old position and the
// new position contain any range keys.
//
// In this scenario, we don't seek the LevelIterator and instead we Prev it,
// one file at a time, checking each for range keys.
prevInsteadOfSeek := flags.RelativeSeek() && l.combinedIterState != nil && !l.combinedIterState.initialized
var m *fileMetadata
if prevInsteadOfSeek {
m = l.iterFile
} else {
m = l.files.SeekLT(l.cmp, key)
}
// The below loop has a bit of an unusual organization. There are several
// conditions under which we need to Prev to a previous file. If none of
// those conditions are met, the file in `m` is okay to return. The loop
// body is structured with a series of if statements, each of which may
// continue the loop to the previous file. If none of the statements are
// met, the end of the loop body is a break.
for m != nil {
if m.HasRangeKeys {
l.maybeTriggerCombinedIteration(m, -1)
// Some files may only contain range keys, which we can skip.
// NB: HasPointKeys=true if the file contains any points or range
// deletions (which delete points).
if !m.HasPointKeys {
m = l.files.Prev()
continue
}
}
// This file has point keys.
//
// However, there are a couple reasons why `m` may not be positioned <
// `key` yet:
//
// 1. If SeekLT(key) landed on a file containing range keys, the file
// may contain range keys < `key` but no point keys < `key`.
// 2. When preving instead of seeking, we must check to see whether
// we've preved sufficiently far, or we need to prev again.
//
// If the file does not contain point keys < `key`, prev to continue
// looking for a file that does.
if (m.HasRangeKeys || prevInsteadOfSeek) && l.cmp(m.SmallestPointKey.UserKey, key) >= 0 {
m = l.files.Prev()
continue
}
// This file contains point keys < `key`. Break and return it.
break
}
return m
}
// Init the iteration bounds for the current table. Returns -1 if the table
@@ -248,11 +514,11 @@ func (l *levelIter) findFileLT(key []byte) *fileMetadata {
func (l *levelIter) initTableBounds(f *fileMetadata) int {
l.tableOpts.LowerBound = l.lower
if l.tableOpts.LowerBound != nil {
if l.cmp(f.Largest.UserKey, l.tableOpts.LowerBound) < 0 {
if l.cmp(f.LargestPointKey.UserKey, l.tableOpts.LowerBound) < 0 {
// The largest key in the sstable is smaller than the lower bound.
return -1
}
if l.cmp(l.tableOpts.LowerBound, f.Smallest.UserKey) <= 0 {
if l.cmp(l.tableOpts.LowerBound, f.SmallestPointKey.UserKey) <= 0 {
// The lower bound is smaller or equal to the smallest key in the
// table. Iteration within the table does not need to check the lower
// bound.
@@ -261,15 +527,16 @@ func (l *levelIter) initTableBounds(f *fileMetadata) int {
}
l.tableOpts.UpperBound = l.upper
if l.tableOpts.UpperBound != nil {
if l.cmp(f.Smallest.UserKey, l.tableOpts.UpperBound) >= 0 {
if l.cmp(f.SmallestPointKey.UserKey, l.tableOpts.UpperBound) >= 0 {
// The smallest key in the sstable is greater than or equal to the upper
// bound.
return 1
}
if l.cmp(l.tableOpts.UpperBound, f.Largest.UserKey) > 0 {
if l.cmp(l.tableOpts.UpperBound, f.LargestPointKey.UserKey) > 0 {
// The upper bound is greater than the largest key in the
// table. Iteration within the table does not need to check the upper
// bound. NB: tableOpts.UpperBound is exclusive and f.Largest is inclusive.
// bound. NB: tableOpts.UpperBound is exclusive and f.LargestPointKey is
// inclusive.
l.tableOpts.UpperBound = nil
}
}
@@ -287,8 +554,9 @@ const (
func (l *levelIter) loadFile(file *fileMetadata, dir int) loadFileReturnIndicator {
l.smallestBoundary = nil
l.largestBoundary = nil
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = false
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
l.boundaryContext.isIgnorableBoundaryKey = false
}
if l.iterFile == file {
if l.err != nil {
@@ -301,6 +569,16 @@ func (l *levelIter) loadFile(file *fileMetadata, dir int) loadFileReturnIndicato
if l.rangeDelIterPtr != nil {
*l.rangeDelIterPtr = l.rangeDelIterCopy
}
// There are a few reasons we might not have triggered combined
// iteration yet, even though we already had `file` open.
// 1. If the bounds changed, we might have previously avoided
// switching to combined iteration because the bounds excluded
// the range keys contained in this file.
// 2. If an existing iterator was reconfigured to iterate over range
// keys (eg, using SetOptions), then we wouldn't have triggered
// the switch to combined iteration yet.
l.maybeTriggerCombinedIteration(file, dir)
return fileAlreadyLoaded
}
// We were already at file, but don't have an iterator, probably because the file was
@@ -322,6 +600,18 @@ func (l *levelIter) loadFile(file *fileMetadata, dir int) loadFileReturnIndicato
return noFileLoaded
}
l.maybeTriggerCombinedIteration(file, dir)
if !file.HasPointKeys {
switch dir {
case +1:
file = l.files.Next()
continue
case -1:
file = l.files.Prev()
continue
}
}
switch l.initTableBounds(file) {
case -1:
// The largest key in the sstable is smaller than the lower bound.
@@ -342,25 +632,30 @@ func (l *levelIter) loadFile(file *fileMetadata, dir int) loadFileReturnIndicato
var rangeDelIter keyspan.FragmentIterator
var iter internalIterator
iter, rangeDelIter, l.err = l.newIters(l.files.Current(), &l.tableOpts, l.bytesIterated)
l.iter = base.WrapIterWithStats(iter)
iter, rangeDelIter, l.err = l.newIters(l.files.Current(), &l.tableOpts, l.internalOpts)
l.iter = iter
if l.err != nil {
return noFileLoaded
}
if rangeDelIter != nil {
if fi, ok := iter.(filteredIter); ok {
l.filteredIter = fi
} else {
l.filteredIter = nil
}
} else {
l.filteredIter = nil
}
if l.rangeDelIterPtr != nil {
*l.rangeDelIterPtr = rangeDelIter
l.rangeDelIterCopy = rangeDelIter
} else if rangeDelIter != nil {
rangeDelIter.Close()
}
if l.smallestUserKey != nil {
*l.smallestUserKey = file.Smallest.UserKey
}
if l.largestUserKey != nil {
*l.largestUserKey = file.Largest.UserKey
}
if l.isLargestUserKeyRangeDelSentinel != nil {
*l.isLargestUserKeyRangeDelSentinel = file.Largest.IsExclusiveSentinel()
if l.boundaryContext != nil {
l.boundaryContext.smallestUserKey = file.Smallest.UserKey
l.boundaryContext.largestUserKey = file.Largest.UserKey
l.boundaryContext.isLargestUserKeyRangeDelSentinel = file.Largest.IsExclusiveSentinel()
}
return newFileLoaded
}
@@ -368,7 +663,7 @@ func (l *levelIter) loadFile(file *fileMetadata, dir int) loadFileReturnIndicato
// In race builds we verify that the keys returned by levelIter lie within
// [lower,upper).
func (l *levelIter) verify(key *InternalKey, val []byte) (*InternalKey, []byte) {
func (l *levelIter) verify(key *InternalKey, val base.LazyValue) (*InternalKey, base.LazyValue) {
// Note that invariants.Enabled is a compile time constant, which means the
// block of code will be compiled out of normal builds making this method
// eligible for inlining. Do not change this to use a variable.
@@ -386,116 +681,123 @@ func (l *levelIter) verify(key *InternalKey, val []byte) (*InternalKey, []byte)
return key, val
}
func (l *levelIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) {
func (l *levelIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) {
l.err = nil // clear cached iteration error
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = false
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
l.boundaryContext.isIgnorableBoundaryKey = false
}
// NB: the top-level Iterator has already adjusted key based on
// IterOptions.LowerBound.
loadFileIndicator := l.loadFile(l.findFileGE(key), +1)
loadFileIndicator := l.loadFile(l.findFileGE(key, flags), +1)
if loadFileIndicator == noFileLoaded {
return nil, nil
return nil, base.LazyValue{}
}
if loadFileIndicator == newFileLoaded {
// File changed, so l.iter has changed, and that iterator is not
// positioned appropriately.
trySeekUsingNext = false
flags = flags.DisableTrySeekUsingNext()
}
if ikey, val := l.iter.SeekGE(key, trySeekUsingNext); ikey != nil {
if ikey, val := l.iter.SeekGE(key, flags); ikey != nil {
return l.verify(ikey, val)
}
return l.verify(l.skipEmptyFileForward())
}
func (l *levelIter) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
l.err = nil // clear cached iteration error
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = false
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
l.boundaryContext.isIgnorableBoundaryKey = false
}
// NB: the top-level Iterator has already adjusted key based on
// IterOptions.LowerBound.
loadFileIndicator := l.loadFile(l.findFileGE(key), +1)
loadFileIndicator := l.loadFile(l.findFileGE(key, flags), +1)
if loadFileIndicator == noFileLoaded {
return nil, nil
return nil, base.LazyValue{}
}
if loadFileIndicator == newFileLoaded {
// File changed, so l.iter has changed, and that iterator is not
// positioned appropriately.
trySeekUsingNext = false
flags = flags.DisableTrySeekUsingNext()
}
if key, val := l.iter.SeekPrefixGE(prefix, key, trySeekUsingNext); key != nil {
if key, val := l.iter.SeekPrefixGE(prefix, key, flags); key != nil {
return l.verify(key, val)
}
// When SeekPrefixGE returns nil, we have not necessarily reached the end of
// the sstable. All we know is that a key with prefix does not exist in the
// current sstable. We do know that the key lies within the bounds of the
// table as findFileGE found the table where key <= meta.Largest. We treat
// this case the same as SeekGE where an upper-bound resides within the
// sstable and generate a synthetic boundary key.
// table as findFileGE found the table where key <= meta.Largest. We return
// the table's bound with isIgnorableBoundaryKey set.
if l.rangeDelIterPtr != nil && *l.rangeDelIterPtr != nil {
if l.tableOpts.UpperBound != nil {
l.syntheticBoundary.UserKey = l.tableOpts.UpperBound
l.syntheticBoundary.Trailer = InternalKeyRangeDeleteSentinel
l.largestBoundary = &l.syntheticBoundary
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = true
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = true
l.boundaryContext.isIgnorableBoundaryKey = false
}
return l.verify(l.largestBoundary, nil)
return l.verify(l.largestBoundary, base.LazyValue{})
}
l.syntheticBoundary = l.iterFile.Largest
l.syntheticBoundary.SetKind(InternalKeyKindRangeDelete)
l.largestBoundary = &l.syntheticBoundary
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = false
// Return the file's largest bound, ensuring this file stays open until
// the mergingIter advances beyond the file's bounds. We set
// isIgnorableBoundaryKey to signal that the actual key returned should
// be ignored, and does not represent a real key in the database.
l.largestBoundary = &l.iterFile.LargestPointKey
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
l.boundaryContext.isIgnorableBoundaryKey = true
}
return l.verify(l.largestBoundary, nil)
return l.verify(l.largestBoundary, base.LazyValue{})
}
// It is possible that we are here because bloom filter matching failed.
// In that case it is likely that all keys matching the prefix are wholly
// It is possible that we are here because bloom filter matching failed. In
// that case it is likely that all keys matching the prefix are wholly
// within the current file and cannot be in the subsequent file. In that
// case we don't want to go to the next file, since loading and seeking in
// there has some cost. Additionally, for sparse key spaces, loading the
// next file will defeat the optimization for the next SeekPrefixGE that
// is called with trySeekUsingNext=true, since for sparse key spaces it is
// next file will defeat the optimization for the next SeekPrefixGE that is
// called with flags.TrySeekUsingNext(), since for sparse key spaces it is
// likely that the next key will also be contained in the current file.
if n := l.split(l.iterFile.Largest.UserKey); l.cmp(prefix, l.iterFile.Largest.UserKey[:n]) < 0 {
return nil, nil
if n := l.split(l.iterFile.LargestPointKey.UserKey); l.cmp(prefix, l.iterFile.LargestPointKey.UserKey[:n]) < 0 {
return nil, base.LazyValue{}
}
return l.verify(l.skipEmptyFileForward())
}
func (l *levelIter) SeekLT(key []byte) (*InternalKey, []byte) {
func (l *levelIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
l.err = nil // clear cached iteration error
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = false
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
l.boundaryContext.isIgnorableBoundaryKey = false
}
// NB: the top-level Iterator has already adjusted key based on
// IterOptions.UpperBound.
if l.loadFile(l.findFileLT(key), -1) == noFileLoaded {
return nil, nil
if l.loadFile(l.findFileLT(key, flags), -1) == noFileLoaded {
return nil, base.LazyValue{}
}
if key, val := l.iter.SeekLT(key); key != nil {
if key, val := l.iter.SeekLT(key, flags); key != nil {
return l.verify(key, val)
}
return l.verify(l.skipEmptyFileBackward())
}
func (l *levelIter) First() (*InternalKey, []byte) {
func (l *levelIter) First() (*InternalKey, base.LazyValue) {
l.err = nil // clear cached iteration error
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = false
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
l.boundaryContext.isIgnorableBoundaryKey = false
}
// NB: the top-level Iterator will call SeekGE if IterOptions.LowerBound is
// set.
if l.loadFile(l.files.First(), +1) == noFileLoaded {
return nil, nil
return nil, base.LazyValue{}
}
if key, val := l.iter.First(); key != nil {
return l.verify(key, val)
@@ -503,16 +805,17 @@ func (l *levelIter) First() (*InternalKey, []byte) {
return l.verify(l.skipEmptyFileForward())
}
func (l *levelIter) Last() (*InternalKey, []byte) {
func (l *levelIter) Last() (*InternalKey, base.LazyValue) {
l.err = nil // clear cached iteration error
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = false
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
l.boundaryContext.isIgnorableBoundaryKey = false
}
// NB: the top-level Iterator will call SeekLT if IterOptions.UpperBound is
// set.
if l.loadFile(l.files.Last(), -1) == noFileLoaded {
return nil, nil
return nil, base.LazyValue{}
}
if key, val := l.iter.Last(); key != nil {
return l.verify(key, val)
@@ -520,12 +823,13 @@ func (l *levelIter) Last() (*InternalKey, []byte) {
return l.verify(l.skipEmptyFileBackward())
}
func (l *levelIter) Next() (*InternalKey, []byte) {
func (l *levelIter) Next() (*InternalKey, base.LazyValue) {
if l.err != nil || l.iter == nil {
return nil, nil
return nil, base.LazyValue{}
}
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = false
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
l.boundaryContext.isIgnorableBoundaryKey = false
}
switch {
@@ -539,7 +843,7 @@ func (l *levelIter) Next() (*InternalKey, []byte) {
if l.rangeDelIterPtr != nil {
*l.rangeDelIterPtr = nil
}
return nil, nil
return nil, base.LazyValue{}
}
// We're stepping past the boundary key, so now we can load the next file.
if l.loadFile(l.files.Next(), +1) != noFileLoaded {
@@ -548,7 +852,7 @@ func (l *levelIter) Next() (*InternalKey, []byte) {
}
return l.verify(l.skipEmptyFileForward())
}
return nil, nil
return nil, base.LazyValue{}
default:
// Reset the smallest boundary since we're moving away from it.
@@ -560,12 +864,13 @@ func (l *levelIter) Next() (*InternalKey, []byte) {
return l.verify(l.skipEmptyFileForward())
}
func (l *levelIter) Prev() (*InternalKey, []byte) {
func (l *levelIter) Prev() (*InternalKey, base.LazyValue) {
if l.err != nil || l.iter == nil {
return nil, nil
return nil, base.LazyValue{}
}
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = false
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
l.boundaryContext.isIgnorableBoundaryKey = false
}
switch {
@@ -579,7 +884,7 @@ func (l *levelIter) Prev() (*InternalKey, []byte) {
if l.rangeDelIterPtr != nil {
*l.rangeDelIterPtr = nil
}
return nil, nil
return nil, base.LazyValue{}
}
// We're stepping past the boundary key, so now we can load the prev file.
if l.loadFile(l.files.Prev(), -1) != noFileLoaded {
@@ -588,7 +893,7 @@ func (l *levelIter) Prev() (*InternalKey, []byte) {
}
return l.verify(l.skipEmptyFileBackward())
}
return nil, nil
return nil, base.LazyValue{}
default:
// Reset the largest boundary since we're moving away from it.
@@ -600,9 +905,9 @@ func (l *levelIter) Prev() (*InternalKey, []byte) {
return l.verify(l.skipEmptyFileBackward())
}
func (l *levelIter) skipEmptyFileForward() (*InternalKey, []byte) {
func (l *levelIter) skipEmptyFileForward() (*InternalKey, base.LazyValue) {
var key *InternalKey
var val []byte
var val base.LazyValue
// The first iteration of this loop starts with an already exhausted
// l.iter. The reason for the exhaustion is either that we iterated to the
// end of the sstable, or our iteration was terminated early due to the
@@ -636,35 +941,61 @@ func (l *levelIter) skipEmptyFileForward() (*InternalKey, []byte) {
l.syntheticBoundary.UserKey = l.tableOpts.UpperBound
l.syntheticBoundary.Trailer = InternalKeyRangeDeleteSentinel
l.largestBoundary = &l.syntheticBoundary
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = true
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = true
}
return l.largestBoundary, nil
return l.largestBoundary, base.LazyValue{}
}
// Else there are no range deletions in this sstable. This
// helps with performance when many levels are populated with
// sstables and most don't have any actual keys within the
// bounds.
return nil, nil
return nil, base.LazyValue{}
}
// If the boundary is a range deletion tombstone, return that key.
if l.iterFile.Largest.Kind() == InternalKeyKindRangeDelete {
if l.iterFile.LargestPointKey.Kind() == InternalKeyKindRangeDelete {
l.largestBoundary = &l.iterFile.LargestPointKey
return l.largestBoundary, base.LazyValue{}
}
// If the last point iterator positioning op might've skipped keys,
// it's possible the file's range deletions are still relevant to
// other levels. Return the largest boundary as a special ignorable
// marker to avoid advancing to the next file.
//
// The sstable iterator cannot guarantee that keys were skipped. A
// SeekGE that lands on a index separator k only knows that the
// block at the index entry contains keys ≤ k. We can't know whether
// there were actually keys between the seek key and the index
// separator key. If the block is then excluded due to block
// property filters, the iterator does not know whether keys were
// actually skipped by the block's exclusion.
//
// Since MaybeFilteredKeys cannot guarantee that keys were skipped,
// it's possible l.iterFile.Largest was already returned. Returning
// l.iterFile.Largest again is a violation of the strict
// monotonicity normally provided. The mergingIter's heap can
// tolerate this repeat key and in this case will keep the level at
// the top of the heap and immediately skip the entry, advancing to
// the next file.
if *l.rangeDelIterPtr != nil && l.filteredIter != nil &&
l.filteredIter.MaybeFilteredKeys() {
l.largestBoundary = &l.iterFile.Largest
return l.largestBoundary, nil
l.boundaryContext.isIgnorableBoundaryKey = true
return l.largestBoundary, base.LazyValue{}
}
}
// Current file was exhausted. Move to the next file.
if l.loadFile(l.files.Next(), +1) == noFileLoaded {
return nil, nil
return nil, base.LazyValue{}
}
}
return key, val
}
func (l *levelIter) skipEmptyFileBackward() (*InternalKey, []byte) {
func (l *levelIter) skipEmptyFileBackward() (*InternalKey, base.LazyValue) {
var key *InternalKey
var val []byte
var val base.LazyValue
// The first iteration of this loop starts with an already exhausted
// l.iter. The reason for the exhaustion is either that we iterated to the
// end of the sstable, or our iteration was terminated early due to the
@@ -697,27 +1028,52 @@ func (l *levelIter) skipEmptyFileBackward() (*InternalKey, []byte) {
l.syntheticBoundary.UserKey = l.tableOpts.LowerBound
l.syntheticBoundary.Trailer = InternalKeyRangeDeleteSentinel
l.smallestBoundary = &l.syntheticBoundary
if l.isSyntheticIterBoundsKey != nil {
*l.isSyntheticIterBoundsKey = true
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = true
}
return l.smallestBoundary, nil
return l.smallestBoundary, base.LazyValue{}
}
// Else there are no range deletions in this sstable. This
// helps with performance when many levels are populated with
// sstables and most don't have any actual keys within the
// bounds.
return nil, nil
return nil, base.LazyValue{}
}
// If the boundary is a range deletion tombstone, return that key.
if l.iterFile.Smallest.Kind() == InternalKeyKindRangeDelete {
if l.iterFile.SmallestPointKey.Kind() == InternalKeyKindRangeDelete {
l.smallestBoundary = &l.iterFile.SmallestPointKey
return l.smallestBoundary, base.LazyValue{}
}
// If the last point iterator positioning op skipped keys, it's
// possible the file's range deletions are still relevant to other
// levels. Return the smallest boundary as a special ignorable key
// to avoid advancing to the next file.
//
// The sstable iterator cannot guarantee that keys were skipped. A
// SeekGE that lands on a index separator k only knows that the
// block at the index entry contains keys ≤ k. We can't know whether
// there were actually keys between the seek key and the index
// separator key. If the block is then excluded due to block
// property filters, the iterator does not know whether keys were
// actually skipped by the block's exclusion.
//
// Since MaybeFilteredKeys cannot guarantee that keys were skipped,
// it's possible l.iterFile.Smallest was already returned. Returning
// l.iterFile.Smallest again is a violation of the strict
// monotonicity normally provided. The mergingIter's heap can
// tolerate this repeat key and in this case will keep the level at
// the top of the heap and immediately skip the entry, advancing to
// the next file.
if *l.rangeDelIterPtr != nil && l.filteredIter != nil && l.filteredIter.MaybeFilteredKeys() {
l.smallestBoundary = &l.iterFile.Smallest
return l.smallestBoundary, nil
l.boundaryContext.isIgnorableBoundaryKey = true
return l.smallestBoundary, base.LazyValue{}
}
}
// Current file was exhausted. Move to the previous file.
if l.loadFile(l.files.Prev(), -1) == noFileLoaded {
return nil, nil
return nil, base.LazyValue{}
}
}
return key, val
@@ -732,7 +1088,6 @@ func (l *levelIter) Error() error {
func (l *levelIter) Close() error {
if l.iter != nil {
l.stats.Merge(l.iter.Stats())
l.err = l.iter.Close()
l.iter = nil
}
@@ -773,21 +1128,4 @@ func (l *levelIter) String() string {
return fmt.Sprintf("%s: fileNum=<nil>", l.level)
}
var _ internalIteratorWithStats = &levelIter{}
// Stats implements InternalIteratorWithStats.
func (l *levelIter) Stats() base.InternalIteratorStats {
stats := l.stats
if l.iter != nil {
stats.Merge(l.iter.Stats())
}
return stats
}
// ResetStats implements InternalIteratorWithStats.
func (l *levelIter) ResetStats() {
l.stats = base.InternalIteratorStats{}
if l.iter != nil {
l.iter.ResetStats()
}
}
var _ internalIterator = &levelIter{}

View File

@@ -257,6 +257,10 @@ func (m *memTable) newRangeKeyIter(*IterOptions) keyspan.FragmentIterator {
return keyspan.NewIter(m.cmp, rangeKeys)
}
func (m *memTable) containsRangeKeys() bool {
return atomic.LoadUint32(&m.rangeKeys.atomicCount) > 0
}
func (m *memTable) availBytes() uint32 {
a := m.skl.Arena()
if atomic.LoadInt32(&m.writerRefs) == 1 {
@@ -332,7 +336,7 @@ func (f *keySpanFrags) get(
it := skl.NewIter(nil, nil)
var keysDst []keyspan.Key
for key, val := it.First(); key != nil; key, val = it.Next() {
s, err := constructSpan(*key, val, keysDst)
s, err := constructSpan(*key, val.InPlaceValue(), keysDst)
if err != nil {
panic(err)
}
@@ -348,7 +352,7 @@ func (f *keySpanFrags) get(
// invalidated whenever a key of the same kind is added to a memTable, and
// populated when empty when a span iterator of that key kind is created.
type keySpanCache struct {
count uint32
atomicCount uint32
frags unsafe.Pointer
cmp Compare
formatKey base.FormatKey
@@ -359,7 +363,7 @@ type keySpanCache struct {
// Invalidate the current set of cached spans, indicating the number of
// spans that were added.
func (c *keySpanCache) invalidate(count uint32) {
newCount := atomic.AddUint32(&c.count, count)
newCount := atomic.AddUint32(&c.atomicCount, count)
var frags *keySpanFrags
for {

View File

@@ -16,7 +16,7 @@ import (
)
type mergingIterLevel struct {
iter internalIteratorWithStats
iter internalIterator
// rangeDelIter is set to the range-deletion iterator for the level. When
// configured with a levelIter, this pointer changes as sstable boundaries
// are crossed. See levelIter.initRangeDel and the Range Deletions comment
@@ -24,22 +24,41 @@ type mergingIterLevel struct {
rangeDelIter keyspan.FragmentIterator
// iterKey and iterValue cache the current key and value iter are pointed at.
iterKey *InternalKey
iterValue []byte
iterValue base.LazyValue
// smallestUserKey, largestUserKey, isLargestUserKeyRangeDelSentinel are set using the sstable
// boundary keys when using levelIter. See levelIter comment and the Range Deletions comment
// below.
smallestUserKey, largestUserKey []byte
isLargestUserKeyRangeDelSentinel bool
isSyntheticIterBoundsKey bool
// levelIterBoundaryContext's fields are set when using levelIter, in order
// to surface sstable boundary keys and file-level context. See levelIter
// comment and the Range Deletions comment below.
levelIterBoundaryContext
// tombstone caches the tombstone rangeDelIter is currently pointed at. If
// tombstone.Valid() is false, there are no further tombstones within the
// tombstone is nil, there are no further tombstones within the
// current sstable in the current iterator direction. The cached tombstone is
// only valid for the levels in the range [0,heap[0].index]. This avoids
// positioning tombstones at lower levels which cannot possibly shadow the
// current key.
tombstone keyspan.Span
tombstone *keyspan.Span
}
type levelIterBoundaryContext struct {
// smallestUserKey and largestUserKey are populated with the smallest and
// largest boundaries of the current file.
smallestUserKey, largestUserKey []byte
// isLargestUserKeyRangeDelSentinel is set to true when a file's largest
// boundary is an exclusive range deletion sentinel. If true, the file does
// not contain any keys with the provided user key, and the largestUserKey
// bound is exclusive.
isLargestUserKeyRangeDelSentinel bool
// isSyntheticIterBoundsKey is set to true iff the key returned by the level
// iterator is a synthetic key derived from the iterator bounds. This is
// used to prevent the mergingIter from being stuck at such a synthetic key
// if it becomes the top element of the heap.
isSyntheticIterBoundsKey bool
// isIgnorableBoundaryKey is set to true iff the key returned by the level
// iterator is a file boundary key that should be ignored. This is used to
// keep a levelIter file's range deletion iterator open as long as other
// levels within the merging iterator require it.
isIgnorableBoundaryKey bool
}
// mergingIter provides a merged view of multiple iterators from different
@@ -211,17 +230,20 @@ type mergingIterLevel struct {
// scenarios and have each step display the current state (i.e. the current
// heap and range-del iterator positioning).
type mergingIter struct {
logger Logger
split Split
dir int
snapshot uint64
levels []mergingIterLevel
heap mergingIterHeap
err error
prefix []byte
lower []byte
upper []byte
stats InternalIteratorStats
logger Logger
split Split
dir int
snapshot uint64
batchSnapshot uint64
levels []mergingIterLevel
heap mergingIterHeap
err error
prefix []byte
lower []byte
upper []byte
stats *InternalIteratorStats
combinedIterState *combinedIterState
// Elide range tombstones from being returned during iteration. Set to true
// when mergingIter is a child of Iterator and the mergingIter is processing
@@ -243,19 +265,27 @@ var _ base.InternalIterator = (*mergingIter)(nil)
//
// None of the iters may be nil.
func newMergingIter(
logger Logger, cmp Compare, split Split, iters ...internalIterator,
logger Logger,
stats *base.InternalIteratorStats,
cmp Compare,
split Split,
iters ...internalIterator,
) *mergingIter {
m := &mergingIter{}
levels := make([]mergingIterLevel, len(iters))
for i := range levels {
levels[i].iter = base.WrapIterWithStats(iters[i])
levels[i].iter = iters[i]
}
m.init(&IterOptions{logger: logger}, cmp, split, levels...)
m.init(&IterOptions{logger: logger}, stats, cmp, split, levels...)
return m
}
func (m *mergingIter) init(
opts *IterOptions, cmp Compare, split Split, levels ...mergingIterLevel,
opts *IterOptions,
stats *base.InternalIteratorStats,
cmp Compare,
split Split,
levels ...mergingIterLevel,
) {
m.err = nil // clear cached iteration error
m.logger = opts.getLogger()
@@ -264,9 +294,11 @@ func (m *mergingIter) init(
m.upper = opts.UpperBound
}
m.snapshot = InternalKeySeqNumMax
m.batchSnapshot = InternalKeySeqNumMax
m.levels = levels
m.heap.cmp = cmp
m.split = split
m.stats = stats
if cap(m.heap.items) < len(levels) {
m.heap.items = make([]mergingIterItem, 0, len(levels))
} else {
@@ -317,7 +349,7 @@ func (m *mergingIter) initMinRangeDelIters(oldTopLevel int) {
if l.rangeDelIter == nil {
continue
}
l.tombstone = keyspan.SeekGE(m.heap.cmp, l.rangeDelIter, item.key.UserKey, m.snapshot)
l.tombstone = keyspan.SeekGE(m.heap.cmp, l.rangeDelIter, item.key.UserKey)
}
}
@@ -344,14 +376,14 @@ func (m *mergingIter) initMaxRangeDelIters(oldTopLevel int) {
if l.rangeDelIter == nil {
continue
}
l.tombstone = keyspan.SeekLE(m.heap.cmp, l.rangeDelIter, item.key.UserKey, m.snapshot)
l.tombstone = keyspan.SeekLE(m.heap.cmp, l.rangeDelIter, item.key.UserKey)
}
}
func (m *mergingIter) switchToMinHeap() {
if m.heap.len() == 0 {
if m.lower != nil {
m.SeekGE(m.lower, false /* trySeekUsingNext */)
m.SeekGE(m.lower, base.SeekGEFlagsNone)
} else {
m.First()
}
@@ -404,7 +436,7 @@ func (m *mergingIter) switchToMinHeap() {
l.iterKey.IsExclusiveSentinel() &&
m.heap.cmp(l.iterKey.UserKey, m.lower) <= 0) {
if m.lower != nil {
l.iterKey, l.iterValue = l.iter.SeekGE(m.lower, false /* trySeekUsingNext */)
l.iterKey, l.iterValue = l.iter.SeekGE(m.lower, base.SeekGEFlagsNone)
} else {
l.iterKey, l.iterValue = l.iter.First()
}
@@ -425,7 +457,7 @@ func (m *mergingIter) switchToMinHeap() {
// levelIter's invariants. See the example in the for loop above.
if m.lower != nil && cur.isSyntheticIterBoundsKey && cur.iterKey.IsExclusiveSentinel() &&
m.heap.cmp(cur.iterKey.UserKey, m.lower) <= 0 {
cur.iterKey, cur.iterValue = cur.iter.SeekGE(m.lower, false /* trySeekUsingNext */)
cur.iterKey, cur.iterValue = cur.iter.SeekGE(m.lower, base.SeekGEFlagsNone)
} else {
cur.iterKey, cur.iterValue = cur.iter.Next()
}
@@ -435,7 +467,7 @@ func (m *mergingIter) switchToMinHeap() {
func (m *mergingIter) switchToMaxHeap() {
if m.heap.len() == 0 {
if m.upper != nil {
m.SeekLT(m.upper)
m.SeekLT(m.upper, base.SeekLTFlagsNone)
} else {
m.Last()
}
@@ -486,7 +518,7 @@ func (m *mergingIter) switchToMaxHeap() {
if l.iterKey == nil || (m.upper != nil && l.isSyntheticIterBoundsKey &&
l.iterKey.IsExclusiveSentinel() && m.heap.cmp(l.iterKey.UserKey, m.upper) >= 0) {
if m.upper != nil {
l.iterKey, l.iterValue = l.iter.SeekLT(m.upper)
l.iterKey, l.iterValue = l.iter.SeekLT(m.upper, base.SeekLTFlagsNone)
} else {
l.iterKey, l.iterValue = l.iter.Last()
}
@@ -508,7 +540,7 @@ func (m *mergingIter) switchToMaxHeap() {
// above.
if m.upper != nil && cur.isSyntheticIterBoundsKey && cur.iterKey.IsExclusiveSentinel() &&
m.heap.cmp(cur.iterKey.UserKey, m.upper) >= 0 {
cur.iterKey, cur.iterValue = cur.iter.SeekLT(m.upper)
cur.iterKey, cur.iterValue = cur.iter.SeekLT(m.upper, base.SeekLTFlagsNone)
} else {
cur.iterKey, cur.iterValue = cur.iter.Prev()
}
@@ -555,8 +587,8 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
// entry.
for level := 0; level <= item.index; level++ {
l := &m.levels[level]
if l.rangeDelIter == nil || !l.tombstone.Valid() {
// If l.tombstone.Valid() is false, there are no further tombstones
if l.rangeDelIter == nil || l.tombstone == nil {
// If l.tombstone is nil, there are no further tombstones
// in the current sstable in the current (forward) iteration
// direction.
continue
@@ -570,9 +602,9 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
// levelIter in the future cannot contain item.key). Also, it is possible that we
// will encounter parts of the range delete that should be ignored -- we handle that
// below.
l.tombstone = keyspan.SeekGE(m.heap.cmp, l.rangeDelIter, item.key.UserKey, m.snapshot)
l.tombstone = keyspan.SeekGE(m.heap.cmp, l.rangeDelIter, item.key.UserKey)
}
if !l.tombstone.Valid() {
if l.tombstone == nil {
continue
}
@@ -595,7 +627,7 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
//
// For a tombstone at the same level as the key, the file bounds are trivially satisfied.
if (l.smallestUserKey == nil || m.heap.cmp(l.smallestUserKey, item.key.UserKey) <= 0) &&
l.tombstone.Contains(m.heap.cmp, item.key.UserKey) && !l.tombstone.Empty() {
l.tombstone.VisibleAt(m.snapshot) && l.tombstone.Contains(m.heap.cmp, item.key.UserKey) {
if level < item.index {
// We could also do m.seekGE(..., level + 1). The levels from
// [level + 1, item.index) are already after item.key so seeking them may be
@@ -625,11 +657,17 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
}
// This seek is not directly due to a SeekGE call, so we don't
// know enough about the underlying iterator positions, and so
// we set trySeekUsingNext=false.
m.seekGE(seekKey, item.index, false /* trySeekUsingNext */)
// we keep the try-seek-using-next optimization disabled.
//
// Additionally, we set the relative-seek flag. This is
// important when iterating with lazy combined iteration. If
// there's a range key between this level's current file and the
// file the seek will land on, we need to detect it in order to
// trigger construction of the combined iterator.
m.seekGE(seekKey, item.index, base.SeekGEFlagsNone.EnableRelativeSeek())
return true
}
if l.tombstone.Covers(item.key.SeqNum()) {
if l.tombstone.CoversAt(m.snapshot, item.key.SeqNum()) {
m.nextEntry(item)
return true
}
@@ -639,37 +677,65 @@ func (m *mergingIter) isNextEntryDeleted(item *mergingIterItem) bool {
}
// Starting from the current entry, finds the first (next) entry that can be returned.
func (m *mergingIter) findNextEntry() (*InternalKey, []byte) {
func (m *mergingIter) findNextEntry() (*InternalKey, base.LazyValue) {
var reseeked bool
for m.heap.len() > 0 && m.err == nil {
item := &m.heap.items[0]
if m.levels[item.index].isSyntheticIterBoundsKey {
break
}
// For prefix iteration, stop if we've already exceeded the iterator's
// current prefix. There are two cases where we perform this check.
//
// 1. We already re-seeked the iterator due to a range tombstone. We
// could amortize the cost of this comparison, by doing it only after
// we have iterated in this for loop a few times. But unless we find
// a performance benefit to that, we do the simple thing and compare
// each time. Note that isNextEntryDeleted already did at least 4 key
// comparisons in order to return true, and additionally at least one
// heap comparison to step to the next entry.
//
// Note that we cannot move this comparison into the
// isNextEntryDeleted branch. Once isNextEntryDeleted determines a
// key is deleted and seeks the level's iterator, item.key's memory
// is potentially invalid. If the iterator is now exhausted, item.key
// may be garbage.
//
// 2. If the heap root is an ignorable boundary key, Next-ing the
// iterator will close the associated file and open the next file in
// the level. We don't want to do this unnecessarily because the
// 'exhausted' file may only appear exhausted because it failed a
// bloom filter check. Avoiding Next-ing the file has a performance
// benefit of unnecessarily advancing to the next file, but it's also
// necessary for correctness of some TrySeekUsingNext optimizations
// deeper in the iterator stack:
//
// There may exist valid keys between our current prefix (m.prefix)
// and the ignorable boundary key (item.key) that were not returned
// due to the bloom filter test. A subsequent SeekPrefixGE call with
// a seek key in the range (m.prefix - item.key) will have the
// TrySeekUsingNext flag enabled. The levelIter relies on having not
// skipped these keys in order to avoid more expensive full seeks.
if m.prefix != nil && (reseeked || m.levels[item.index].isIgnorableBoundaryKey) {
if n := m.split(item.key.UserKey); !bytes.Equal(m.prefix, item.key.UserKey[:n]) {
return nil, base.LazyValue{}
}
}
m.addItemStats(item)
if m.isNextEntryDeleted(item) {
m.stats.PointsCoveredByRangeTombstones++
// For prefix iteration, stop if we are past the prefix. We could
// amortize the cost of this comparison, by doing it only after we
// have iterated in this for loop a few times. But unless we find
// a performance benefit to that, we do the simple thing and
// compare each time. Note that isNextEntryDeleted already did at
// least 4 key comparisons in order to return true, and
// additionally at least one heap comparison to step to the next
// entry.
if m.prefix != nil {
if n := m.split(item.key.UserKey); !bytes.Equal(m.prefix, item.key.UserKey[:n]) {
return nil, nil
}
}
reseeked = true
continue
}
if item.key.Visible(m.snapshot) &&
if item.key.Visible(m.snapshot, m.batchSnapshot) &&
(!m.levels[item.index].isIgnorableBoundaryKey) &&
(item.key.Kind() != InternalKeyKindRangeDelete || !m.elideRangeTombstones) {
return &item.key, item.value
}
m.nextEntry(item)
}
return nil, nil
return nil, base.LazyValue{}
}
// Steps to the prev entry. item is the current top item in the heap.
@@ -713,8 +779,8 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
// entry.
for level := 0; level <= item.index; level++ {
l := &m.levels[level]
if l.rangeDelIter == nil || !l.tombstone.Valid() {
// If l.tombstone.Valid() is false, there are no further tombstones
if l.rangeDelIter == nil || l.tombstone == nil {
// If l.tombstone is nil, there are no further tombstones
// in the current sstable in the current (reverse) iteration
// direction.
continue
@@ -728,9 +794,9 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
// levelIter in the future cannot contain item.key). Also, it is it is possible that we
// will encounter parts of the range delete that should be ignored -- we handle that
// below.
l.tombstone = keyspan.SeekLE(m.heap.cmp, l.rangeDelIter, item.key.UserKey, m.snapshot)
l.tombstone = keyspan.SeekLE(m.heap.cmp, l.rangeDelIter, item.key.UserKey)
}
if !l.tombstone.Valid() {
if l.tombstone == nil {
continue
}
@@ -767,7 +833,7 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
cmpResult := m.heap.cmp(l.largestUserKey, item.key.UserKey)
withinLargestSSTableBound = cmpResult > 0 || (cmpResult == 0 && !l.isLargestUserKeyRangeDelSentinel)
}
if withinLargestSSTableBound && l.tombstone.Contains(m.heap.cmp, item.key.UserKey) && !l.tombstone.Empty() {
if withinLargestSSTableBound && l.tombstone.Contains(m.heap.cmp, item.key.UserKey) && l.tombstone.VisibleAt(m.snapshot) {
if level < item.index {
// We could also do m.seekLT(..., level + 1). The levels from
// [level + 1, item.index) are already before item.key so seeking them may be
@@ -787,10 +853,15 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
if l.smallestUserKey != nil && m.heap.cmp(l.smallestUserKey, seekKey) > 0 {
seekKey = l.smallestUserKey
}
m.seekLT(seekKey, item.index)
// We set the relative-seek flag. This is important when
// iterating with lazy combined iteration. If there's a range
// key between this level's current file and the file the seek
// will land on, we need to detect it in order to trigger
// construction of the combined iterator.
m.seekLT(seekKey, item.index, base.SeekLTFlagsNone.EnableRelativeSeek())
return true
}
if l.tombstone.Covers(item.key.SeqNum()) {
if l.tombstone.CoversAt(m.snapshot, item.key.SeqNum()) {
m.prevEntry(item)
return true
}
@@ -800,7 +871,7 @@ func (m *mergingIter) isPrevEntryDeleted(item *mergingIterItem) bool {
}
// Starting from the current entry, finds the first (prev) entry that can be returned.
func (m *mergingIter) findPrevEntry() (*InternalKey, []byte) {
func (m *mergingIter) findPrevEntry() (*InternalKey, base.LazyValue) {
for m.heap.len() > 0 && m.err == nil {
item := &m.heap.items[0]
if m.levels[item.index].isSyntheticIterBoundsKey {
@@ -811,17 +882,18 @@ func (m *mergingIter) findPrevEntry() (*InternalKey, []byte) {
m.stats.PointsCoveredByRangeTombstones++
continue
}
if item.key.Visible(m.snapshot) &&
if item.key.Visible(m.snapshot, m.batchSnapshot) &&
(!m.levels[item.index].isIgnorableBoundaryKey) &&
(item.key.Kind() != InternalKeyKindRangeDelete || !m.elideRangeTombstones) {
return &item.key, item.value
}
m.prevEntry(item)
}
return nil, nil
return nil, base.LazyValue{}
}
// Seeks levels >= level to >= key. Additionally uses range tombstones to extend the seeks.
func (m *mergingIter) seekGE(key []byte, level int, trySeekUsingNext bool) {
func (m *mergingIter) seekGE(key []byte, level int, flags base.SeekGEFlags) {
// When seeking, we can use tombstones to adjust the key we seek to on each
// level. Consider the series of range tombstones:
//
@@ -850,12 +922,19 @@ func (m *mergingIter) seekGE(key []byte, level int, trySeekUsingNext bool) {
l := &m.levels[level]
if m.prefix != nil {
l.iterKey, l.iterValue = l.iter.SeekPrefixGE(m.prefix, key, trySeekUsingNext)
l.iterKey, l.iterValue = l.iter.SeekPrefixGE(m.prefix, key, flags)
} else {
l.iterKey, l.iterValue = l.iter.SeekGE(key, trySeekUsingNext)
l.iterKey, l.iterValue = l.iter.SeekGE(key, flags)
}
if rangeDelIter := l.rangeDelIter; rangeDelIter != nil {
// If this level contains overlapping range tombstones, alter the seek
// key accordingly. Caveat: If we're performing lazy-combined iteration,
// we cannot alter the seek key: Range tombstones don't delete range
// keys, and there might exist live range keys within the range
// tombstone's span that need to be observed to trigger a switch to
// combined iteration.
if rangeDelIter := l.rangeDelIter; rangeDelIter != nil &&
(m.combinedIterState == nil || m.combinedIterState.initialized) {
// The level has a range-del iterator. Find the tombstone containing
// the search key.
//
@@ -871,8 +950,8 @@ func (m *mergingIter) seekGE(key []byte, level int, trySeekUsingNext bool) {
// so we can have a sstable with bounds [c#8, i#InternalRangeDelSentinel], and the
// tombstone is [b, k)#8 and the seek key is i: levelIter.SeekGE(i) will move past
// this sstable since it realizes the largest key is a InternalRangeDelSentinel.
l.tombstone = keyspan.SeekGE(m.heap.cmp, rangeDelIter, key, m.snapshot)
if !l.tombstone.Empty() && l.tombstone.Contains(m.heap.cmp, key) &&
l.tombstone = keyspan.SeekGE(m.heap.cmp, rangeDelIter, key)
if l.tombstone != nil && l.tombstone.VisibleAt(m.snapshot) && l.tombstone.Contains(m.heap.cmp, key) &&
(l.smallestUserKey == nil || m.heap.cmp(l.smallestUserKey, key) <= 0) {
// NB: Based on the comment above l.largestUserKey >= key, and based on the
// containment condition tombstone.End > key, so the assignment to key results
@@ -904,10 +983,10 @@ func (m *mergingIter) String() string {
// SeekGE implements base.InternalIterator.SeekGE. Note that SeekGE only checks
// the upper bound. It is up to the caller to ensure that key is greater than
// or equal to the lower bound.
func (m *mergingIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) {
func (m *mergingIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) {
m.err = nil // clear cached iteration error
m.prefix = nil
m.seekGE(key, 0 /* start level */, trySeekUsingNext)
m.seekGE(key, 0 /* start level */, flags)
return m.findNextEntry()
}
@@ -915,16 +994,16 @@ func (m *mergingIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, [
// SeekPrefixGE only checks the upper bound. It is up to the caller to ensure
// that key is greater than or equal to the lower bound.
func (m *mergingIter) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
m.err = nil // clear cached iteration error
m.prefix = prefix
m.seekGE(key, 0 /* start level */, trySeekUsingNext)
m.seekGE(key, 0 /* start level */, flags)
return m.findNextEntry()
}
// Seeks levels >= level to < key. Additionally uses range tombstones to extend the seeks.
func (m *mergingIter) seekLT(key []byte, level int) {
func (m *mergingIter) seekLT(key []byte, level int, flags base.SeekLTFlags) {
// See the comment in seekGE regarding using tombstones to adjust the seek
// target per level.
m.prefix = nil
@@ -934,9 +1013,16 @@ func (m *mergingIter) seekLT(key []byte, level int) {
}
l := &m.levels[level]
l.iterKey, l.iterValue = l.iter.SeekLT(key)
l.iterKey, l.iterValue = l.iter.SeekLT(key, flags)
if rangeDelIter := l.rangeDelIter; rangeDelIter != nil {
// If this level contains overlapping range tombstones, alter the seek
// key accordingly. Caveat: If we're performing lazy-combined iteration,
// we cannot alter the seek key: Range tombstones don't delete range
// keys, and there might exist live range keys within the range
// tombstone's span that need to be observed to trigger a switch to
// combined iteration.
if rangeDelIter := l.rangeDelIter; rangeDelIter != nil &&
(m.combinedIterState == nil || m.combinedIterState.initialized) {
// The level has a range-del iterator. Find the tombstone containing
// the search key.
//
@@ -957,8 +1043,9 @@ func (m *mergingIter) seekLT(key []byte, level int) {
withinLargestSSTableBound = cmpResult > 0 || (cmpResult == 0 && !l.isLargestUserKeyRangeDelSentinel)
}
l.tombstone = keyspan.SeekLE(m.heap.cmp, rangeDelIter, key, m.snapshot)
if !l.tombstone.Empty() && l.tombstone.Contains(m.heap.cmp, key) && withinLargestSSTableBound {
l.tombstone = keyspan.SeekLE(m.heap.cmp, rangeDelIter, key)
if l.tombstone != nil && l.tombstone.VisibleAt(m.snapshot) &&
l.tombstone.Contains(m.heap.cmp, key) && withinLargestSSTableBound {
// NB: Based on the comment above l.smallestUserKey <= key, and based
// on the containment condition tombstone.Start.UserKey <= key, so the
// assignment to key results in a monotonically non-increasing key
@@ -986,17 +1073,17 @@ func (m *mergingIter) seekLT(key []byte, level int) {
// SeekLT implements base.InternalIterator.SeekLT. Note that SeekLT only checks
// the lower bound. It is up to the caller to ensure that key is less than the
// upper bound.
func (m *mergingIter) SeekLT(key []byte) (*InternalKey, []byte) {
func (m *mergingIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
m.err = nil // clear cached iteration error
m.prefix = nil
m.seekLT(key, 0 /* start level */)
m.seekLT(key, 0 /* start level */, flags)
return m.findPrevEntry()
}
// First implements base.InternalIterator.First. Note that First only checks
// the upper bound. It is up to the caller to ensure that key is greater than
// or equal to the lower bound (e.g. via a call to SeekGE(lower)).
func (m *mergingIter) First() (*InternalKey, []byte) {
func (m *mergingIter) First() (*InternalKey, base.LazyValue) {
m.err = nil // clear cached iteration error
m.prefix = nil
m.heap.items = m.heap.items[:0]
@@ -1011,7 +1098,7 @@ func (m *mergingIter) First() (*InternalKey, []byte) {
// Last implements base.InternalIterator.Last. Note that Last only checks the
// lower bound. It is up to the caller to ensure that key is less than the
// upper bound (e.g. via a call to SeekLT(upper))
func (m *mergingIter) Last() (*InternalKey, []byte) {
func (m *mergingIter) Last() (*InternalKey, base.LazyValue) {
m.err = nil // clear cached iteration error
m.prefix = nil
for i := range m.levels {
@@ -1022,9 +1109,9 @@ func (m *mergingIter) Last() (*InternalKey, []byte) {
return m.findPrevEntry()
}
func (m *mergingIter) Next() (*InternalKey, []byte) {
func (m *mergingIter) Next() (*InternalKey, base.LazyValue) {
if m.err != nil {
return nil, nil
return nil, base.LazyValue{}
}
if m.dir != 1 {
@@ -1033,29 +1120,29 @@ func (m *mergingIter) Next() (*InternalKey, []byte) {
}
if m.heap.len() == 0 {
return nil, nil
return nil, base.LazyValue{}
}
m.nextEntry(&m.heap.items[0])
return m.findNextEntry()
}
func (m *mergingIter) Prev() (*InternalKey, []byte) {
func (m *mergingIter) Prev() (*InternalKey, base.LazyValue) {
if m.err != nil {
return nil, nil
return nil, base.LazyValue{}
}
if m.dir != -1 {
if m.prefix != nil {
m.err = errors.New("pebble: unsupported reverse prefix iteration")
return nil, nil
return nil, base.LazyValue{}
}
m.switchToMaxHeap()
return m.findPrevEntry()
}
if m.heap.len() == 0 {
return nil, nil
return nil, base.LazyValue{}
}
m.prevEntry(&m.heap.items[0])
@@ -1125,24 +1212,7 @@ func (m *mergingIter) ForEachLevelIter(fn func(li *levelIter) bool) {
func (m *mergingIter) addItemStats(item *mergingIterItem) {
m.stats.PointCount++
m.stats.KeyBytes += uint64(len(item.key.UserKey))
m.stats.ValueBytes += uint64(len(item.value))
m.stats.ValueBytes += uint64(len(item.value.ValueOrHandle))
}
var _ internalIteratorWithStats = &mergingIter{}
// Stats implements InternalIteratorWithStats.
func (m *mergingIter) Stats() base.InternalIteratorStats {
stats := m.stats
for i := range m.levels {
stats.Merge(m.levels[i].iter.Stats())
}
return stats
}
// ResetStats implements InternalIteratorWithStats.
func (m *mergingIter) ResetStats() {
m.stats = InternalIteratorStats{}
for i := range m.levels {
m.levels[i].iter.ResetStats()
}
}
var _ internalIterator = &mergingIter{}

View File

@@ -4,10 +4,12 @@
package pebble
import "github.com/cockroachdb/pebble/internal/base"
type mergingIterItem struct {
index int
key InternalKey
value []byte
value base.LazyValue
}
type mergingIterHeap struct {

View File

@@ -6,13 +6,15 @@ package pebble
import (
"fmt"
"time"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/cache"
"github.com/cockroachdb/pebble/internal/humanize"
"github.com/cockroachdb/pebble/record"
"github.com/cockroachdb/pebble/sstable"
"github.com/cockroachdb/redact"
"github.com/codahale/hdrhistogram"
"github.com/prometheus/client_golang/prometheus"
)
// CacheMetrics holds metrics for the block and table cache.
@@ -140,6 +142,7 @@ type Metrics struct {
MoveCount int64
ReadCount int64
RewriteCount int64
MultiLevelCount int64
// An estimate of the number of bytes that need to be compacted for the LSM
// to reach a stable state.
EstimatedDebt uint64
@@ -157,7 +160,8 @@ type Metrics struct {
Flush struct {
// The total number of flushes.
Count int64
Count int64
WriteThroughput ThroughputMetric
}
Filter FilterMetrics
@@ -177,6 +181,11 @@ type Metrics struct {
ZombieCount int64
}
Keys struct {
// The approximate count of internal range key set keys in the database.
RangeKeySetsCount uint64
}
Snapshots struct {
// The number of currently open snapshots.
Count int
@@ -221,12 +230,26 @@ type Metrics struct {
BytesWritten uint64
}
LogWriter struct {
FsyncLatency prometheus.Histogram
record.LogWriterMetrics
}
private struct {
optionsFileSize uint64
manifestFileSize uint64
}
}
var (
// FsyncLatencyBuckets are prometheus histogram buckets suitable for a histogram
// that records latencies for fsyncs.
FsyncLatencyBuckets = append(
prometheus.LinearBuckets(0.0, float64(time.Microsecond*100), 50),
prometheus.ExponentialBucketsRange(float64(time.Millisecond*5), float64(10*time.Second), 50)...,
)
)
// DiskSpaceUsage returns the total disk space used by the database in bytes,
// including live and obsolete files.
func (m *Metrics) DiskSpaceUsage() uint64 {
@@ -386,13 +409,14 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
humanize.IEC.Int64(m.Compact.InProgressBytes),
redact.Safe(m.Compact.NumInProgress),
redact.SafeString(""))
w.Printf(" ctype %9d %7d %7d %7d %7d %7d (default, delete, elision, move, read, rewrite)\n",
w.Printf(" ctype %9d %7d %7d %7d %7d %7d %7d (default, delete, elision, move, read, rewrite, multi-level)\n",
redact.Safe(m.Compact.DefaultCount),
redact.Safe(m.Compact.DeleteOnlyCount),
redact.Safe(m.Compact.ElisionOnlyCount),
redact.Safe(m.Compact.MoveCount),
redact.Safe(m.Compact.ReadCount),
redact.Safe(m.Compact.RewriteCount))
redact.Safe(m.Compact.RewriteCount),
redact.Safe(m.Compact.MultiLevelCount))
w.Printf(" memtbl %9d %7s\n",
redact.Safe(m.MemTable.Count),
humanize.IEC.Uint64(m.MemTable.Size))
@@ -422,41 +446,3 @@ func hitRate(hits, misses int64) float64 {
}
return 100 * float64(hits) / float64(sum)
}
// InternalIntervalMetrics exposes metrics about internal subsystems, that can
// be useful for deep observability purposes, and for higher-level admission
// control systems that are trying to estimate the capacity of the DB. These
// are experimental and subject to change, since they expose internal
// implementation details, so do not rely on these without discussion with the
// Pebble team.
// These represent the metrics over the interval of time from the last call to
// retrieve these metrics. These are not cumulative, unlike Metrics. The main
// challenge in making these cumulative is the hdrhistogram.Histogram, which
// does not have the ability to subtract a histogram from a preceding metric
// retrieval.
type InternalIntervalMetrics struct {
// LogWriter metrics.
LogWriter struct {
// WriteThroughput is the WAL throughput.
WriteThroughput ThroughputMetric
// PendingBufferUtilization is the utilization of the WAL writer's
// finite-sized pending blocks buffer. It provides an additional signal
// regarding how close to "full" the WAL writer is. The value is in the
// interval [0,1].
PendingBufferUtilization float64
// SyncQueueUtilization is the utilization of the WAL writer's
// finite-sized queue of work that is waiting to sync. The value is in the
// interval [0,1].
SyncQueueUtilization float64
// SyncLatencyMicros is a distribution of the fsync latency observed by
// the WAL writer. It can be nil if there were no fsyncs.
SyncLatencyMicros *hdrhistogram.Histogram
}
// Flush loop metrics.
Flush struct {
// WriteThroughput is the flushing throughput.
WriteThroughput ThroughputMetric
}
// NB: the LogWriter throughput and the Flush throughput are not directly
// comparable because the former does not compress, unlike the latter.
}

View File

@@ -8,7 +8,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"sort"
@@ -24,6 +23,7 @@ import (
"github.com/cockroachdb/pebble/internal/rate"
"github.com/cockroachdb/pebble/record"
"github.com/cockroachdb/pebble/vfs"
"github.com/prometheus/client_golang/prometheus"
)
const (
@@ -75,7 +75,6 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
merge: opts.Merger.Merge,
split: opts.Comparer.Split,
abbreviatedKey: opts.Comparer.AbbreviatedKey,
rangeKeys: opts.Experimental.RangeKeys,
largeBatchThreshold: (opts.MemTableSize - int(memTableEmptySize)) / 2,
logRecycler: logRecycler{limit: opts.MemTableStopWritesThreshold + 1},
closed: new(atomic.Value),
@@ -125,12 +124,6 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
apply: d.commitApply,
write: d.commitWrite,
})
d.compactionLimiter = rate.NewLimiter(
rate.Limit(d.opts.private.minCompactionRate),
d.opts.private.minCompactionRate)
d.flushLimiter = rate.NewLimiter(
rate.Limit(d.opts.private.minFlushRate),
d.opts.private.minFlushRate)
d.deletionLimiter = rate.NewLimiter(
rate.Limit(d.opts.Experimental.MinDeletionRate),
d.opts.Experimental.MinDeletionRate)
@@ -161,6 +154,24 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
}
}
// Ensure we close resources if we error out early. If the database is
// successfully opened, the named return value `db` will be set to `d`.
defer func() {
if db != nil {
// The database was successfully opened.
return
}
if d.dataDir != nil {
d.dataDir.Close()
}
if d.walDirname != d.dirname && d.walDir != nil {
d.walDir.Close()
}
if d.mu.formatVers.marker != nil {
d.mu.formatVers.marker.Close()
}
}()
// Open the database and WAL directories first in order to check for their
// existence.
var err error
@@ -220,6 +231,15 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
// Find the currently active manifest, if there is one.
manifestMarker, manifestFileNum, exists, err := findCurrentManifest(d.mu.formatVers.vers, opts.FS, dirname)
setCurrent := setCurrentFunc(d.mu.formatVers.vers, manifestMarker, opts.FS, dirname, d.dataDir)
defer func() {
// Ensure we close the manifest marker if we error out for any reason.
// If the database is successfully opened, the *versionSet will take
// ownership over the manifest marker, ensuring it's closed when the DB
// is closed.
if db == nil {
manifestMarker.Close()
}
}()
if err != nil {
return nil, errors.Wrapf(err, "pebble: database %q", dirname)
} else if !exists && !d.opts.ReadOnly && !d.opts.ErrorIfNotExists {
@@ -395,14 +415,22 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
d.mu.mem.queue[len(d.mu.mem.queue)-1].logNum = newLogNum
logFile = vfs.NewSyncingFile(logFile, vfs.SyncingFileOptions{
NoSyncOnClose: d.opts.NoSyncOnClose,
BytesPerSync: d.opts.WALBytesPerSync,
PreallocateSize: d.walPreallocateSize(),
})
d.mu.log.LogWriter = record.NewLogWriter(logFile, newLogNum)
d.mu.log.LogWriter.SetMinSyncInterval(d.opts.WALMinSyncInterval)
d.mu.log.metrics.fsyncLatency = prometheus.NewHistogram(prometheus.HistogramOpts{
Buckets: FsyncLatencyBuckets,
})
logWriterConfig := record.LogWriterConfig{
WALMinSyncInterval: d.opts.WALMinSyncInterval,
WALFsyncLatency: d.mu.log.metrics.fsyncLatency,
}
d.mu.log.LogWriter = record.NewLogWriter(logFile, newLogNum, logWriterConfig)
d.mu.versions.metrics.WAL.Files++
}
d.updateReadStateLocked(d.opts.DebugCheck, nil)
d.updateReadStateLocked(d.opts.DebugCheck)
if !d.opts.ReadOnly {
// Write the current options to disk.
@@ -517,7 +545,7 @@ func GetVersion(dir string, fs vfs.FS) (string, error) {
if err != nil {
return "", err
}
data, err := ioutil.ReadAll(f)
data, err := io.ReadAll(f)
f.Close()
if err != nil {
@@ -679,14 +707,8 @@ func (d *DB) replayWAL(
// mem is nil here.
if !d.opts.ReadOnly {
c := newFlush(d.opts, d.mu.versions.currentVersion(),
1 /* base level */, toFlush, &d.atomic.bytesFlushed)
newVE, _, err := d.runCompaction(jobID, c, nilPacer)
if err != nil {
return 0, err
}
// TODO(jackson): Remove the below call to applyFlushedRangeKeys once
// flushes actually persist range keys to sstables.
err = d.applyFlushedRangeKeys(toFlush)
1 /* base level */, toFlush)
newVE, _, err := d.runCompaction(jobID, c)
if err != nil {
return 0, err
}
@@ -705,7 +727,7 @@ func checkOptions(opts *Options, path string) (strictWALTail bool, err error) {
}
defer f.Close()
data, err := ioutil.ReadAll(f)
data, err := io.ReadAll(f)
if err != nil {
return false, err
}

View File

@@ -23,7 +23,8 @@ import (
)
const (
cacheDefaultSize = 8 << 20 // 8 MB
cacheDefaultSize = 8 << 20 // 8 MB
defaultLevelMultiplier = 10
)
// Compression exports the base.Compression type.
@@ -163,11 +164,15 @@ type IterOptions struct {
// existing is not low or if we just expect a one-time Seek (where loading the
// data block directly is better).
UseL6Filters bool
// Internal options.
logger Logger
// Level corresponding to this file. Only passed in if constructed by a
// levelIter.
level manifest.Level
// disableLazyCombinedIteration is an internal testing option.
disableLazyCombinedIteration bool
// NB: If adding new Options, you must account for them in iterator
// construction and Iterator.SetOptions.
@@ -212,9 +217,9 @@ func (o *IterOptions) getLogger() Logger {
// RangeKeyMasking configures automatic hiding of point keys by range keys. A
// non-nil Suffix enables range-key masking. When enabled, range keys with
// suffixes ≤ Suffix behave as masks. All point keys that are contained within a
// masking range key's bounds and have suffixes less than the range key's suffix
// are automatically skipped.
// suffixes ≥ Suffix behave as masks. All point keys that are contained within a
// masking range key's bounds and have suffixes greater than the range key's
// suffix are automatically skipped.
//
// Specifically, when configured with a RangeKeyMasking.Suffix _s_, and there
// exists a range key with suffix _r_ covering a point key with suffix _p_, and
@@ -227,12 +232,58 @@ func (o *IterOptions) getLogger() Logger {
// range keys with IterKeyTypePointsAndRanges.
type RangeKeyMasking struct {
// Suffix configures which range keys may mask point keys. Only range keys
// that are defined at suffixes less than or equal to Suffix will mask point
// keys.
// that are defined at suffixes greater than or equal to Suffix will mask
// point keys.
Suffix []byte
// Filter is an optional field that may be used to improve performance of
// range-key masking through a block-property filter defined over key
// suffixes. If non-nil, Filter is called by Pebble to construct a
// block-property filter mask at iterator creation. The filter is used to
// skip whole point-key blocks containing point keys with suffixes greater
// than a covering range-key's suffix.
//
// To use this functionality, the caller must create and configure (through
// Options.BlockPropertyCollectors) a block-property collector that records
// the maxmimum suffix contained within a block. The caller then must write
// and provide a BlockPropertyFilterMask implementation on that same
// property. See the BlockPropertyFilterMask type for more information.
Filter func() BlockPropertyFilterMask
}
// TODO(jackson): Add fields necessary for constructing and updating block
// property collectors.
// BlockPropertyFilterMask extends the BlockPropertyFilter interface for use
// with range-key masking. Unlike an ordinary block property filter, a
// BlockPropertyFilterMask's filtering criteria is allowed to change when Pebble
// invokes its SetSuffix method.
//
// When a Pebble iterator steps into a range key's bounds and the range key has
// a suffix greater than or equal to RangeKeyMasking.Suffix, the range key acts
// as a mask. The masking range key hides all point keys that fall within the
// range key's bounds and have suffixes > the range key's suffix. Without a
// filter mask configured, Pebble performs this hiding by stepping through point
// keys and comparing suffixes. If large numbers of point keys are masked, this
// requires Pebble to load, iterate through and discard a large number of
// sstable blocks containing masked point keys.
//
// If a block-property collector and a filter mask are configured, Pebble may
// skip loading some point-key blocks altogether. If a block's keys are known to
// all fall within the bounds of the masking range key and the block was
// annotated by a block-property collector with the maximal suffix, Pebble can
// ask the filter mask to compare the property to the current masking range
// key's suffix. If the mask reports no intersection, the block may be skipped.
//
// If unsuffixed and suffixed keys are written to the database, care must be
// taken to avoid unintentionally masking un-suffixed keys located in the same
// block as suffixed keys. One solution is to interpret unsuffixed keys as
// containing the maximal suffix value, ensuring that blocks containing
// unsuffixed keys are always loaded.
type BlockPropertyFilterMask interface {
BlockPropertyFilter
// SetSuffix configures the mask with the suffix of a range key. The filter
// should return false from Intersects whenever it's provided with a
// property encoding a block's minimum suffix that's greater (according to
// Compare) than the provided suffix.
SetSuffix(suffix []byte) error
}
// WriteOptions hold the optional per-query parameters for Set and Delete
@@ -429,12 +480,6 @@ type Options struct {
// concurrency slots as determined by the two options is chosen.
CompactionDebtConcurrency int
// DeleteRangeFlushDelay configures how long the database should wait
// before forcing a flush of a memtable that contains a range
// deletion. Disk space cannot be reclaimed until the range deletion
// is flushed. No automatic flush occurs if zero.
DeleteRangeFlushDelay time.Duration
// MinDeletionRate is the minimum number of bytes per second that would
// be deleted. Deletion pacing is used to slow down deletions when
// compactions finish up or readers close, and newly-obsolete files need
@@ -446,12 +491,6 @@ type Options struct {
// deletion pacing, which is also the default.
MinDeletionRate int
// RangeKeys enables the experimental use of range keys, stored in an
// in-memory nondurable arena. This option is only intended to be
// temporary, to allow the Pebble metamorphic tests to reuse the
// range-key arena across DB restarts.
RangeKeys *RangeKeysArena
// ReadCompactionRate controls the frequency of read triggered
// compactions by adjusting `AllowedSeeks` in manifest.FileMetadata:
//
@@ -511,12 +550,38 @@ type Options struct {
// By default, this value is false.
ValidateOnIngest bool
// LevelMultiplier configures the size multiplier used to determine the
// desired size of each level of the LSM. Defaults to 10.
LevelMultiplier int
// MultiLevelCompaction allows the compaction of SSTs from more than two
// levels iff a conventional two level compaction will quickly trigger a
// compaction in the output level.
MultiLevelCompaction bool
// MaxWriterConcurrency is used to indicate the maximum number of
// compression workers the compression queue is allowed to use. If
// MaxWriterConcurrency > 0, then the Writer will use parallelism, to
// compress and write blocks to disk. Otherwise, the writer will
// compress and write blocks to disk synchronously.
MaxWriterConcurrency int
// ForceWriterParallelism is used to force parallelism in the sstable
// Writer for the metamorphic tests. Even with the MaxWriterConcurrency
// option set, we only enable parallelism in the sstable Writer if there
// is enough CPU available, and this option bypasses that.
ForceWriterParallelism bool
// CPUWorkPermissionGranter should be set if Pebble should be given the
// ability to optionally schedule additional CPU. See the documentation
// for CPUWorkPermissionGranter for more details.
CPUWorkPermissionGranter CPUWorkPermissionGranter
// PointTombstoneWeight is a float in the range [0, +inf) used to weight the
// point tombstone heuristics during compaction picking.
//
// The default value is 1, which results in no scaling of point tombstones.
PointTombstoneWeight float64
}
// Filters is a map from filter policy name to filter policy. It is used for
@@ -525,6 +590,18 @@ type Options struct {
// map during normal usage of a DB.
Filters map[string]FilterPolicy
// FlushDelayDeleteRange configures how long the database should wait before
// forcing a flush of a memtable that contains a range deletion. Disk space
// cannot be reclaimed until the range deletion is flushed. No automatic
// flush occurs if zero.
FlushDelayDeleteRange time.Duration
// FlushDelayRangeKey configures how long the database should wait before
// forcing a flush of a memtable that contains a range key. Range keys in
// the memtable prevent lazy combined iteration, so it's desirable to flush
// range keys promptly. No automatic flush occurs if zero.
FlushDelayRangeKey time.Duration
// FlushSplitBytes denotes the target number of bytes per sublevel in
// each flush split interval (i.e. range between two flush split keys)
// in L0 sstables. When set to zero, only a single sstable is generated
@@ -622,13 +699,20 @@ type Options struct {
// - when L0 read-amplification passes the L0CompactionConcurrency threshold
// - for automatic background compactions
// - when a manual compaction for a level is split and parallelized
MaxConcurrentCompactions int
// MaxConcurrentCompactions must be greater than 0.
MaxConcurrentCompactions func() int
// DisableAutomaticCompactions dictates whether automatic compactions are
// scheduled or not. The default is false (enabled). This option is only used
// externally when running a manual compaction, and internally for tests.
DisableAutomaticCompactions bool
// NoSyncOnClose decides whether the Pebble instance will enforce a
// close-time synchronization (e.g., fdatasync() or sync_file_range())
// on files it writes to. Setting this to true removes the guarantee for a
// sync on close. Some implementations can still issue a non-blocking sync.
NoSyncOnClose bool
// NumPrevManifest is the number of non-current or older manifests which
// we want to keep around for debugging purposes. By default, we're going
// to keep one older manifest.
@@ -699,24 +783,9 @@ type Options struct {
// RocksDB 6.2.1.
strictWALTail bool
// TODO(peter): A private option to enable flush/compaction pacing. Only used
// by tests. Compaction/flush pacing is disabled until we fix the impact on
// throughput.
enablePacing bool
// A private option to disable stats collection.
disableTableStats bool
// minCompactionRate sets the minimum rate at which compactions occur. The
// default is 4 MB/s. Currently disabled as this option has no effect while
// private.enablePacing is false.
minCompactionRate int
// minFlushRate sets the minimum rate at which the MemTables are flushed. The
// default is 1 MB/s. Currently disabled as this option has no effect while
// private.enablePacing is false.
minFlushRate int
// 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.
@@ -726,6 +795,13 @@ type Options struct {
// against the FS are made after the DB is closed, the FS may leak a
// goroutine indefinitely.
fsCloser io.Closer
// disableLazyCombinedIteration is a private option used by the
// metamorphic tests to test equivalence between lazy-combined iteration
// and constructing the range-key iterator upfront. It's a private
// option to avoid littering the public interface with options that we
// do not want to allow users to actually configure.
disableLazyCombinedIteration bool
}
}
@@ -831,14 +907,8 @@ func (o *Options) EnsureDefaults() *Options {
o.Merger = DefaultMerger
}
o.private.strictWALTail = true
if o.private.minCompactionRate == 0 {
o.private.minCompactionRate = 4 << 20 // 4 MB/s
}
if o.private.minFlushRate == 0 {
o.private.minFlushRate = 1 << 20 // 1 MB/s
}
if o.MaxConcurrentCompactions <= 0 {
o.MaxConcurrentCompactions = 1
if o.MaxConcurrentCompactions == nil {
o.MaxConcurrentCompactions = func() int { return 1 }
}
if o.NumPrevManifest <= 0 {
o.NumPrevManifest = 1
@@ -860,6 +930,9 @@ func (o *Options) EnsureDefaults() *Options {
if o.FlushSplitBytes <= 0 {
o.FlushSplitBytes = 2 * o.Levels[0].TargetFileSize
}
if o.Experimental.LevelMultiplier <= 0 {
o.Experimental.LevelMultiplier = defaultLevelMultiplier
}
if o.Experimental.ReadCompactionRate == 0 {
o.Experimental.ReadCompactionRate = 16000
}
@@ -869,6 +942,12 @@ func (o *Options) EnsureDefaults() *Options {
if o.Experimental.TableCacheShards <= 0 {
o.Experimental.TableCacheShards = runtime.GOMAXPROCS(0)
}
if o.Experimental.CPUWorkPermissionGranter == nil {
o.Experimental.CPUWorkPermissionGranter = defaultCPUWorkGranter{}
}
if o.Experimental.PointTombstoneWeight == 0 {
o.Experimental.PointTombstoneWeight = 1
}
o.initMaps()
return o
@@ -943,8 +1022,9 @@ func (o *Options) String() string {
fmt.Fprintf(&buf, " cleaner=%s\n", o.Cleaner)
fmt.Fprintf(&buf, " compaction_debt_concurrency=%d\n", o.Experimental.CompactionDebtConcurrency)
fmt.Fprintf(&buf, " comparer=%s\n", o.Comparer.Name)
fmt.Fprintf(&buf, " delete_range_flush_delay=%s\n", o.Experimental.DeleteRangeFlushDelay)
fmt.Fprintf(&buf, " disable_wal=%t\n", o.DisableWAL)
fmt.Fprintf(&buf, " flush_delay_delete_range=%s\n", o.FlushDelayDeleteRange)
fmt.Fprintf(&buf, " flush_delay_range_key=%s\n", o.FlushDelayRangeKey)
fmt.Fprintf(&buf, " flush_split_bytes=%d\n", o.FlushSplitBytes)
fmt.Fprintf(&buf, " format_major_version=%d\n", o.FormatMajorVersion)
fmt.Fprintf(&buf, " l0_compaction_concurrency=%d\n", o.Experimental.L0CompactionConcurrency)
@@ -952,15 +1032,17 @@ func (o *Options) String() string {
fmt.Fprintf(&buf, " l0_compaction_threshold=%d\n", o.L0CompactionThreshold)
fmt.Fprintf(&buf, " l0_stop_writes_threshold=%d\n", o.L0StopWritesThreshold)
fmt.Fprintf(&buf, " lbase_max_bytes=%d\n", o.LBaseMaxBytes)
fmt.Fprintf(&buf, " max_concurrent_compactions=%d\n", o.MaxConcurrentCompactions)
if o.Experimental.LevelMultiplier != defaultLevelMultiplier {
fmt.Fprintf(&buf, " level_multiplier=%d\n", o.Experimental.LevelMultiplier)
}
fmt.Fprintf(&buf, " max_concurrent_compactions=%d\n", o.MaxConcurrentCompactions())
fmt.Fprintf(&buf, " max_manifest_file_size=%d\n", o.MaxManifestFileSize)
fmt.Fprintf(&buf, " max_open_files=%d\n", o.MaxOpenFiles)
fmt.Fprintf(&buf, " mem_table_size=%d\n", o.MemTableSize)
fmt.Fprintf(&buf, " mem_table_stop_writes_threshold=%d\n", o.MemTableStopWritesThreshold)
fmt.Fprintf(&buf, " min_compaction_rate=%d\n", o.private.minCompactionRate)
fmt.Fprintf(&buf, " min_deletion_rate=%d\n", o.Experimental.MinDeletionRate)
fmt.Fprintf(&buf, " min_flush_rate=%d\n", o.private.minFlushRate)
fmt.Fprintf(&buf, " merger=%s\n", o.Merger.Name)
fmt.Fprintf(&buf, " point_tombstone_weight=%f\n", o.Experimental.PointTombstoneWeight)
fmt.Fprintf(&buf, " read_compaction_rate=%d\n", o.Experimental.ReadCompactionRate)
fmt.Fprintf(&buf, " read_sampling_multiplier=%d\n", o.Experimental.ReadSamplingMultiplier)
fmt.Fprintf(&buf, " strict_wal_tail=%t\n", o.private.strictWALTail)
@@ -979,6 +1061,16 @@ func (o *Options) String() string {
fmt.Fprintf(&buf, " wal_dir=%s\n", o.WALDir)
fmt.Fprintf(&buf, " wal_bytes_per_sync=%d\n", o.WALBytesPerSync)
fmt.Fprintf(&buf, " max_writer_concurrency=%d\n", o.Experimental.MaxWriterConcurrency)
fmt.Fprintf(&buf, " force_writer_parallelism=%t\n", o.Experimental.ForceWriterParallelism)
// Private options.
if o.private.disableLazyCombinedIteration {
// This option is only encoded if true, because we do not want it to
// appear in production serialized Options files, since it's a
// testing-only option. It's only serialized when true, which still
// ensures that the metamorphic tests may propagate it to subprocesses.
fmt.Fprintln(&buf, " disable_lazy_combined_iteration=true")
}
for i := range o.Levels {
l := &o.Levels[i]
@@ -1117,9 +1209,17 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
case "compaction_debt_concurrency":
o.Experimental.CompactionDebtConcurrency, err = strconv.Atoi(value)
case "delete_range_flush_delay":
o.Experimental.DeleteRangeFlushDelay, err = time.ParseDuration(value)
// NB: This is a deprecated serialization of the
// `flush_delay_delete_range`.
o.FlushDelayDeleteRange, err = time.ParseDuration(value)
case "disable_wal":
o.DisableWAL, err = strconv.ParseBool(value)
case "disable_lazy_combined_iteration":
o.private.disableLazyCombinedIteration, err = strconv.ParseBool(value)
case "flush_delay_delete_range":
o.FlushDelayDeleteRange, err = time.ParseDuration(value)
case "flush_delay_range_key":
o.FlushDelayRangeKey, err = time.ParseDuration(value)
case "flush_split_bytes":
o.FlushSplitBytes, err = strconv.ParseInt(value, 10, 64)
case "format_major_version":
@@ -1147,8 +1247,16 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
// Do nothing; option existed in older versions of pebble.
case "lbase_max_bytes":
o.LBaseMaxBytes, err = strconv.ParseInt(value, 10, 64)
case "level_multiplier":
o.Experimental.LevelMultiplier, err = strconv.Atoi(value)
case "max_concurrent_compactions":
o.MaxConcurrentCompactions, err = strconv.Atoi(value)
var concurrentCompactions int
concurrentCompactions, err = strconv.Atoi(value)
if concurrentCompactions <= 0 {
err = errors.New("max_concurrent_compactions cannot be <= 0")
} else {
o.MaxConcurrentCompactions = func() int { return concurrentCompactions }
}
case "max_manifest_file_size":
o.MaxManifestFileSize, err = strconv.ParseInt(value, 10, 64)
case "max_open_files":
@@ -1158,11 +1266,15 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
case "mem_table_stop_writes_threshold":
o.MemTableStopWritesThreshold, err = strconv.Atoi(value)
case "min_compaction_rate":
o.private.minCompactionRate, err = strconv.Atoi(value)
// Do nothing; option existed in older versions of pebble, and
// may be meaningful again eventually.
case "min_deletion_rate":
o.Experimental.MinDeletionRate, err = strconv.Atoi(value)
case "min_flush_rate":
o.private.minFlushRate, err = strconv.Atoi(value)
// Do nothing; option existed in older versions of pebble, and
// may be meaningful again eventually.
case "point_tombstone_weight":
o.Experimental.PointTombstoneWeight, err = strconv.ParseFloat(value, 64)
case "strict_wal_tail":
o.private.strictWALTail, err = strconv.ParseBool(value)
case "merger":
@@ -1199,6 +1311,8 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
o.WALBytesPerSync, err = strconv.Atoi(value)
case "max_writer_concurrency":
o.Experimental.MaxWriterConcurrency, err = strconv.Atoi(value)
case "force_writer_parallelism":
o.Experimental.ForceWriterParallelism, err = strconv.ParseBool(value)
default:
if hooks != nil && hooks.SkipUnknown != nil && hooks.SkipUnknown(section+"."+key, value) {
return nil
@@ -1383,6 +1497,5 @@ func (o *Options) MakeWriterOptions(level int, format sstable.TableFormat) sstab
writerOpts.FilterPolicy = levelOpts.FilterPolicy
writerOpts.FilterType = levelOpts.FilterType
writerOpts.IndexBlockSize = levelOpts.IndexBlockSize
writerOpts.Parallelism = o.Experimental.MaxWriterConcurrency > 0
return writerOpts
}

View File

@@ -26,221 +26,6 @@ type pacer interface {
maybeThrottle(bytesIterated uint64) error
}
// compactionInternalPacer contains fields and methods common to compactionPacer
// and flushPacer.
type compactionInternalPacer struct {
limiter limiter
iterCount uint64
prevBytesIterated uint64
refreshBytesThreshold uint64
slowdownThreshold uint64
}
// limit applies rate limiting if the current byte level is below the configured
// threshold.
func (p *compactionInternalPacer) limit(amount, currentLevel uint64) error {
if currentLevel <= p.slowdownThreshold {
burst := p.limiter.Burst()
for amount > uint64(burst) {
d := p.limiter.DelayN(time.Now(), burst)
if d == rate.InfDuration {
return errors.Errorf("pacing failed")
}
time.Sleep(d)
amount -= uint64(burst)
}
d := p.limiter.DelayN(time.Now(), int(amount))
if d == rate.InfDuration {
return errors.Errorf("pacing failed")
}
time.Sleep(d)
} else {
burst := p.limiter.Burst()
for amount > uint64(burst) {
p.limiter.AllowN(time.Now(), burst)
amount -= uint64(burst)
}
p.limiter.AllowN(time.Now(), int(amount))
}
return nil
}
// compactionPacerInfo contains information necessary for compaction pacing.
type compactionPacerInfo struct {
// slowdownThreshold is the low watermark for compaction debt. If compaction debt is
// below this threshold, we slow down compactions. If compaction debt is above this
// threshold, we let compactions continue as fast as possible. We want to keep
// compaction speed as slow as possible to match the speed of flushes. This threshold
// is set so that a single flush cannot contribute enough compaction debt to overshoot
// the threshold.
slowdownThreshold uint64
totalCompactionDebt uint64
// totalDirtyBytes is the number of dirty bytes in memtables. The compaction
// pacer can monitor changes to this value to determine if user writes have
// stopped.
totalDirtyBytes uint64
}
// compactionPacerEnv defines the environment in which the compaction rate limiter
// is applied.
type compactionPacerEnv struct {
limiter limiter
memTableSize uint64
getInfo func() compactionPacerInfo
}
// compactionPacer rate limits compactions depending on compaction debt. The rate
// limiter is applied at a rate that keeps compaction debt at a steady level. If
// compaction debt increases at a rate that is faster than the system can handle,
// no rate limit is applied.
type compactionPacer struct {
compactionInternalPacer
env compactionPacerEnv
totalCompactionDebt uint64
totalDirtyBytes uint64
}
func newCompactionPacer(env compactionPacerEnv) *compactionPacer {
return &compactionPacer{
env: env,
compactionInternalPacer: compactionInternalPacer{
limiter: env.limiter,
},
}
}
// maybeThrottle slows down compactions to match memtable flush rate. The DB
// provides a compaction debt estimate and a slowdown threshold. We subtract the
// compaction debt estimate by the bytes iterated in the current compaction. If
// the new compaction debt estimate is below the threshold, the rate limiter is
// applied. If the new compaction debt is above the threshold, the rate limiter
// is not applied.
func (p *compactionPacer) maybeThrottle(bytesIterated uint64) error {
if bytesIterated == 0 {
return nil
}
// Recalculate total compaction debt and the slowdown threshold only once
// every 1000 iterations or when the refresh threshold is hit since it
// requires grabbing DB.mu which is expensive.
if p.iterCount == 0 || bytesIterated > p.refreshBytesThreshold {
pacerInfo := p.env.getInfo()
p.slowdownThreshold = pacerInfo.slowdownThreshold
p.totalCompactionDebt = pacerInfo.totalCompactionDebt
p.refreshBytesThreshold = bytesIterated + (p.env.memTableSize * 5 / 100)
p.iterCount = 1000
if p.totalDirtyBytes == pacerInfo.totalDirtyBytes {
// The total dirty bytes in the memtables have not changed since the
// previous call: user writes have completely stopped. Allow the
// compaction to proceed as fast as possible until the next
// recalculation. We adjust the recalculation threshold so that we can be
// nimble in the face of new user writes.
p.totalCompactionDebt += p.slowdownThreshold
p.iterCount = 100
}
p.totalDirtyBytes = pacerInfo.totalDirtyBytes
}
p.iterCount--
var curCompactionDebt uint64
if p.totalCompactionDebt > bytesIterated {
curCompactionDebt = p.totalCompactionDebt - bytesIterated
}
compactAmount := bytesIterated - p.prevBytesIterated
p.prevBytesIterated = bytesIterated
// We slow down compactions when the compaction debt falls below the slowdown
// threshold, which is set dynamically based on the number of non-empty levels.
// This will only occur if compactions can keep up with the pace of flushes. If
// bytes are flushed faster than how fast compactions can occur, compactions
// proceed at maximum (unthrottled) speed.
return p.limit(compactAmount, curCompactionDebt)
}
// flushPacerInfo contains information necessary for compaction pacing.
type flushPacerInfo struct {
inuseBytes uint64
}
// flushPacerEnv defines the environment in which the compaction rate limiter is
// applied.
type flushPacerEnv struct {
limiter limiter
memTableSize uint64
getInfo func() flushPacerInfo
}
// flushPacer rate limits memtable flushing to match the speed of incoming user
// writes. If user writes come in faster than the memtable can be flushed, no
// rate limit is applied.
type flushPacer struct {
compactionInternalPacer
env flushPacerEnv
inuseBytes uint64
adjustedInuseBytes uint64
}
func newFlushPacer(env flushPacerEnv) *flushPacer {
return &flushPacer{
env: env,
compactionInternalPacer: compactionInternalPacer{
limiter: env.limiter,
slowdownThreshold: env.memTableSize * 105 / 100,
},
}
}
// maybeThrottle slows down memtable flushing to match user write rate. The DB
// provides the total number of bytes in all the memtables. We subtract this total
// by the number of bytes flushed in the current flush to get a "dirty byte" count.
// If the dirty byte count is below the watermark (105% memtable size), the rate
// limiter is applied. If the dirty byte count is above the watermark, the rate
// limiter is not applied.
func (p *flushPacer) maybeThrottle(bytesIterated uint64) error {
if bytesIterated == 0 {
return nil
}
// Recalculate inuse memtable bytes only once every 1000 iterations or when
// the refresh threshold is hit since getting the inuse memtable byte count
// requires grabbing DB.mu which is expensive.
if p.iterCount == 0 || bytesIterated > p.refreshBytesThreshold {
pacerInfo := p.env.getInfo()
p.iterCount = 1000
p.refreshBytesThreshold = bytesIterated + (p.env.memTableSize * 5 / 100)
p.adjustedInuseBytes = pacerInfo.inuseBytes
if p.inuseBytes == pacerInfo.inuseBytes {
// The inuse bytes in the memtables have not changed since the previous
// call: user writes have completely stopped. Allow the flush to proceed
// as fast as possible until the next recalculation. We adjust the
// recalculation threshold so that we can be nimble in the face of new
// user writes.
p.adjustedInuseBytes += p.slowdownThreshold
p.iterCount = 100
}
p.inuseBytes = pacerInfo.inuseBytes
}
p.iterCount--
// dirtyBytes is the inuse number of bytes in the memtables minus the number of
// bytes flushed. It represents unflushed bytes in all the memtables, even the
// ones which aren't being flushed such as the mutable memtable.
dirtyBytes := p.adjustedInuseBytes - bytesIterated
flushAmount := bytesIterated - p.prevBytesIterated
p.prevBytesIterated = bytesIterated
// We slow down memtable flushing when the dirty bytes indicator falls
// below the low watermark, which is 105% memtable size. This will only
// occur if memtable flushing can keep up with the pace of incoming
// writes. If writes come in faster than how fast the memtable can flush,
// flushing proceeds at maximum (unthrottled) speed.
return p.limit(flushAmount, dirtyBytes)
}
// deletionPacerInfo contains any info from the db necessary to make deletion
// pacing decisions.
type deletionPacerInfo struct {

View File

@@ -5,136 +5,665 @@
package pebble
import (
"sync"
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/arenaskl"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/invariants"
"github.com/cockroachdb/pebble/internal/keyspan"
"github.com/cockroachdb/pebble/internal/rangekey"
"github.com/cockroachdb/pebble/internal/manifest"
"github.com/cockroachdb/pebble/sstable"
)
const rangeKeyArenaSize = 1 << 20
// RangeKeysArena is an in-memory arena in which range keys are stored.
//
// This is a temporary type that will eventually be removed.
type RangeKeysArena struct {
once sync.Once
skl arenaskl.Skiplist
arena *arenaskl.Arena
fragCache keySpanCache
}
// applyFlushedRangeKeys is a temporary hack to support in-memory only range
// keys. We use much of the same code that we will use for the memtable, but we
// use a separate arena that exists beyond the lifetime of any individual
// memtable. For as long as we're relying on this hack, a single *pebble.DB may
// only store as many range keys as fit in this arena.
//
// TODO(jackson): Remove applyFlushedRangeKeys when range keys are persisted.
func (d *DB) applyFlushedRangeKeys(flushable []*flushableEntry) error {
var added uint32
for i := 0; i < len(flushable); i++ {
iter := flushable[i].newRangeKeyIter(nil)
if iter == nil {
continue
}
d.maybeInitializeRangeKeys()
for s := iter.First(); s.Valid(); s = iter.Next() {
// flushable.newRangeKeyIter provides a FragmentIterator, which
// iterates over parsed keyspan.Spans.
//
// While we're faking a flush, we just want to write the original
// key into the global arena, so we need to re-encode the span's
// keys into internal key-value pairs.
//
// This awkward recombination will be removed when flushes implement
// range-key logic, coalescing range keys and constructing internal
// keys.
err := rangekey.Encode(s, func(k base.InternalKey, v []byte) error {
err := d.rangeKeys.skl.Add(k, v)
switch {
case err == nil:
added++
return nil
case errors.Is(err, arenaskl.ErrRecordExists):
// It's possible that we'll try to add a key to the arena twice
// during metamorphic tests that reset the synced state. Ignore.
// When range keys are actually flushed to stable storage, this
// will go away.
return nil
default:
// err != nil
return err
}
})
if err != nil {
return err
}
}
}
if added > 0 {
d.rangeKeys.fragCache.invalidate(added)
}
return nil
}
func (d *DB) maybeInitializeRangeKeys() {
// Lazily construct the global range key arena, so that tests that
// don't use range keys don't need to allocate this long-lived
// buffer.
d.rangeKeys.once.Do(func() {
arenaBuf := make([]byte, rangeKeyArenaSize)
d.rangeKeys.arena = arenaskl.NewArena(arenaBuf)
d.rangeKeys.skl.Reset(d.rangeKeys.arena, d.cmp)
d.rangeKeys.fragCache = keySpanCache{
cmp: d.cmp,
formatKey: d.opts.Comparer.FormatKey,
skl: &d.rangeKeys.skl,
constructSpan: rangekey.Decode,
}
})
}
func (d *DB) newRangeKeyIter(
it *iteratorRangeKeyState, seqNum uint64, batch *Batch, readState *readState, opts *IterOptions,
) keyspan.FragmentIterator {
d.maybeInitializeRangeKeys()
// TODO(jackson): Preallocate iters, mergingIter, rangeKeyIter in a
// structure analogous to iterAlloc.
var iters []keyspan.FragmentIterator
// constructRangeKeyIter constructs the range-key iterator stack, populating
// i.rangeKey.rangeKeyIter with the resulting iterator.
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)
// If there's an indexed batch with range keys, include it.
if batch != nil {
if rki := batch.newRangeKeyIter(opts); rki != nil {
iters = append(iters, rki)
if i.batch != nil {
if i.batch.index == nil {
i.rangeKey.iterConfig.AddLevel(newErrorKeyspanIter(ErrNotIndexed))
} else {
// Only include the batch's range key iterator if it has any keys.
// NB: This can force reconstruction of the rangekey iterator stack
// in SetOptions if subsequently range keys are added. See
// SetOptions.
if i.batch.countRangeKeys > 0 {
i.batch.initRangeKeyIter(&i.opts, &i.batchRangeKeyIter, i.batchSeqNum)
i.rangeKey.iterConfig.AddLevel(&i.batchRangeKeyIter)
}
}
}
// Next are the flushables: memtables and large batches.
for i := len(readState.memtables) - 1; i >= 0; i-- {
mem := readState.memtables[i]
for j := len(i.readState.memtables) - 1; j >= 0; j-- {
mem := i.readState.memtables[j]
// We only need to read from memtables which contain sequence numbers older
// than seqNum.
if logSeqNum := mem.logSeqNum; logSeqNum >= seqNum {
if logSeqNum := mem.logSeqNum; logSeqNum >= i.seqNum {
continue
}
if rki := mem.newRangeKeyIter(opts); rki != nil {
iters = append(iters, rki)
if rki := mem.newRangeKeyIter(&i.opts); rki != nil {
i.rangeKey.iterConfig.AddLevel(rki)
}
}
// For now while range keys are not fully integrated into Pebble, all range
// keys ever written to the DB are persisted in the d.rangeKeys arena.
frags := d.rangeKeys.fragCache.get()
if len(frags) > 0 {
iters = append(iters, keyspan.NewIter(d.cmp, frags))
current := i.readState.current
// Next are the file levels: L0 sub-levels followed by lower levels.
//
// Add file-specific iterators for L0 files containing range keys. This is less
// efficient than using levelIters for sublevels of L0 files containing
// range keys, but range keys are expected to be sparse anyway, reducing the
// cost benefit of maintaining a separate L0Sublevels instance for range key
// files and then using it here.
//
// NB: We iterate L0's files in reverse order. They're sorted by
// LargestSeqNum ascending, and we need to add them to the merging iterator
// in LargestSeqNum descending to preserve the merging iterator's invariants
// around Key Trailer order.
iter := current.RangeKeyLevels[0].Iter()
for f := iter.Last(); f != nil; f = iter.Prev() {
spanIterOpts := &keyspan.SpanIterOptions{RangeKeyFilters: i.opts.RangeKeyFilters}
spanIter, err := i.newIterRangeKey(f, spanIterOpts)
if err != nil {
i.rangeKey.iterConfig.AddLevel(&errorKeyspanIter{err: err})
continue
}
i.rangeKey.iterConfig.AddLevel(spanIter)
}
// Add level iterators for the non-empty non-L0 levels.
for level := 1; level < len(current.RangeKeyLevels); level++ {
if current.RangeKeyLevels[level].Empty() {
continue
}
li := i.rangeKey.iterConfig.NewLevelIter()
spanIterOpts := keyspan.SpanIterOptions{RangeKeyFilters: i.opts.RangeKeyFilters}
li.Init(spanIterOpts, i.cmp, i.newIterRangeKey, current.RangeKeyLevels[level].Iter(),
manifest.Level(level), i.opts.logger, manifest.KeyTypeRange)
i.rangeKey.iterConfig.AddLevel(li)
}
it.rangeKeyIter = rangekey.InitUserIteration(
d.cmp, seqNum, &it.alloc.merging, &it.alloc.defraging, iters...,
)
return it.rangeKeyIter
}
// Range key masking
//
// Pebble iterators may be configured such that range keys with suffixes mask
// point keys with lower suffixes. The intended use is implementing a MVCC
// delete range operation using range keys, when suffixes are MVCC timestamps.
//
// To enable masking, the user populates the IterOptions's RangeKeyMasking
// field. The Suffix field configures which range keys act as masks. The
// intended use is to hold a MVCC read timestamp. When implementing a MVCC
// delete range operation, only range keys that are visible at the read
// timestamp should be visible. If a range key has a suffix ≤
// RangeKeyMasking.Suffix, it acts as a mask.
//
// Range key masking is facilitated by the keyspan.InterleavingIter. The
// interleaving iterator interleaves range keys and point keys during combined
// iteration. During user iteration, the interleaving iterator is configured
// with a keyspan.SpanMask, implemented by the rangeKeyMasking struct below.
// The SpanMask interface defines two methods: SpanChanged and SkipPoint.
//
// SpanChanged is used to keep the current mask up-to-date. Whenever the point
// iterator has stepped into or out of the bounds of a range key, the
// interleaving iterator invokes SpanChanged passing the current covering range
// key. The below rangeKeyMasking implementation scans the range keys looking
// for the range key with the largest suffix that's still ≤ the suffix supplied
// to IterOptions.RangeKeyMasking.Suffix (the "read timestamp"). If it finds a
// range key that meets the condition, the range key should act as a mask. The
// span and the relevant range key's suffix are saved.
//
// The above ensures that `rangeKeyMasking.maskActiveSuffix` always contains the
// current masking suffix such that any point keys with lower suffixes should be
// skipped.
//
// There are two ways in which masked point keys are skipped.
//
// 1. Interleaving iterator SkipPoint
//
// Whenever the interleaving iterator encounters a point key that falls within
// the bounds of a range key, it invokes SkipPoint. The interleaving iterator
// guarantees that the SpanChanged method described above has already been
// invoked with the covering range key. The below rangeKeyMasking implementation
// of SkipPoint splits the key into prefix and suffix, compares the suffix to
// the `maskActiveSuffix` updated by SpanChanged and returns true if
// suffix(point) < maskActiveSuffix.
//
// The SkipPoint logic is sufficient to ensure that the Pebble iterator filters
// out all masked point keys. However, it requires the iterator read each masked
// point key. For broad range keys that mask many points, this may be expensive.
//
// 2. Block property filter
//
// For more efficient handling of braad range keys that mask many points, the
// IterOptions.RangeKeyMasking field has an optional Filter option. This Filter
// field takes a superset of the block-property filter interface, adding a
// method to dynamically configure the filter's filtering criteria.
//
// To make use of the Filter option, the user is required to define and
// configure a block-property collector that collects a property containing at
// least the maximum suffix of a key within a block.
//
// When the SpanChanged method described above is invoked, rangeKeyMasking also
// reconfigures the user-provided filter. It invokes a SetSuffix method,
// providing the `maskActiveSuffix`, requesting that from now on the
// block-property filter return Intersects()=false for any properties indicating
// that a block contains exclusively keys with suffixes greater than the
// provided suffix.
//
// Note that unlike other block-property filters, the filter used for masking
// must not apply across the entire keyspace. It must only filter blocks that
// lie within the bounds of the range key that set the mask suffix. To
// accommodate this, rangeKeyMasking implements a special interface:
// sstable.BoundLimitedBlockPropertyFilter. This interface extends the block
// property filter interface with two new methods: KeyIsWithinLowerBound and
// KeyIsWithinUpperBound. The rangeKeyMasking type wraps the user-provided block
// property filter, implementing these two methods and overriding Intersects to
// always return true if there is no active mask.
//
// The logic to ensure that a mask block-property filter is only applied within
// the bounds of the masking range key is subtle. The interleaving iterator
// guarantees that it never invokes SpanChanged until the point iterator is
// positioned within the range key. During forward iteration, this guarantees
// that any block that a sstable reader might attempt to load contains only keys
// greater than or equal to the range key's lower bound. During backward
// iteration, it provides the analagous guarantee on the range key's upper
// bound.
//
// The above ensures that an sstable reader only needs to verify that a block
// that it skips meets the opposite bound. This is where the
// KeyIsWithinLowerBound and KeyIsWithinUpperBound methods are used. When an
// sstable iterator is configured with a BoundLimitedBlockPropertyFilter, it
// checks for intersection with the block-property filter before every block
// load, like ordinary block-property filters. However, if the bound-limited
// block property filter indicates that it does NOT intersect, the filter's
// relevant KeyIsWithin{Lower,Upper}Bound method is queried, using a block
// index separator as the bound. If the method indicates that the provided index
// separator does not fall within the range key bounds, the no-intersection
// result is ignored, and the block is read.
type rangeKeyMasking struct {
cmp base.Compare
split base.Split
filter BlockPropertyFilterMask
// maskActiveSuffix holds the suffix of a range key currently acting as a
// mask, hiding point keys with suffixes greater than it. maskActiveSuffix
// is only ever non-nil if IterOptions.RangeKeyMasking.Suffix is non-nil.
// maskActiveSuffix is updated whenever the iterator passes over a new range
// key. The maskActiveSuffix should only be used if maskSpan is non-nil.
//
// See SpanChanged.
maskActiveSuffix []byte
// maskSpan holds the span from which the active mask suffix was extracted.
// The span is used for bounds comparisons, to ensure that a range-key mask
// is not applied beyond the bounds of the range key.
maskSpan *keyspan.Span
parent *Iterator
}
func (m *rangeKeyMasking) init(parent *Iterator, cmp base.Compare, split base.Split) {
m.cmp = cmp
m.split = split
if parent.opts.RangeKeyMasking.Filter != nil {
m.filter = parent.opts.RangeKeyMasking.Filter()
}
m.parent = parent
}
// SpanChanged implements the keyspan.SpanMask interface, used during range key
// iteration.
func (m *rangeKeyMasking) SpanChanged(s *keyspan.Span) {
if s == nil && m.maskSpan == nil {
return
}
m.maskSpan = nil
m.maskActiveSuffix = m.maskActiveSuffix[:0]
// Find the smallest suffix of a range key contained within the Span,
// excluding suffixes less than m.opts.RangeKeyMasking.Suffix.
if s != nil {
m.parent.rangeKey.stale = true
if m.parent.opts.RangeKeyMasking.Suffix != nil {
for j := range s.Keys {
if s.Keys[j].Suffix == nil {
continue
}
if m.cmp(s.Keys[j].Suffix, m.parent.opts.RangeKeyMasking.Suffix) < 0 {
continue
}
if len(m.maskActiveSuffix) == 0 || m.cmp(m.maskActiveSuffix, s.Keys[j].Suffix) > 0 {
m.maskSpan = s
m.maskActiveSuffix = append(m.maskActiveSuffix[:0], s.Keys[j].Suffix...)
}
}
}
}
if m.maskSpan != nil && m.parent.opts.RangeKeyMasking.Filter != nil {
// Update the block-property filter to filter point keys with suffixes
// greater than m.maskActiveSuffix.
err := m.filter.SetSuffix(m.maskActiveSuffix)
if err != nil {
m.parent.err = err
}
}
// If no span is active, we leave the inner block-property filter configured
// with its existing suffix. That's okay, because Intersects calls are first
// evaluated by iteratorRangeKeyState.Intersects, which considers all blocks
// as intersecting if there's no active mask.
}
// SkipPoint implements the keyspan.SpanMask interface, used during range key
// iteration. Whenever a point key is covered by a non-empty Span, the
// interleaving iterator invokes SkipPoint. This function is responsible for
// performing range key masking.
//
// If a non-nil IterOptions.RangeKeyMasking.Suffix is set, range key masking is
// enabled. Masking hides point keys, transparently skipping over the keys.
// Whether or not a point key is masked is determined by comparing the point
// key's suffix, the overlapping span's keys' suffixes, and the user-configured
// IterOption's RangeKeyMasking.Suffix. When configured with a masking threshold
// _t_, and there exists a span with suffix _r_ covering a point key with suffix
// _p_, and
//
// _t_ ≤ _r_ < _p_
//
// then the point key is elided. Consider the following rendering, where using
// integer suffixes with higher integers sort before suffixes with lower
// integers, (for example @7 ≤ @6 < @5):
//
// ^
// @9 | •―――――――――――――――○ [e,m)@9
// s 8 | • l@8
// u 7 |------------------------------------ @7 RangeKeyMasking.Suffix
// f 6 | [h,q)@6 •―――――――――――――――――○ (threshold)
// f 5 | • h@5
// f 4 | • n@4
// i 3 | •―――――――――――○ [f,l)@3
// x 2 | • b@2
// 1 |
// 0 |___________________________________
// a b c d e f g h i j k l m n o p q
//
// An iterator scanning the entire keyspace with the masking threshold set to @7
// will observe point keys b@2 and l@8. The span keys [h,q)@6 and [f,l)@3 serve
// as masks, because cmp(@6,@7) ≥ 0 and cmp(@3,@7) ≥ 0. The span key [e,m)@9
// does not serve as a mask, because cmp(@9,@7) < 0.
//
// Although point l@8 falls within the user key bounds of [e,m)@9, [e,m)@9 is
// non-masking due to its suffix. The point key l@8 also falls within the user
// key bounds of [h,q)@6, but since cmp(@6,@8) ≥ 0, l@8 is unmasked.
//
// Invariant: The userKey is within the user key bounds of the span most
// recently provided to `SpanChanged`.
func (m *rangeKeyMasking) SkipPoint(userKey []byte) bool {
if m.maskSpan == nil {
// No range key is currently acting as a mask, so don't skip.
return false
}
// Range key masking is enabled and the current span includes a range key
// that is being used as a mask. (NB: SpanChanged already verified that the
// range key's suffix is ≥ RangeKeyMasking.Suffix).
//
// This point key falls within the bounds of the range key (guaranteed by
// the InterleavingIter). Skip the point key if the range key's suffix is
// greater than the point key's suffix.
pointSuffix := userKey[m.split(userKey):]
return len(pointSuffix) > 0 && m.cmp(m.maskActiveSuffix, pointSuffix) < 0
}
// The iteratorRangeKeyState type implements the sstable package's
// BoundLimitedBlockPropertyFilter interface in order to use block property
// filters for range key masking. The iteratorRangeKeyState implementation wraps
// the block-property filter provided in Options.RangeKeyMasking.Filter.
//
// Using a block-property filter for range-key masking requires limiting the
// filter's effect to the bounds of the range key currently acting as a mask.
// Consider the range key [a,m)@10, and an iterator positioned just before the
// below block, bounded by index separators `c` and `z`:
//
// c z
// x | c@9 c@5 c@1 d@7 e@4 y@4 | ...
// iter pos
//
// The next block cannot be skipped, despite the range key suffix @10 is greater
// than all the block's keys' suffixes, because it contains a key (y@4) outside
// the bounds of the range key.
//
// This extended BoundLimitedBlockPropertyFilter interface adds two new methods,
// KeyIsWithinLowerBound and KeyIsWithinUpperBound, for testing whether a
// particular block is within bounds.
//
// The iteratorRangeKeyState implements these new methods by first checking if
// the iterator is currently positioned within a range key. If not, the provided
// key is considered out-of-bounds. If the iterator is positioned within a range
// key, it compares the corresponding range key bound.
var _ sstable.BoundLimitedBlockPropertyFilter = (*rangeKeyMasking)(nil)
// Name implements the limitedBlockPropertyFilter interface defined in the
// sstable package by passing through to the user-defined block property filter.
func (m *rangeKeyMasking) Name() string {
return m.filter.Name()
}
// Intersects implements the limitedBlockPropertyFilter interface defined in the
// sstable package by passing the intersection decision to the user-provided
// block property filter only if a range key is covering the current iterator
// position.
func (m *rangeKeyMasking) Intersects(prop []byte) (bool, error) {
if m.maskSpan == nil {
// No span is actively masking.
return true, nil
}
return m.filter.Intersects(prop)
}
// KeyIsWithinLowerBound implements the limitedBlockPropertyFilter interface
// defined in the sstable package. It's used to restrict the masking block
// property filter to only applying within the bounds of the active range key.
func (m *rangeKeyMasking) KeyIsWithinLowerBound(ik *InternalKey) bool {
// Invariant: m.maskSpan != nil
//
// The provided `ik` is an inclusive lower bound of the block we're
// considering skipping.
return m.cmp(m.maskSpan.Start, ik.UserKey) <= 0
}
// KeyIsWithinUpperBound implements the limitedBlockPropertyFilter interface
// defined in the sstable package. It's used to restrict the masking block
// property filter to only applying within the bounds of the active range key.
func (m *rangeKeyMasking) KeyIsWithinUpperBound(ik *InternalKey) bool {
// Invariant: m.maskSpan != nil
//
// The provided `ik` is an *inclusive* upper bound of the block we're
// considering skipping, so the range key's end must be strictly greater
// than the block bound for the block to be within bounds.
return m.cmp(m.maskSpan.End, ik.UserKey) > 0
}
// lazyCombinedIter implements the internalIterator interface, wrapping a
// pointIter. It requires the pointIter's the levelIters be configured with
// pointers to its combinedIterState. When the levelIter observes a file
// containing a range key, the lazyCombinedIter constructs the combined
// range+point key iterator stack and switches to it.
type lazyCombinedIter struct {
// parent holds a pointer to the root *pebble.Iterator containing this
// iterator. It's used to mutate the internalIterator in use when switching
// to combined iteration.
parent *Iterator
pointIter internalIterator
combinedIterState combinedIterState
}
// combinedIterState encapsulates the current state of combined iteration.
// Various low-level iterators (mergingIter, leveliter) hold pointers to the
// *pebble.Iterator's combinedIterState. This allows them to check whether or
// not they must monitor for files containing range keys (!initialized), or not.
//
// When !initialized, low-level iterators watch for files containing range keys.
// When one is discovered, they set triggered=true and key to the smallest
// (forward direction) or largest (reverse direction) range key that's been
// observed.
type combinedIterState struct {
// key holds the smallest (forward direction) or largest (backward
// direction) user key from a range key bound discovered during the iterator
// operation that triggered the switch to combined iteration.
//
// Slices stored here must be stable. This is possible because callers pass
// a Smallest/Largest bound from a fileMetadata, which are immutable. A key
// slice's bytes must not be overwritten.
key []byte
triggered bool
initialized bool
}
// Assert that *lazyCombinedIter implements internalIterator.
var _ internalIterator = (*lazyCombinedIter)(nil)
// initCombinedIteration is invoked after a pointIter positioning operation
// resulted in i.combinedIterState.triggered=true.
//
// The `dir` parameter is `+1` or `-1` indicating forward iteration or backward
// iteration respectively.
//
// The `pointKey` and `pointValue` parameters provide the new point key-value
// pair that the iterator was just positioned to. The combined iterator should
// be seeded with this point key-value pair and return the smaller (forward
// iteration) or largest (backward iteration) of the two.
//
// The `seekKey` parameter is non-nil only if the iterator operation that
// triggered the switch to combined iteration was a SeekGE, SeekPrefixGE or
// SeekLT. It provides the seek key supplied and is used to seek the range-key
// iterator using the same key. This is necessary for SeekGE/SeekPrefixGE
// operations that land in the middle of a range key and must truncate to the
// user-provided seek key.
func (i *lazyCombinedIter) initCombinedIteration(
dir int8, pointKey *InternalKey, pointValue base.LazyValue, seekKey []byte,
) (*InternalKey, base.LazyValue) {
// Invariant: i.parent.rangeKey is nil.
// Invariant: !i.combinedIterState.initialized.
if invariants.Enabled {
if i.combinedIterState.initialized {
panic("pebble: combined iterator already initialized")
}
if i.parent.rangeKey != nil {
panic("pebble: iterator already has a range-key iterator stack")
}
}
// We need to determine the key to seek the range key iterator to. If
// seekKey is not nil, the user-initiated operation that triggered the
// switch to combined iteration was itself a seek, and we can use that key.
// Otherwise, a First/Last or relative positioning operation triggered the
// switch to combined iteration.
//
// The levelIter that observed a file containing range keys populated
// combinedIterState.key with the smallest (forward) or largest (backward)
// range key it observed. If multiple levelIters observed files with range
// keys during the same operation on the mergingIter, combinedIterState.key
// is the smallest [during forward iteration; largest in reverse iteration]
// such key.
if seekKey == nil {
// Use the levelIter-populated key.
seekKey = i.combinedIterState.key
// We may need to adjust the levelIter-populated seek key to the
// surfaced point key. If the key observed is beyond [in the iteration
// direction] the current point key, there may still exist a range key
// at an earlier key. Consider the following example:
//
// L5: 000003:[bar.DEL.5, foo.RANGEKEYSET.9]
// L6: 000001:[bar.SET.2] 000002:[bax.RANGEKEYSET.8]
//
// A call to First() seeks the levels to files L5.000003 and L6.000001.
// The L5 levelIter observes that L5.000003 contains the range key with
// start key `foo`, and triggers a switch to combined iteration, setting
// `combinedIterState.key` = `foo`.
//
// The L6 levelIter did not observe the true first range key
// (bax.RANGEKEYSET.8), because it appears in a later sstable. When the
// combined iterator is initialized, the range key iterator must be
// seeked to a key that will find `bax`. To accomplish this, we seek the
// key instead to `bar`. It is guaranteed that no range key exists
// earlier than `bar`, otherwise a levelIter would've observed it and
// set `combinedIterState.key` to its start key.
if pointKey != nil {
if dir == +1 && i.parent.cmp(i.combinedIterState.key, pointKey.UserKey) > 0 {
seekKey = pointKey.UserKey
} else if dir == -1 && i.parent.cmp(seekKey, pointKey.UserKey) < 0 {
seekKey = pointKey.UserKey
}
}
}
if i.parent.hasPrefix {
si := i.parent.comparer.Split(seekKey)
if i.parent.cmp(seekKey[:si], i.parent.prefixOrFullSeekKey) > 0 {
// The earliest possible range key has a start key with a prefix
// greater than the current iteration prefix. There's no need to
// switch to combined iteration, because there are not any range
// keys within the bounds of the prefix. Additionally, using a seek
// key that is outside the scope of the prefix can violate
// invariants within the range key iterator stack. Optimizations
// that exit early due to exhausting the prefix may result in
// `seekKey` being larger than the next range key's start key.
//
// See the testdata/rangekeys test case associated with #1893.
i.combinedIterState = combinedIterState{initialized: false}
return pointKey, pointValue
}
}
// An operation on the point iterator observed a file containing range keys,
// so we must switch to combined interleaving iteration. First, construct
// the range key iterator stack. It must not exist, otherwise we'd already
// be performing combined iteration.
i.parent.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
i.parent.rangeKey.init(i.parent.comparer.Compare, i.parent.comparer.Split, &i.parent.opts)
i.parent.constructRangeKeyIter()
// Initialize the Iterator's interleaving iterator.
i.parent.rangeKey.iiter.Init(
&i.parent.comparer, i.parent.pointIter, i.parent.rangeKey.rangeKeyIter,
&i.parent.rangeKeyMasking, i.parent.opts.LowerBound, i.parent.opts.UpperBound)
// Set the parent's primary iterator to point to the combined, interleaving
// iterator that's now initialized with our current state.
i.parent.iter = &i.parent.rangeKey.iiter
i.combinedIterState.initialized = true
i.combinedIterState.key = nil
// All future iterator operations will go directly through the combined
// iterator.
//
// Initialize the interleaving iterator. We pass the point key-value pair so
// that the interleaving iterator knows where the point iterator is
// positioned. Additionally, we pass the seek key to which the range-key
// iterator should be seeked in order to initialize its position.
//
// In the forward direction (invert for backwards), the seek key is a key
// guaranteed to find the smallest range key that's greater than the last
// key the iterator returned. The range key may be less than pointKey, in
// which case the range key will be interleaved next instead of the point
// key.
if dir == +1 {
var prefix []byte
if i.parent.hasPrefix {
prefix = i.parent.prefixOrFullSeekKey
}
return i.parent.rangeKey.iiter.InitSeekGE(prefix, seekKey, pointKey, pointValue)
}
return i.parent.rangeKey.iiter.InitSeekLT(seekKey, pointKey, pointValue)
}
func (i *lazyCombinedIter) SeekGE(
key []byte, flags base.SeekGEFlags,
) (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.SeekGE(key, flags)
}
k, v := i.pointIter.SeekGE(key, flags)
if i.combinedIterState.triggered {
return i.initCombinedIteration(+1, k, v, key)
}
return k, v
}
func (i *lazyCombinedIter) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.SeekPrefixGE(prefix, key, flags)
}
k, v := i.pointIter.SeekPrefixGE(prefix, key, flags)
if i.combinedIterState.triggered {
return i.initCombinedIteration(+1, k, v, key)
}
return k, v
}
func (i *lazyCombinedIter) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.SeekLT(key, flags)
}
k, v := i.pointIter.SeekLT(key, flags)
if i.combinedIterState.triggered {
return i.initCombinedIteration(-1, k, v, key)
}
return k, v
}
func (i *lazyCombinedIter) First() (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.First()
}
k, v := i.pointIter.First()
if i.combinedIterState.triggered {
return i.initCombinedIteration(+1, k, v, nil)
}
return k, v
}
func (i *lazyCombinedIter) Last() (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.Last()
}
k, v := i.pointIter.Last()
if i.combinedIterState.triggered {
return i.initCombinedIteration(-1, k, v, nil)
}
return k, v
}
func (i *lazyCombinedIter) Next() (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.Next()
}
k, v := i.pointIter.Next()
if i.combinedIterState.triggered {
return i.initCombinedIteration(+1, k, v, nil)
}
return k, v
}
func (i *lazyCombinedIter) Prev() (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.Prev()
}
k, v := i.pointIter.Prev()
if i.combinedIterState.triggered {
return i.initCombinedIteration(-1, k, v, nil)
}
return k, v
}
func (i *lazyCombinedIter) Error() error {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.Error()
}
return i.pointIter.Error()
}
func (i *lazyCombinedIter) Close() error {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.Close()
}
return i.pointIter.Close()
}
func (i *lazyCombinedIter) SetBounds(lower, upper []byte) {
if i.combinedIterState.initialized {
i.parent.rangeKey.iiter.SetBounds(lower, upper)
return
}
i.pointIter.SetBounds(lower, upper)
}
func (i *lazyCombinedIter) String() string {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.String()
}
return i.pointIter.String()
}

View File

@@ -80,7 +80,7 @@ func (d *DB) loadReadState() *readState {
// list of memtables. Requires DB.mu is held. If checker is not nil, it is
// called after installing the new readState. If atomicFunc is not nil, it is
// executed atomically with the transition to the new read state.
func (d *DB) updateReadStateLocked(checker func(*DB) error, atomicFunc func()) {
func (d *DB) updateReadStateLocked(checker func(*DB) error) {
s := &readState{
db: d,
refcnt: 1,
@@ -95,11 +95,6 @@ func (d *DB) updateReadStateLocked(checker func(*DB) error, atomicFunc func()) {
d.readState.Lock()
old := d.readState.val
d.readState.val = s
// TODO(jackson): Remove atomicFunc. It's temporary to allow us to copy
// range keys from flushed memtables into the global arena atomically.
if atomicFunc != nil {
atomicFunc()
}
d.readState.Unlock()
if checker != nil {
if err := checker(d); err != nil {

View File

@@ -16,7 +16,7 @@ import (
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/crc"
"github.com/codahale/hdrhistogram"
"github.com/prometheus/client_golang/prometheus"
)
var walSyncLabels = pprof.Labels("pebble", "wal-sync")
@@ -281,6 +281,7 @@ type LogWriter struct {
err error
// minSyncInterval is the minimum duration between syncs.
minSyncInterval durationFunc
fsyncLatency prometheus.Histogram
pending []*block
syncQ syncQueue
metrics *LogWriterMetrics
@@ -292,12 +293,18 @@ type LogWriter struct {
afterFunc func(d time.Duration, f func()) syncTimer
}
// LogWriterConfig is a struct used for configuring new LogWriters
type LogWriterConfig struct {
WALMinSyncInterval durationFunc
WALFsyncLatency prometheus.Histogram
}
// CapAllocatedBlocks is the maximum number of blocks allocated by the
// LogWriter.
const CapAllocatedBlocks = 16
// NewLogWriter returns a new LogWriter.
func NewLogWriter(w io.Writer, logNum base.FileNum) *LogWriter {
func NewLogWriter(w io.Writer, logNum base.FileNum, logWriterConfig LogWriterConfig) *LogWriter {
c, _ := w.(io.Closer)
s, _ := w.(syncer)
r := &LogWriter{
@@ -321,25 +328,17 @@ func NewLogWriter(w io.Writer, logNum base.FileNum) *LogWriter {
r.flusher.closed = make(chan struct{})
r.flusher.pending = make([]*block, 0, cap(r.free.blocks))
r.flusher.metrics = &LogWriterMetrics{}
// Histogram with max value of 30s. We are not trying to detect anomalies
// with this, and normally latencies range from 0.5ms to 25ms.
r.flusher.metrics.SyncLatencyMicros = hdrhistogram.New(
0, (time.Second * 30).Microseconds(), 2)
f := &r.flusher
f.minSyncInterval = logWriterConfig.WALMinSyncInterval
f.fsyncLatency = logWriterConfig.WALFsyncLatency
go func() {
pprof.Do(context.Background(), walSyncLabels, r.flushLoop)
}()
return r
}
// SetMinSyncInterval sets the closure to invoke for retrieving the minimum
// sync duration between syncs.
func (w *LogWriter) SetMinSyncInterval(minSyncInterval durationFunc) {
f := &w.flusher
f.Lock()
f.minSyncInterval = minSyncInterval
f.Unlock()
}
func (w *LogWriter) flushLoop(context.Context) {
f := &w.flusher
f.Lock()
@@ -452,8 +451,8 @@ func (w *LogWriter) flushLoop(context.Context) {
f.Unlock()
synced, syncLatency, bytesWritten, err := w.flushPending(data, pending, head, tail)
f.Lock()
if synced {
f.metrics.SyncLatencyMicros.RecordValue(syncLatency.Microseconds())
if synced && f.fsyncLatency != nil {
f.fsyncLatency.Observe(float64(syncLatency))
}
f.err = err
if f.err != nil {
@@ -607,7 +606,9 @@ func (w *LogWriter) Close() error {
syncLatency, err = w.syncWithLatency()
}
f.Lock()
f.metrics.SyncLatencyMicros.RecordValue(syncLatency.Microseconds())
if f.fsyncLatency != nil {
f.fsyncLatency.Observe(float64(syncLatency))
}
f.Unlock()
if w.c != nil {
@@ -730,10 +731,9 @@ func (w *LogWriter) Metrics() *LogWriterMetrics {
// LogWriterMetrics contains misc metrics for the log writer.
type LogWriterMetrics struct {
WriteThroughput base.ThroughputMetric
PendingBufferLen base.GaugeSampleMetric
SyncQueueLen base.GaugeSampleMetric
SyncLatencyMicros *hdrhistogram.Histogram
WriteThroughput base.ThroughputMetric
PendingBufferLen base.GaugeSampleMetric
SyncQueueLen base.GaugeSampleMetric
}
// Merge merges metrics from x. Requires that x is non-nil.
@@ -741,12 +741,5 @@ func (m *LogWriterMetrics) Merge(x *LogWriterMetrics) error {
m.WriteThroughput.Merge(x.WriteThroughput)
m.PendingBufferLen.Merge(x.PendingBufferLen)
m.SyncQueueLen.Merge(x.SyncQueueLen)
dropped := m.SyncLatencyMicros.Merge(x.SyncLatencyMicros)
if dropped > 0 {
// This should never happen since we use a consistent min, max when
// creating these histograms, and out-of-range is the only reason for the
// merge to drop samples.
return errors.Errorf("sync latency histogram merge dropped %d samples", dropped)
}
return nil
}

View File

@@ -32,7 +32,7 @@
// r.Recover()
// continue
// }
// s, err := ioutil.ReadAll(rec)
// s, err := io.ReadAll(rec)
// if err != nil {
// log.Printf("recovering from %v", err)
// r.Recover()

View File

@@ -534,7 +534,7 @@ func (i *blockIter) cacheEntry() {
// SeekGE implements internalIterator.SeekGE, as documented in the pebble
// package.
func (i *blockIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []byte) {
func (i *blockIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) {
i.clearCache()
ikey := base.MakeSearchKey(key)
@@ -629,25 +629,25 @@ func (i *blockIter) SeekGE(key []byte, trySeekUsingNext bool) (*InternalKey, []b
// Iterate from that restart point to somewhere >= the key sought.
for ; i.valid(); i.Next() {
if base.InternalCompare(i.cmp, i.ikey, ikey) >= 0 {
return &i.ikey, i.val
return &i.ikey, base.MakeInPlaceValue(i.val)
}
}
return nil, nil
return nil, base.LazyValue{}
}
// SeekPrefixGE implements internalIterator.SeekPrefixGE, as documented in the
// pebble package.
func (i *blockIter) SeekPrefixGE(
prefix, key []byte, trySeekUsingNext bool,
) (*base.InternalKey, []byte) {
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
// This should never be called as prefix iteration is handled by sstable.Iterator.
panic("pebble: SeekPrefixGE unimplemented")
}
// SeekLT implements internalIterator.SeekLT, as documented in the pebble
// package.
func (i *blockIter) SeekLT(key []byte) (*InternalKey, []byte) {
func (i *blockIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
i.clearCache()
ikey := base.MakeSearchKey(key)
@@ -742,7 +742,7 @@ func (i *blockIter) SeekLT(key []byte) (*InternalKey, []byte) {
// sought.
i.offset = -1
i.nextOffset = 0
return nil, nil
return nil, base.LazyValue{}
}
// Iterate from that restart point to somewhere >= the key sought, then back
@@ -761,7 +761,7 @@ func (i *blockIter) SeekLT(key []byte) (*InternalKey, []byte) {
// loop will execute at least once with this if-block not being true, so
// the key we are backing up to is the last one this loop cached.
i.Prev()
return &i.ikey, i.val
return &i.ikey, base.MakeInPlaceValue(i.val)
}
if i.nextOffset >= targetOffset {
@@ -777,30 +777,30 @@ func (i *blockIter) SeekLT(key []byte) (*InternalKey, []byte) {
}
if !i.valid() {
return nil, nil
return nil, base.LazyValue{}
}
return &i.ikey, i.val
return &i.ikey, base.MakeInPlaceValue(i.val)
}
// First implements internalIterator.First, as documented in the pebble
// package.
func (i *blockIter) First() (*InternalKey, []byte) {
func (i *blockIter) First() (*InternalKey, base.LazyValue) {
i.offset = 0
if !i.valid() {
return nil, nil
return nil, base.LazyValue{}
}
i.clearCache()
i.readEntry()
i.decodeInternalKey(i.key)
return &i.ikey, i.val
return &i.ikey, base.MakeInPlaceValue(i.val)
}
// Last implements internalIterator.Last, as documented in the pebble package.
func (i *blockIter) Last() (*InternalKey, []byte) {
func (i *blockIter) Last() (*InternalKey, base.LazyValue) {
// Seek forward from the last restart point.
i.offset = int32(binary.LittleEndian.Uint32(i.data[i.restarts+4*(i.numRestarts-1):]))
if !i.valid() {
return nil, nil
return nil, base.LazyValue{}
}
i.readEntry()
@@ -813,12 +813,12 @@ func (i *blockIter) Last() (*InternalKey, []byte) {
}
i.decodeInternalKey(i.key)
return &i.ikey, i.val
return &i.ikey, base.MakeInPlaceValue(i.val)
}
// Next implements internalIterator.Next, as documented in the pebble
// package.
func (i *blockIter) Next() (*InternalKey, []byte) {
func (i *blockIter) Next() (*InternalKey, base.LazyValue) {
if len(i.cachedBuf) > 0 {
// We're switching from reverse iteration to forward iteration. We need to
// populate i.fullKey with the current key we're positioned at so that
@@ -836,7 +836,7 @@ func (i *blockIter) Next() (*InternalKey, []byte) {
i.offset = i.nextOffset
if !i.valid() {
return nil, nil
return nil, base.LazyValue{}
}
i.readEntry()
// Manually inlined version of i.decodeInternalKey(i.key).
@@ -850,12 +850,12 @@ func (i *blockIter) Next() (*InternalKey, []byte) {
i.ikey.Trailer = uint64(InternalKeyKindInvalid)
i.ikey.UserKey = nil
}
return &i.ikey, i.val
return &i.ikey, base.MakeInPlaceValue(i.val)
}
// Prev implements internalIterator.Prev, as documented in the pebble
// package.
func (i *blockIter) Prev() (*InternalKey, []byte) {
func (i *blockIter) Prev() (*InternalKey, base.LazyValue) {
if n := len(i.cached) - 1; n >= 0 {
i.nextOffset = i.offset
e := &i.cached[n]
@@ -874,14 +874,14 @@ func (i *blockIter) Prev() (*InternalKey, []byte) {
i.ikey.UserKey = nil
}
i.cached = i.cached[:n]
return &i.ikey, i.val
return &i.ikey, base.MakeInPlaceValue(i.val)
}
i.clearCache()
if i.offset <= 0 {
i.offset = -1
i.nextOffset = 0
return nil, nil
return nil, base.LazyValue{}
}
targetOffset := i.offset
@@ -921,7 +921,7 @@ func (i *blockIter) Prev() (*InternalKey, []byte) {
}
i.decodeInternalKey(i.key)
return &i.ikey, i.val
return &i.ikey, base.MakeInPlaceValue(i.val)
}
// Key implements internalIterator.Key, as documented in the pebble package.
@@ -929,10 +929,8 @@ func (i *blockIter) Key() *InternalKey {
return &i.ikey
}
// Value implements internalIterator.Value, as documented in the pebble
// package.
func (i *blockIter) Value() []byte {
return i.val
func (i *blockIter) value() base.LazyValue {
return base.MakeInPlaceValue(i.val)
}
// Error implements internalIterator.Error, as documented in the pebble
@@ -987,6 +985,10 @@ type fragmentBlockIter struct {
closeHook func(i keyspan.FragmentIterator) error
}
func (i *fragmentBlockIter) resetForReuse() fragmentBlockIter {
return fragmentBlockIter{blockIter: i.blockIter.resetForReuse()}
}
func (i *fragmentBlockIter) decodeSpanKeys(k *InternalKey, internalValue []byte) {
// TODO(jackson): The use of i.span.Keys to accumulate keys across multiple
// calls to Decode is too confusing and subtle. Refactor to make it
@@ -1017,10 +1019,10 @@ func (i *fragmentBlockIter) decodeSpanKeys(k *InternalKey, internalValue []byte)
//
// gatherForward iterates forward, re-combining the fragmented internal keys to
// reconstruct a keyspan.Span that holds all the keys defined over the span.
func (i *fragmentBlockIter) gatherForward(k *InternalKey, internalValue []byte) keyspan.Span {
func (i *fragmentBlockIter) gatherForward(k *InternalKey, lazyValue base.LazyValue) *keyspan.Span {
i.span = keyspan.Span{}
if k == nil || !i.blockIter.valid() {
return i.span
return nil
}
i.err = nil
// Use the i.keyBuf array to back the Keys slice to prevent an allocation
@@ -1028,20 +1030,22 @@ func (i *fragmentBlockIter) gatherForward(k *InternalKey, internalValue []byte)
i.span.Keys = i.keyBuf[:0]
// Decode the span's end key and individual keys from the value.
internalValue := lazyValue.InPlaceValue()
i.decodeSpanKeys(k, internalValue)
if i.err != nil {
return i.span
return nil
}
prevEnd := i.span.End
// There might exist additional internal keys with identical bounds encoded
// within the block. Iterate forward, accumulating all the keys with
// identical bounds to s.
k, internalValue = i.blockIter.Next()
k, lazyValue = i.blockIter.Next()
internalValue = lazyValue.InPlaceValue()
for k != nil && i.blockIter.cmp(k.UserKey, i.span.Start) == 0 {
i.decodeSpanKeys(k, internalValue)
if i.err != nil {
return i.span
return nil
}
// Since k indicates an equal start key, the encoded end key must
@@ -1051,12 +1055,13 @@ func (i *fragmentBlockIter) gatherForward(k *InternalKey, internalValue []byte)
if i.blockIter.cmp(prevEnd, i.span.End) != 0 {
i.err = base.CorruptionErrorf("pebble: corrupt keyspan fragmentation")
i.span = keyspan.Span{}
return i.span
return nil
}
k, internalValue = i.blockIter.Next()
k, lazyValue = i.blockIter.Next()
internalValue = lazyValue.InPlaceValue()
}
// i.blockIter is positioned over the first internal key for the next span.
return i.span
return &i.span
}
// gatherBackward gathers internal keys with identical bounds. Keys defined over
@@ -1067,10 +1072,10 @@ func (i *fragmentBlockIter) gatherForward(k *InternalKey, internalValue []byte)
//
// gatherBackward iterates backwards, re-combining the fragmented internal keys
// to reconstruct a keyspan.Span that holds all the keys defined over the span.
func (i *fragmentBlockIter) gatherBackward(k *InternalKey, internalValue []byte) keyspan.Span {
func (i *fragmentBlockIter) gatherBackward(k *InternalKey, lazyValue base.LazyValue) *keyspan.Span {
i.span = keyspan.Span{}
if k == nil || !i.blockIter.valid() {
return i.span
return nil
}
i.err = nil
// Use the i.keyBuf array to back the Keys slice to prevent an allocation
@@ -1078,20 +1083,22 @@ func (i *fragmentBlockIter) gatherBackward(k *InternalKey, internalValue []byte)
i.span.Keys = i.keyBuf[:0]
// Decode the span's end key and individual keys from the value.
internalValue := lazyValue.InPlaceValue()
i.decodeSpanKeys(k, internalValue)
if i.err != nil {
return i.span
return nil
}
prevEnd := i.span.End
// There might exist additional internal keys with identical bounds encoded
// within the block. Iterate backward, accumulating all the keys with
// identical bounds to s.
k, internalValue = i.blockIter.Prev()
k, lazyValue = i.blockIter.Prev()
internalValue = lazyValue.InPlaceValue()
for k != nil && i.blockIter.cmp(k.UserKey, i.span.Start) == 0 {
i.decodeSpanKeys(k, internalValue)
if i.err != nil {
return i.span
return nil
}
// Since k indicates an equal start key, the encoded end key must
@@ -1101,27 +1108,26 @@ func (i *fragmentBlockIter) gatherBackward(k *InternalKey, internalValue []byte)
if i.blockIter.cmp(prevEnd, i.span.End) != 0 {
i.err = base.CorruptionErrorf("pebble: corrupt keyspan fragmentation")
i.span = keyspan.Span{}
return i.span
return nil
}
k, internalValue = i.blockIter.Prev()
k, lazyValue = i.blockIter.Prev()
internalValue = lazyValue.InPlaceValue()
}
// i.blockIter is positioned over the last internal key for the previous
// span.
// Backwards iteration encounters internal keys in the wrong order.
keyspan.SortKeys(i.span.Keys)
keyspan.SortKeysByTrailer(&i.span.Keys)
return i.span
return &i.span
}
// Error implements (base.InternalIterator).Error, as documented in the
// internal/base package.
// Error implements (keyspan.FragmentIterator).Error.
func (i *fragmentBlockIter) Error() error {
return i.err
}
// Close implements (base.InternalIterator).Close, as documented in the
// internal/base package.
// Close implements (keyspan.FragmentIterator).Close.
func (i *fragmentBlockIter) Close() error {
var err error
if i.closeHook != nil {
@@ -1131,23 +1137,20 @@ func (i *fragmentBlockIter) Close() error {
return err
}
// First implements (base.InternalIterator).First, as documented in the
// internal/base package.
func (i *fragmentBlockIter) First() keyspan.Span {
// First implements (keyspan.FragmentIterator).First
func (i *fragmentBlockIter) First() *keyspan.Span {
i.dir = +1
return i.gatherForward(i.blockIter.First())
}
// Last implements (base.InternalIterator).Last, as documented in the
// internal/base package.
func (i *fragmentBlockIter) Last() keyspan.Span {
// Last implements (keyspan.FragmentIterator).Last.
func (i *fragmentBlockIter) Last() *keyspan.Span {
i.dir = -1
return i.gatherBackward(i.blockIter.Last())
}
// Next implements (base.InternalIterator).Next, as documented in the
// internal/base package.
func (i *fragmentBlockIter) Next() keyspan.Span {
// Next implements (keyspan.FragmentIterator).Next.
func (i *fragmentBlockIter) Next() *keyspan.Span {
switch {
case i.dir == -1 && !i.span.Valid():
// Switching directions.
@@ -1176,12 +1179,12 @@ func (i *fragmentBlockIter) Next() keyspan.Span {
}
i.dir = +1
}
return i.gatherForward(&i.blockIter.ikey, i.blockIter.val)
// We know that this blockIter has in-place values.
return i.gatherForward(&i.blockIter.ikey, base.MakeInPlaceValue(i.blockIter.val))
}
// Prev implements (base.InternalIterator).Prev, as documented in the
// internal/base package.
func (i *fragmentBlockIter) Prev() keyspan.Span {
// Prev implements (keyspan.FragmentIterator).Prev.
func (i *fragmentBlockIter) Prev() *keyspan.Span {
switch {
case i.dir == +1 && !i.span.Valid():
// Switching directions.
@@ -1210,21 +1213,20 @@ func (i *fragmentBlockIter) Prev() keyspan.Span {
}
i.dir = -1
}
return i.gatherBackward(&i.blockIter.ikey, i.blockIter.val)
// We know that this blockIter has in-place values.
return i.gatherBackward(&i.blockIter.ikey, base.MakeInPlaceValue(i.blockIter.val))
}
// SeekGE implements (base.InternalIterator).SeekGE, as documented in the
// internal/base package.
func (i *fragmentBlockIter) SeekGE(k []byte) keyspan.Span {
// SeekGE implements (keyspan.FragmentIterator).SeekGE.
func (i *fragmentBlockIter) SeekGE(k []byte) *keyspan.Span {
i.dir = +1
return i.gatherForward(i.blockIter.SeekGE(k, false))
return i.gatherForward(i.blockIter.SeekGE(k, base.SeekGEFlags(0)))
}
// SeekLT implements (base.InternalIterator).SeekLT, as documented in the
// internal/base package.
func (i *fragmentBlockIter) SeekLT(k []byte) keyspan.Span {
// SeekLT implements (keyspan.FragmentIterator).SeekLT.
func (i *fragmentBlockIter) SeekLT(k []byte) *keyspan.Span {
i.dir = -1
return i.gatherBackward(i.blockIter.SeekLT(k))
return i.gatherBackward(i.blockIter.SeekLT(k, base.SeekLTFlagsNone))
}
// String implements fmt.Stringer.

View File

@@ -46,6 +46,40 @@ import (
// identical behavior to table properties by returning the nil slice from
// FinishDataBlock and FinishIndexBlock, and interpret them as the universal
// set in BlockPropertyFilter, and return a non-universal set in FinishTable.
//
// Block property filtering is nondeterministic because the separation of keys
// into blocks is nondeterministic. Clients use block-property filters to
// implement efficient application of a filter F that applies to key-value pairs
// (abbreviated as kv-filter). Consider correctness defined as surfacing exactly
// the same key-value pairs that would be surfaced if one applied the filter F
// above normal iteration. With this correctness definition, block property
// filtering may introduce two kinds of errors:
//
// a) Block property filtering that uses a kv-filter may produce additional
// key-value pairs that don't satisfy the filter because of the separation
// of keys into blocks. Clients may remove these extra key-value pairs by
// re-applying the kv filter while reading results back from Pebble.
//
// b) Block property filtering may surface deleted key-value pairs if the
// kv filter is not a strict function of the key's user key. A block
// containing k.DEL may be filtered, while a block containing the deleted
// key k.SET may not be filtered, if the kv filter applies to one but not
// the other.
//
// This error may be avoided trivially by using a kv filter that is a pure
// function of the user key. A filter that examines values or key kinds
// requires care to ensure F(k.SET, <value>) = F(k.DEL) = F(k.SINGLEDEL).
//
// The combination of range deletions and filtering by table-level properties
// add another opportunity for deleted point keys to be surfaced. The pebble
// Iterator stack takes care to correctly apply filtered tables' range deletions
// to lower tables, preventing this form of nondeterministic error.
//
// In addition to the non-determinism discussed in (b), which limits the use
// of properties over values, we now have support for values that are not
// stored together with the key, and may not even be retrieved during
// compactions. If Pebble is configured with such value separation, block
// properties must only apply to the key, and will be provided a nil value.
// BlockPropertyCollector is used when writing a sstable.
// - All calls to Add are included in the next FinishDataBlock, after which
@@ -112,6 +146,57 @@ type SuffixReplaceableBlockCollector interface {
// be thread-safe.
type BlockPropertyFilter = base.BlockPropertyFilter
// BoundLimitedBlockPropertyFilter implements the block-property filter but
// imposes an additional constraint on its usage, requiring that only blocks
// containing exclusively keys between its lower and upper bounds may be
// filtered. The bounds may be change during iteration, so the filter doesn't
// expose the bounds, instead implementing KeyIsWithin[Lower,Upper]Bound methods
// for performing bound comparisons.
//
// To be used, a BoundLimitedBlockPropertyFilter must be supplied directly
// through NewBlockPropertiesFilterer's dedicated parameter. If supplied through
// the ordinary slice of block property filters, this filter's bounds will be
// ignored.
//
// The current [lower,upper) bounds of the filter are unknown, because they may
// be changing. During forward iteration the lower bound is externally
// guaranteed, meaning Intersects only returns false if the sstable iterator is
// already known to be positioned at a key ≥ lower. The sstable iterator is then
// only responsible for ensuring filtered blocks also meet the upper bound, and
// should only allow a block to be filtered if all its keys are < upper. The
// sstable iterator may invoke KeyIsWithinUpperBound(key) to perform this check,
// where key is an inclusive upper bound on the block's keys.
//
// During backward iteration the upper bound is externally guaranteed, and
// Intersects only returns false if the sstable iterator is already known to be
// positioned at a key < upper. The sstable iterator is responsible for ensuring
// filtered blocks also meet the lower bound, enforcing that a block is only
// filtered if all its keys are ≥ lower. This check is made through passing the
// block's inclusive lower bound to KeyIsWithinLowerBound.
//
// Implementations may become active or inactive through implementing Intersects
// to return true whenever the filter is disabled.
//
// Usage of BoundLimitedBlockPropertyFilter is subtle, and Pebble consumers
// should not implement this interface directly. This interface is an internal
// detail in the implementation of block-property range-key masking.
type BoundLimitedBlockPropertyFilter interface {
BlockPropertyFilter
// KeyIsWithinLowerBound tests whether the provided internal key falls
// within the current lower bound of the filter. A true return value
// indicates that the filter may be used to filter blocks that exclusively
// contain keys ≥ `key`, so long as the blocks' keys also satisfy the upper
// bound.
KeyIsWithinLowerBound(key *InternalKey) bool
// KeyIsWithinUpperBound tests whether the provided internal key falls
// within the current upper bound of the filter. A true return value
// indicates that the filter may be used to filter blocks that exclusively
// contain keys ≤ `key`, so long as the blocks' keys also satisfy the lower
// bound.
KeyIsWithinUpperBound(key *InternalKey) bool
}
// BlockIntervalCollector is a helper implementation of BlockPropertyCollector
// for users who want to represent a set of the form [lower,upper) where both
// lower and upper are uint64, and lower <= upper.
@@ -326,32 +411,44 @@ func (w *suffixReplacementBlockCollectorWrapper) UpdateKeySuffixes(
return w.BlockIntervalCollector.points.(SuffixReplaceableBlockCollector).UpdateKeySuffixes(oldProp, from, to)
}
// blockIntervalFilter is an implementation of BlockPropertyFilter when the
// BlockIntervalFilter is an implementation of BlockPropertyFilter when the
// corresponding collector is a BlockIntervalCollector. That is, the set is of
// the form [lower, upper).
type blockIntervalFilter struct {
type BlockIntervalFilter struct {
name string
filterInterval interval
}
var _ BlockPropertyFilter = (*BlockIntervalFilter)(nil)
// NewBlockIntervalFilter constructs a BlockPropertyFilter that filters blocks
// based on an interval property collected by BlockIntervalCollector and the
// given [lower, upper) bounds. The given name specifies the
// BlockIntervalCollector's properties to read.
func NewBlockIntervalFilter(name string, lower uint64, upper uint64) BlockPropertyFilter {
return &blockIntervalFilter{
func NewBlockIntervalFilter(name string, lower uint64, upper uint64) *BlockIntervalFilter {
b := new(BlockIntervalFilter)
b.Init(name, lower, upper)
return b
}
// Init initializes (or re-initializes, clearing previous state) an existing
// BLockPropertyFilter to filter blocks based on an interval property collected
// by BlockIntervalCollector and the given [lower, upper) bounds. The given name
// specifies the BlockIntervalCollector's properties to read.
func (b *BlockIntervalFilter) Init(name string, lower, upper uint64) {
*b = BlockIntervalFilter{
name: name,
filterInterval: interval{lower: lower, upper: upper},
}
}
// Name implements the BlockPropertyFilter interface.
func (b *blockIntervalFilter) Name() string {
func (b *BlockIntervalFilter) Name() string {
return b.name
}
// Intersects implements the BlockPropertyFilter interface.
func (b *blockIntervalFilter) Intersects(prop []byte) (bool, error) {
func (b *BlockIntervalFilter) Intersects(prop []byte) (bool, error) {
var i interval
if err := i.decode(prop); err != nil {
return false, err
@@ -359,6 +456,14 @@ func (b *blockIntervalFilter) Intersects(prop []byte) (bool, error) {
return i.intersects(b.filterInterval), nil
}
// SetInterval adjusts the [lower, upper) bounds used by the filter. It is not
// generally safe to alter the filter while it's in use, except as part of the
// implementation of BlockPropertyFilterMask.SetSuffix used for range-key
// masking.
func (b *BlockIntervalFilter) SetInterval(lower, upper uint64) {
b.filterInterval = interval{lower: lower, upper: upper}
}
// When encoding block properties for each block, we cannot afford to encode
// the name. Instead, the name is mapped to a shortID, in the scope of that
// sstable, and the shortID is encoded. Since we use a uint8, there is a limit
@@ -454,6 +559,25 @@ type BlockPropertiesFilterer struct {
// has two filters, corresponding to shortIDs 2, 0, this would be:
// len(shortIDToFiltersIndex)==3, 0=>1, 1=>-1, 2=>0.
shortIDToFiltersIndex []int
// boundLimitedFilter, if non-nil, holds a single block-property filter with
// additional constraints on its filtering. A boundLimitedFilter may only
// filter blocks that are wholly contained within its bounds. During forward
// iteration the lower bound (and during backward iteration the upper bound)
// must be externally guaranteed, with Intersects only returning false if
// that bound is met. The opposite bound is verified during iteration by the
// sstable iterator.
//
// boundLimitedFilter is permitted to be defined on a property (`Name()`)
// for which another filter exists in filters. In this case both filters
// will be consulted, and either filter may exclude block(s). Only a single
// bound-limited block-property filter may be set.
//
// The boundLimitedShortID field contains the shortID of the filter's
// property within the sstable. It's set to -1 if the property was not
// collected when the table was built.
boundLimitedFilter BoundLimitedBlockPropertyFilter
boundLimitedShortID int
}
var blockPropertiesFiltererPool = sync.Pool{
@@ -464,9 +588,16 @@ var blockPropertiesFiltererPool = sync.Pool{
// NewBlockPropertiesFilterer returns a partially initialized filterer. To complete
// initialization, call IntersectsUserPropsAndFinishInit.
func NewBlockPropertiesFilterer(filters []BlockPropertyFilter) *BlockPropertiesFilterer {
func NewBlockPropertiesFilterer(
filters []BlockPropertyFilter, limited BoundLimitedBlockPropertyFilter,
) *BlockPropertiesFilterer {
filterer := blockPropertiesFiltererPool.Get().(*BlockPropertiesFilterer)
*filterer = BlockPropertiesFilterer{filters: filters}
*filterer = BlockPropertiesFilterer{
filters: filters,
shortIDToFiltersIndex: filterer.shortIDToFiltersIndex[:0],
boundLimitedFilter: limited,
boundLimitedShortID: -1,
}
return filterer
}
@@ -518,12 +649,75 @@ func (f *BlockPropertiesFilterer) IntersectsUserPropsAndFinishInit(
}
f.shortIDToFiltersIndex[shortID] = i
}
if f.boundLimitedFilter == nil {
return true, nil
}
// There's a bound-limited filter. Find its shortID. It's possible that
// there's an existing filter in f.filters on the same property. That's
// okay. Both filters will be consulted whenever a relevant prop is decoded.
props, ok := userProperties[f.boundLimitedFilter.Name()]
if !ok {
// The collector was not used when writing this file, so it's
// intersecting. We leave f.boundLimitedShortID=-1, so the filter will
// be unused within this file.
return true, nil
}
byteProps := []byte(props)
if len(byteProps) < 1 {
return false, base.CorruptionErrorf(
"block properties for %s is corrupted", f.boundLimitedFilter.Name())
}
f.boundLimitedShortID = int(byteProps[0])
// We don't check for table-level intersection for the bound-limited filter.
// The bound-limited filter is treated as vacuously intersecting.
//
// NB: If a block-property filter needs to be toggled inactive/active, it
// should be implemented within the Intersects implementation.
//
// TODO(jackson): We could filter at the table-level by threading the table
// smallest and largest bounds here.
// The bound-limited filter isn't included in shortIDToFiltersIndex.
//
// When determining intersection, we decode props only up to the shortID
// len(shortIDToFiltersIndex). If f.limitedShortID is greater than any of
// the existing filters' shortIDs, we need to grow shortIDToFiltersIndex.
// Growing the index with -1s ensures we're able to consult the index
// without length checks.
if n := len(f.shortIDToFiltersIndex); n <= f.boundLimitedShortID {
if cap(f.shortIDToFiltersIndex) <= f.boundLimitedShortID {
index := make([]int, f.boundLimitedShortID+1)
copy(index, f.shortIDToFiltersIndex)
f.shortIDToFiltersIndex = index
} else {
f.shortIDToFiltersIndex = f.shortIDToFiltersIndex[:f.boundLimitedShortID+1]
}
for j := n; j <= f.boundLimitedShortID; j++ {
f.shortIDToFiltersIndex[j] = -1
}
}
return true, nil
}
func (f *BlockPropertiesFilterer) intersects(props []byte) (bool, error) {
type intersectsResult int8
const (
blockIntersects intersectsResult = iota
blockExcluded
// blockMaybeExcluded is returned by BlockPropertiesFilterer.intersects when
// no filters unconditionally exclude the block, but the bound-limited block
// property filter will exclude it if the block's bounds fall within the
// filter's current bounds. See the reader's
// {single,two}LevelIterator.resolveMaybeExcluded methods.
blockMaybeExcluded
)
func (f *BlockPropertiesFilterer) intersects(props []byte) (ret intersectsResult, err error) {
i := 0
decoder := blockPropertiesDecoder{props: props}
ret = blockIntersects
for i < len(f.shortIDToFiltersIndex) {
var id int
var prop []byte
@@ -532,43 +726,68 @@ func (f *BlockPropertiesFilterer) intersects(props []byte) (bool, error) {
var err error
shortID, prop, err = decoder.next()
if err != nil {
return false, err
return ret, err
}
id = int(shortID)
} else {
id = math.MaxUint8 + 1
}
for i < len(f.shortIDToFiltersIndex) && id > i {
if f.shortIDToFiltersIndex[i] >= 0 {
// There is a filter for this id, but the property for this id
// is not encoded for this block.
intersects, err := f.filters[f.shortIDToFiltersIndex[i]].Intersects(nil)
if err != nil {
return false, err
}
if !intersects {
return false, nil
}
// The property for this id is not encoded for this block, but there
// may still be a filter for this id.
if intersects, err := f.intersectsFilter(i, nil); err != nil {
return ret, err
} else if intersects == blockExcluded {
return blockExcluded, nil
} else if intersects == blockMaybeExcluded {
ret = blockMaybeExcluded
}
i++
}
if i >= len(f.shortIDToFiltersIndex) {
return true, nil
return ret, nil
}
// INVARIANT: id <= i. And since i is always incremented by 1, id==i.
if id != i {
panic(fmt.Sprintf("%d != %d", id, i))
}
if f.shortIDToFiltersIndex[i] >= 0 {
intersects, err := f.filters[f.shortIDToFiltersIndex[i]].Intersects(prop)
if err != nil {
return false, err
}
if !intersects {
return false, nil
}
if intersects, err := f.intersectsFilter(i, prop); err != nil {
return ret, err
} else if intersects == blockExcluded {
return blockExcluded, nil
} else if intersects == blockMaybeExcluded {
ret = blockMaybeExcluded
}
i++
}
return true, nil
// ret == blockIntersects || ret == blockMaybeExcluded
return ret, nil
}
func (f *BlockPropertiesFilterer) intersectsFilter(i int, prop []byte) (intersectsResult, error) {
if f.shortIDToFiltersIndex[i] >= 0 {
intersects, err := f.filters[f.shortIDToFiltersIndex[i]].Intersects(prop)
if err != nil {
return blockIntersects, err
}
if !intersects {
return blockExcluded, nil
}
}
if i == f.boundLimitedShortID {
// The bound-limited filter uses this id.
//
// The bound-limited filter only applies within a keyspan interval. We
// expect the Intersects call to be cheaper than bounds checks. If
// Intersects determines that there is no intersection, we return
// `blockMaybeExcluded` if no other bpf unconditionally excludes the
// block.
intersects, err := f.boundLimitedFilter.Intersects(prop)
if err != nil {
return blockIntersects, err
} else if !intersects {
return blockMaybeExcluded, nil
}
}
return blockIntersects, nil
}

View File

@@ -0,0 +1,117 @@
// Copyright 2022 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.
package sstable
import (
"math"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/testkeys"
)
// Code in this file contains utils for testing. It implements interval block
// property collectors and filters on the suffixes of keys in the format used
// by the testkeys package (eg, 'key@5').
const testKeysBlockPropertyName = `pebble.internal.testkeys.suffixes`
// NewTestKeysBlockPropertyCollector constructs a sstable property collector
// over testkey suffixes.
func NewTestKeysBlockPropertyCollector() BlockPropertyCollector {
return NewBlockIntervalCollector(
testKeysBlockPropertyName,
&testKeysSuffixIntervalCollector{},
nil)
}
// NewTestKeysBlockPropertyFilter constructs a new block-property filter that excludes
// blocks containing exclusively suffixed keys where all the suffixes fall
// outside of the range [filterMin, filterMax).
//
// The filter only filters based on data derived from the key. The iteration
// results of this block property filter are deterministic for unsuffixed keys
// and keys with suffixes within the range [filterMin, filterMax). For keys with
// suffixes outside the range, iteration is nondeterministic.
func NewTestKeysBlockPropertyFilter(filterMin, filterMax uint64) *BlockIntervalFilter {
return NewBlockIntervalFilter(testKeysBlockPropertyName, filterMin, filterMax)
}
// NewTestKeysMaskingFilter constructs a TestKeysMaskingFilter that implements
// pebble.BlockPropertyFilterMask for efficient range-key masking using the
// testkeys block property filter. The masking filter wraps a block interval
// filter, and modifies the configured interval when Pebble requests it.
func NewTestKeysMaskingFilter() TestKeysMaskingFilter {
return TestKeysMaskingFilter{BlockIntervalFilter: NewTestKeysBlockPropertyFilter(0, math.MaxUint64)}
}
// TestKeysMaskingFilter implements BlockPropertyFilterMask and may be used to mask
// point keys with the testkeys-style suffixes (eg, @4) that are masked by range
// keys with testkeys-style suffixes.
type TestKeysMaskingFilter struct {
*BlockIntervalFilter
}
// SetSuffix implements pebble.BlockPropertyFilterMask.
func (f TestKeysMaskingFilter) SetSuffix(suffix []byte) error {
ts, err := testkeys.ParseSuffix(suffix)
if err != nil {
return err
}
f.BlockIntervalFilter.SetInterval(uint64(ts), math.MaxUint64)
return nil
}
// Intersects implements the BlockPropertyFilter interface.
func (f TestKeysMaskingFilter) Intersects(prop []byte) (bool, error) {
return f.BlockIntervalFilter.Intersects(prop)
}
var _ DataBlockIntervalCollector = (*testKeysSuffixIntervalCollector)(nil)
// testKeysSuffixIntervalCollector maintains an interval over the timestamps in
// MVCC-like suffixes for keys (e.g. foo@123).
type testKeysSuffixIntervalCollector struct {
initialized bool
lower, upper uint64
}
// Add implements DataBlockIntervalCollector by adding the timestamp(s) in the
// suffix(es) of this record to the current interval.
//
// Note that range sets and unsets may have multiple suffixes. Range key deletes
// do not have a suffix. All other point keys have a single suffix.
func (c *testKeysSuffixIntervalCollector) Add(key base.InternalKey, value []byte) error {
i := testkeys.Comparer.Split(key.UserKey)
if i == len(key.UserKey) {
c.initialized = true
c.lower, c.upper = 0, math.MaxUint64
return nil
}
ts, err := testkeys.ParseSuffix(key.UserKey[i:])
if err != nil {
return err
}
uts := uint64(ts)
if !c.initialized {
c.lower, c.upper = uts, uts+1
c.initialized = true
return nil
}
if uts < c.lower {
c.lower = uts
}
if uts >= c.upper {
c.upper = uts + 1
}
return nil
}
// FinishDataBlock implements DataBlockIntervalCollector.
func (c *testKeysSuffixIntervalCollector) FinishDataBlock() (lower, upper uint64, err error) {
l, u := c.lower, c.upper
c.lower, c.upper = 0, 0
c.initialized = false
return l, u, nil
}

View File

@@ -9,6 +9,12 @@ import "github.com/cockroachdb/pebble/internal/base"
// InternalKeyKind exports the base.InternalKeyKind type.
type InternalKeyKind = base.InternalKeyKind
// SeekGEFlags exports base.SeekGEFlags.
type SeekGEFlags = base.SeekGEFlags
// SeekLTFlags exports base.SeekLTFlags.
type SeekLTFlags = base.SeekLTFlags
// These constants are part of the file format, and should not be changed.
const (
InternalKeyKindDelete = base.InternalKeyKindDelete

View File

@@ -33,7 +33,8 @@ func (w *rawBlockWriter) add(key InternalKey, value []byte) {
// keys are stored in "raw" format (i.e. not as internal keys). Note that there
// is significant similarity between this code and the code in blockIter. Yet
// reducing duplication is difficult due to the blockIter being performance
// critical.
// critical. rawBlockIter must only be used for blocks where the value is
// stored together with the key.
type rawBlockIter struct {
cmp Compare
offset int32
@@ -148,19 +149,6 @@ func (i *rawBlockIter) SeekGE(key []byte) bool {
return i.Valid()
}
// SeekPrefixGE implements internalIterator.SeekPrefixGE, as documented in the
// pebble package.
func (i *rawBlockIter) SeekPrefixGE(key []byte) bool {
// This should never be called as prefix iteration is never used with raw blocks.
panic("pebble: SeekPrefixGE unimplemented")
}
// SeekLT implements internalIterator.SeekLT, as documented in the pebble
// package.
func (i *rawBlockIter) SeekLT(key []byte) bool {
panic("pebble/table: SeekLT unimplemented")
}
// First implements internalIterator.First, as documented in the pebble
// package.
func (i *rawBlockIter) First() bool {

File diff suppressed because it is too large Load Diff

View File

@@ -9,13 +9,16 @@ import (
"github.com/cespare/xxhash/v2"
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/rangekey"
)
// RewriteKeySuffixes copies the content of the passed SSTable bytes to a new
// sstable, written to `out`, in which the suffix `from` has is replaced with
// `to` in every key. The input sstable must consist of only Sets and every key
// must have `from` as its suffix as determined by the Split function of the
// Comparer in the passed WriterOptions.
// `to` in every key. The input sstable must consist of only Sets or RangeKeySets
// and every key must have `from` as its suffix as determined by the Split
// function of the Comparer in the passed WriterOptions. Range deletes must not
// exist in this sstable, as they will be ignored.
//
// Data blocks are rewritten in parallel by `concurrency` workers and then
// assembled into a final SST. Filters are copied from the original SST without
@@ -73,12 +76,14 @@ func rewriteKeySuffixesInBlocks(
return nil, errors.Wrap(err, "rewriting data blocks")
}
// Copy over the range key block and replace suffixes in it if it exists.
if err := rewriteRangeKeyBlockToWriter(r, w, from, to); err != nil {
return nil, errors.Wrap(err, "rewriting range key blocks")
}
// Copy over the filter block if it exists (rewriteDataBlocksToWriter will
// already have ensured this is valid if it exists).
if w.filter != nil {
if l.Filter.Length == 0 {
return nil, errors.New("input table has no filter")
}
if w.filter != nil && l.Filter.Length > 0 {
filterBlock, _, err := readBlockBuf(r, l.Filter, nil)
if err != nil {
return nil, errors.Wrap(err, "reading filter")
@@ -173,7 +178,8 @@ func rewriteBlocks(
copy(scratch.UserKey, key.UserKey[:si])
copy(scratch.UserKey[si:], to)
bw.add(scratch, val)
v := val.InPlaceValue()
bw.add(scratch, v)
if output[i].start.UserKey == nil {
keyAlloc, output[i].start = cloneKeyWithBuf(scratch, keyAlloc)
}
@@ -205,6 +211,10 @@ func rewriteDataBlocksToWriter(
split Split,
concurrency int,
) error {
if r.Properties.NumEntries == 0 {
// No point keys.
return nil
}
blocks := make([]blockWithSpan, len(data))
if w.filter != nil {
@@ -313,8 +323,47 @@ func rewriteDataBlocksToWriter(
w.props.NumEntries = r.Properties.NumEntries
w.props.RawKeySize = r.Properties.RawKeySize
w.props.RawValueSize = r.Properties.RawValueSize
w.meta.SmallestPoint, w.meta.LargestPoint = blocks[0].start, blocks[len(blocks)-1].end
w.meta.HasPointKeys = true
w.meta.SetSmallestPointKey(blocks[0].start)
w.meta.SetLargestPointKey(blocks[len(blocks)-1].end)
return nil
}
func rewriteRangeKeyBlockToWriter(r *Reader, w *Writer, from, to []byte) error {
iter, err := r.NewRawRangeKeyIter()
if err != nil {
return err
}
if iter == nil {
// No range keys.
return nil
}
defer iter.Close()
for s := iter.First(); s != nil; s = iter.Next() {
if !s.Valid() {
break
}
for i := range s.Keys {
if s.Keys[i].Kind() != base.InternalKeyKindRangeKeySet {
return errBadKind
}
if !bytes.Equal(s.Keys[i].Suffix, from) {
return errors.Errorf("key has suffix %q, expected %q", s.Keys[i].Suffix, from)
}
s.Keys[i].Suffix = to
}
err := rangekey.Encode(s, func(k base.InternalKey, v []byte) error {
// Calling AddRangeKey instead of addRangeKeySpan bypasses the fragmenter.
// This is okay because the raw fragments off of `iter` are already
// fragmented, and suffix replacement should not affect fragmentation.
return w.AddRangeKey(k, v)
})
if err != nil {
return err
}
}
return nil
}
@@ -362,11 +411,18 @@ func RewriteKeySuffixesViaWriter(
scratch.UserKey = append(scratch.UserKey, to...)
scratch.Trailer = k.Trailer
if w.addPoint(scratch, v); err != nil {
val, _, err := v.Value(nil)
if err != nil {
return nil, err
}
if w.addPoint(scratch, val); err != nil {
return nil, err
}
k, v = i.Next()
}
if err := rewriteRangeKeyBlockToWriter(r, w, from, to); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}

View File

@@ -17,8 +17,6 @@ type writeTask struct {
// currIndexBlock is the index block on which indexBlock.add must be called.
currIndexBlock *indexBlockBuf
indexEntrySep InternalKey
// inflightSize is used to decrement Writer.coordination.sizeEstimate.inflightSize.
inflightSize int
// inflightIndexEntrySize is used to decrement Writer.indexBlock.sizeEstimate.inflightSize.
indexInflightSize int
// If the index block is finished, then we set the finishedIndexProps here.
@@ -69,11 +67,6 @@ func (w *writeQueue) performWrite(task *writeTask) error {
return err
}
// Update the size estimates after writing the data block to disk.
w.writer.coordination.sizeEstimate.dataBlockWritten(
w.writer.meta.Size, task.inflightSize, int(bh.Length),
)
bhp = BlockHandleWithProperties{BlockHandle: bh, Props: task.buf.dataBlockProps}
if err = w.writer.addIndexEntry(
task.indexEntrySep, bhp, task.buf.tmp[:], task.flushableIndexBlock, task.currIndexBlock,
@@ -114,7 +107,6 @@ func (w *writeQueue) runWorker() {
w.wg.Done()
}
//lint:ignore U1000 - Will be used in a future pr.
func (w *writeQueue) add(task *writeTask) {
w.tasks <- task
}

View File

@@ -221,7 +221,9 @@ type coordinationState struct {
func (c *coordinationState) init(parallelismEnabled bool, writer *Writer) {
c.parallelismEnabled = parallelismEnabled
c.sizeEstimate.useMutex = parallelismEnabled
// useMutex is false regardless of parallelismEnabled, because we do not do
// parallel compression yet.
c.sizeEstimate.useMutex = false
// writeQueueSize determines the size of the write queue, or the number
// of items which can be added to the queue without blocking. By default, we
@@ -234,6 +236,47 @@ func (c *coordinationState) init(parallelismEnabled bool, writer *Writer) {
c.writeQueue = newWriteQueue(writeQueueSize, writer)
}
// sizeEstimate is a general purpose helper for estimating two kinds of sizes:
// A. The compressed sstable size, which is useful for deciding when to start
// a new sstable during flushes or compactions. In practice, we use this in
// estimating the data size (excluding the index).
// B. The size of index blocks to decide when to start a new index block.
//
// There are some terminology peculiarities which are due to the origin of
// sizeEstimate for use case A with parallel compression enabled (for which
// the code has not been merged). Specifically this relates to the terms
// "written" and "compressed".
// - The notion of "written" for case A is sufficiently defined by saying that
// the data block is compressed. Waiting for the actual data block write to
// happen can result in unnecessary estimation, when we already know how big
// it will be in compressed form. Additionally, with the forthcoming value
// blocks containing older MVCC values, these compressed block will be held
// in-memory until late in the sstable writing, and we do want to accurately
// account for them without waiting for the actual write.
// For case B, "written" means that the index entry has been fully
// generated, and has been added to the uncompressed block buffer for that
// index block. It does not include actually writing a potentially
// compressed index block.
// - The notion of "compressed" is to differentiate between a "inflight" size
// and the actual size, and is handled via computing a compression ratio
// observed so far (defaults to 1).
// For case A, this is actual data block compression, so the "inflight" size
// is uncompressed blocks (that are no longer being written to) and the
// "compressed" size is after they have been compressed.
// For case B the inflight size is for a key-value pair in the index for
// which the value size (the encoded size of the BlockHandleWithProperties)
// is not accurately known, while the compressed size is the size of that
// entry when it has been added to the (in-progress) index ssblock.
//
// Usage: To update state, one can optionally provide an inflight write value
// using addInflight (used for case B). When something is "written" the state
// can be updated using either writtenWithDelta or writtenWithTotal, which
// provide the actual delta size or the total size (latter must be
// monotonically non-decreasing). If there were no calls to addInflight, there
// isn't any real estimation happening here. So case A does not do any real
// estimation. However, when we introduce parallel compression, there will be
// estimation in that the client goroutine will call addInFlight and the
// compression goroutines will call writtenWithDelta.
type sizeEstimate struct {
// emptySize is the size when there is no inflight data, and numEntries is 0.
// emptySize is constant once set.
@@ -300,18 +343,22 @@ func (s *sizeEstimate) addInflight(size int) {
s.inflightSize += uint64(size)
}
func (s *sizeEstimate) written(newTotalSize uint64, inflightSize int, finalEntrySize int) {
s.inflightSize -= uint64(inflightSize)
func (s *sizeEstimate) writtenWithTotal(newTotalSize uint64, inflightSize int) {
finalEntrySize := int(newTotalSize - s.totalSize)
s.writtenWithDelta(finalEntrySize, inflightSize)
}
func (s *sizeEstimate) writtenWithDelta(finalEntrySize int, inflightSize int) {
if inflightSize > 0 {
// This entry was previously inflight, so we should decrement inflight
// entries.
// entries and update the "compression" stats for future estimation.
s.numInflightEntries--
s.inflightSize -= uint64(inflightSize)
s.uncompressedSize += uint64(inflightSize)
s.compressedSize += uint64(finalEntrySize)
}
s.numWrittenEntries++
s.totalSize = newTotalSize
s.uncompressedSize += uint64(inflightSize)
s.compressedSize += uint64(finalEntrySize)
s.totalSize += uint64(finalEntrySize)
}
func (s *sizeEstimate) clear() {
@@ -368,7 +415,6 @@ func (i *indexBlockBuf) shouldFlush(
defer i.size.mu.Unlock()
}
// nEntries := i.size.estimate.numWrittenEntries + i.size.estimate.numInflightEntries
nEntries := i.size.estimate.numTotalEntries()
return shouldFlush(
sep, valueLen, i.restartInterval, int(i.size.estimate.size()),
@@ -382,10 +428,7 @@ func (i *indexBlockBuf) add(key InternalKey, value []byte, inflightSize int) {
i.size.mu.Lock()
defer i.size.mu.Unlock()
}
// Since, we're not compressing index entries when adding them to index blocks,
// we assume that the size of entry written to the index block is equal to the
// size of the inflight entry, giving us a compression ratio of 1.
i.size.estimate.written(uint64(size), inflightSize, inflightSize)
i.size.estimate.writtenWithTotal(uint64(size), inflightSize)
}
func (i *indexBlockBuf) finish() []byte {
@@ -424,11 +467,12 @@ func (i *indexBlockBuf) estimatedSize() uint64 {
return i.size.estimate.size()
}
// sizeEstimate is used for sstable size estimation. sizeEstimate can be accessed by
// the Writer client, writeQueue, compressionQueue goroutines. Fields should only be
// read/updated through the functions defined on the *sizeEstimate type.
// sizeEstimate is used for sstable size estimation. sizeEstimate can be
// accessed by the Writer client and compressionQueue goroutines. Fields
// should only be read/updated through the functions defined on the
// *sizeEstimate type.
type dataBlockEstimates struct {
// If we don't do block compression, block writes in parallel, then we don't need to take
// If we don't do block compression in parallel, then we don't need to take
// the performance hit of synchronizing using this mutex.
useMutex bool
mu sync.Mutex
@@ -436,18 +480,16 @@ type dataBlockEstimates struct {
estimate sizeEstimate
}
// newTotalSize is the new w.meta.Size. inflightSize is the uncompressed block size estimate which
// was previously added to sizeEstimate.inflightSize. writtenSize is the compressed size of the block
// which was written to disk.
func (d *dataBlockEstimates) dataBlockWritten(
newTotalSize uint64, inflightSize int, writtenSize int,
) {
// inflightSize is the uncompressed block size estimate which has been
// previously provided to addInflightDataBlock(). If addInflightDataBlock()
// has not been called, this must be set to 0. compressedSize is the
// compressed size of the block.
func (d *dataBlockEstimates) dataBlockCompressed(compressedSize int, inflightSize int) {
if d.useMutex {
d.mu.Lock()
defer d.mu.Unlock()
}
d.estimate.written(newTotalSize, inflightSize, writtenSize)
d.estimate.writtenWithDelta(compressedSize+blockTrailerLen, inflightSize)
}
// size is an estimated size of datablock data which has been written to disk.
@@ -456,18 +498,19 @@ func (d *dataBlockEstimates) size() uint64 {
d.mu.Lock()
defer d.mu.Unlock()
}
// Use invariants to make sure that the size estimation works as expected
// when parallelism is disabled.
// If there is no parallel compression, there should not be any inflight bytes.
if invariants.Enabled && !d.useMutex {
if d.estimate.inflightSize != 0 {
panic("unexpected inflight entry in data block size estimation")
}
}
return d.estimate.size()
}
// Avoid linter unused error.
var _ = (&dataBlockEstimates{}).addInflightDataBlock
// NB: unused since no parallel compression.
func (d *dataBlockEstimates) addInflightDataBlock(size int) {
if d.useMutex {
d.mu.Lock()
@@ -609,9 +652,10 @@ type indexBlockAndBlockProperties struct {
block []byte
}
// Set sets the value for the given key. The sequence number is set to
// 0. Intended for use to externally construct an sstable before ingestion into
// a DB.
// Set sets the value for the given key. The sequence number is set to 0.
// Intended for use to externally construct an sstable before ingestion into a
// DB. For a given Writer, the keys passed to Set must be in strictly increasing
// order.
//
// TODO(peter): untested
func (w *Writer) Set(key, value []byte) error {
@@ -708,8 +752,8 @@ func (w *Writer) addPoint(key InternalKey, value []byte) error {
// 3.5% faster on BenchmarkWriter on go1.13. Remove if go1.14 or future
// versions show this to not be a performance win.
x := w.compare(largestPointKey.UserKey, key.UserKey)
if x > 0 || (x == 0 && largestPointKey.Trailer < key.Trailer) {
w.err = errors.Errorf("pebble: keys must be added in order: %s, %s",
if x > 0 || (x == 0 && largestPointKey.Trailer <= key.Trailer) {
w.err = errors.Errorf("pebble: keys must be added in strictly increasing order: %s, %s",
largestPointKey.Pretty(w.formatKey), key.Pretty(w.formatKey))
return w.err
}
@@ -790,7 +834,7 @@ func (w *Writer) addTombstone(key InternalKey, value []byte) error {
return w.err
}
if prevKey.SeqNum() <= key.SeqNum() {
w.err = errors.Errorf("pebble: keys must be added in order: %s, %s",
w.err = errors.Errorf("pebble: keys must be added in strictly increasing order: %s, %s",
prevKey.Pretty(w.formatKey), key.Pretty(w.formatKey))
return w.err
}
@@ -938,7 +982,9 @@ func (w *Writer) addRangeKeySpan(span keyspan.Span) error {
func (w *Writer) coalesceSpans(span keyspan.Span) {
// This method is the emit function of the Fragmenter, so span.Keys is only
// owned by this span and it's safe to mutate.
err := rangekey.Coalesce(w.compare, span, &w.rangeKeyCoalesced)
w.rangeKeyCoalesced.Start = span.Start
w.rangeKeyCoalesced.End = span.End
err := rangekey.Coalesce(w.compare, span.Keys, &w.rangeKeyCoalesced.Keys)
if err != nil {
w.err = errors.Newf("sstable: could not coalesce span: %s", err)
return
@@ -946,7 +992,7 @@ func (w *Writer) coalesceSpans(span keyspan.Span) {
// NB: The span only contains range keys and is internally consistent (eg,
// no duplicate suffixes, no additional keys after a RANGEKEYDEL).
w.err = firstError(w.err, w.rangeKeyEncoder.Encode(w.rangeKeyCoalesced))
w.err = firstError(w.err, w.rangeKeyEncoder.Encode(&w.rangeKeyCoalesced))
}
func (w *Writer) addRangeKey(key InternalKey, value []byte) error {
@@ -1080,19 +1126,16 @@ func (w *Writer) maybeAddToFilter(key []byte) {
}
func (w *Writer) flush(key InternalKey) error {
estimatedUncompressedSize := w.dataBlockBuf.dataBlock.estimatedSize()
w.coordination.sizeEstimate.addInflightDataBlock(estimatedUncompressedSize)
var err error
// We're finishing a data block.
err = w.finishDataBlockProps(w.dataBlockBuf)
err := w.finishDataBlockProps(w.dataBlockBuf)
if err != nil {
return err
}
w.dataBlockBuf.finish()
w.dataBlockBuf.compressAndChecksum(w.compression)
// Since dataBlockEstimates.addInflightDataBlock was never called, the
// inflightSize is set to 0.
w.coordination.sizeEstimate.dataBlockCompressed(len(w.dataBlockBuf.compressed), 0)
// Determine if the index block should be flushed. Since we're accessing the
// dataBlockBuf.dataBlock.curKey here, we have to make sure that once we start
@@ -1139,7 +1182,6 @@ func (w *Writer) flush(key InternalKey) error {
writeTask.compressionDone <- true
writeTask.buf = w.dataBlockBuf
writeTask.indexEntrySep = sep
writeTask.inflightSize = estimatedUncompressedSize
writeTask.currIndexBlock = w.indexBlock
writeTask.indexInflightSize = sep.Size() + encodedBHPEstimatedSize
writeTask.finishedIndexProps = indexProps
@@ -1813,14 +1855,6 @@ func (w *Writer) Close() (err error) {
// EstimatedSize returns the estimated size of the sstable being written if a
// call to Finish() was made without adding additional keys.
func (w *Writer) EstimatedSize() uint64 {
if invariants.Enabled && !w.coordination.parallelismEnabled {
// The w.meta.Size should only be accessed from the writeQueue goroutine
// if parallelism is enabled, but since it isn't we break that invariant
// here.
if w.coordination.sizeEstimate.size() != w.meta.Size {
panic("sstable size estimation sans parallelism is incorrect")
}
}
return w.coordination.sizeEstimate.size() +
uint64(w.dataBlockBuf.dataBlock.estimatedSize()) +
w.indexBlock.estimatedSize()
@@ -1838,7 +1872,7 @@ func (w *Writer) Metadata() (*WriterMetadata, error) {
// WriterOption provide an interface to do work on Writer while it is being
// opened.
type WriterOption interface {
// writerAPply is called on the writer during opening in order to set
// writerApply is called on the writer during opening in order to set
// internal parameters.
writerApply(*Writer)
}

View File

@@ -28,6 +28,25 @@ import (
var emptyIter = &errorIter{err: nil}
var emptyKeyspanIter = &errorKeyspanIter{err: nil}
// filteredAll is a singleton internalIterator implementation used when an
// sstable does contain point keys, but all the keys are filtered by the active
// PointKeyFilters set in the iterator's IterOptions.
//
// filteredAll implements filteredIter, ensuring the level iterator recognizes
// when it may need to return file boundaries to keep the rangeDelIter open
// during mergingIter operation.
var filteredAll = &filteredAllKeysIter{errorIter: errorIter{err: nil}}
var _ filteredIter = filteredAll
type filteredAllKeysIter struct {
errorIter
}
func (s *filteredAllKeysIter) MaybeFilteredKeys() bool {
return true
}
var tableCacheLabels = pprof.Labels("pebble", "table-cache")
// tableCacheOpts contains the db specific fields
@@ -114,9 +133,9 @@ func (c *tableCacheContainer) close() error {
}
func (c *tableCacheContainer) newIters(
file *manifest.FileMetadata, opts *IterOptions, bytesIterated *uint64,
file *manifest.FileMetadata, opts *IterOptions, internalOpts internalIterOpts,
) (internalIterator, keyspan.FragmentIterator, error) {
return c.tableCache.getShard(file.FileNum).newIters(file, opts, bytesIterated, &c.dbOpts)
return c.tableCache.getShard(file.FileNum).newIters(file, opts, internalOpts, &c.dbOpts)
}
func (c *tableCacheContainer) newRangeKeyIter(
@@ -280,8 +299,9 @@ type tableCacheShard struct {
sizeCold int
sizeTest int
}
releasing sync.WaitGroup
releasingCh chan *tableCacheValue
releasing sync.WaitGroup
releasingCh chan *tableCacheValue
releaseLoopExit sync.WaitGroup
}
func (c *tableCacheShard) init(size int) {
@@ -290,6 +310,7 @@ func (c *tableCacheShard) init(size int) {
c.mu.nodes = make(map[tableCacheKey]*tableCacheNode)
c.mu.coldTarget = size
c.releasingCh = make(chan *tableCacheValue, 100)
c.releaseLoopExit.Add(1)
go c.releaseLoop()
if invariants.RaceEnabled {
@@ -299,6 +320,7 @@ func (c *tableCacheShard) init(size int) {
func (c *tableCacheShard) releaseLoop() {
pprof.Do(context.Background(), tableCacheLabels, func(context.Context) {
defer c.releaseLoopExit.Done()
for v := range c.releasingCh {
v.release(c)
}
@@ -312,14 +334,15 @@ func (c *tableCacheShard) checkAndIntersectFilters(
v *tableCacheValue,
tableFilter func(userProps map[string]string) bool,
blockPropertyFilters []BlockPropertyFilter,
boundLimitedFilter sstable.BoundLimitedBlockPropertyFilter,
) (ok bool, filterer *sstable.BlockPropertiesFilterer, err error) {
if tableFilter != nil &&
!tableFilter(v.reader.Properties.UserProperties) {
return false, nil, nil
}
if len(blockPropertyFilters) > 0 {
filterer = sstable.NewBlockPropertiesFilterer(blockPropertyFilters)
if boundLimitedFilter != nil || len(blockPropertyFilters) > 0 {
filterer = sstable.NewBlockPropertiesFilterer(blockPropertyFilters, boundLimitedFilter)
intersects, err :=
filterer.IntersectsUserPropsAndFinishInit(v.reader.Properties.UserProperties)
if err != nil {
@@ -333,7 +356,10 @@ func (c *tableCacheShard) checkAndIntersectFilters(
}
func (c *tableCacheShard) newIters(
file *manifest.FileMetadata, opts *IterOptions, bytesIterated *uint64, dbOpts *tableCacheOpts,
file *manifest.FileMetadata,
opts *IterOptions,
internalOpts internalIterOpts,
dbOpts *tableCacheOpts,
) (internalIterator, keyspan.FragmentIterator, error) {
// Calling findNode gives us the responsibility of decrementing v's
// refCount. If opening the underlying table resulted in error, then we
@@ -350,17 +376,36 @@ func (c *tableCacheShard) newIters(
var filterer *sstable.BlockPropertiesFilterer
var err error
if opts != nil {
ok, filterer, err = c.checkAndIntersectFilters(v, opts.TableFilter, opts.PointKeyFilters)
ok, filterer, err = c.checkAndIntersectFilters(v, opts.TableFilter,
opts.PointKeyFilters, internalOpts.boundLimitedFilter)
}
if err != nil {
c.unrefValue(v)
return nil, nil, err
}
// NB: range-del iterator does not maintain a reference to the table, nor
// does it need to read from it after creation.
rangeDelIter, err := v.reader.NewRawRangeDelIter()
if err != nil {
c.unrefValue(v)
return nil, nil, err
}
if !ok {
c.unrefValue(v)
// Return the empty iterator. This iterator has no mutable state, so
// Return an empty iterator. This iterator has no mutable state, so
// using a singleton is fine.
return emptyIter, nil, err
// NB: We still return the potentially non-empty rangeDelIter. This
// ensures the iterator observes the file's range deletions even if the
// block property filters exclude all the file's point keys. The range
// deletions may still delete keys lower in the LSM in files that DO
// match the active filters.
//
// The point iterator returned must implement the filteredIter
// interface, so that the level iterator surfaces file boundaries when
// range deletions are present.
return filteredAll, rangeDelIter, err
}
var iter sstable.Iterator
@@ -368,20 +413,22 @@ func (c *tableCacheShard) newIters(
if opts != nil {
useFilter = manifest.LevelToInt(opts.level) != 6 || opts.UseL6Filters
}
if bytesIterated != nil {
iter, err = v.reader.NewCompactionIter(bytesIterated)
if internalOpts.bytesIterated != nil {
iter, err = v.reader.NewCompactionIter(internalOpts.bytesIterated)
} else {
iter, err = v.reader.NewIterWithBlockPropertyFilters(
opts.GetLowerBound(), opts.GetUpperBound(), filterer, useFilter)
opts.GetLowerBound(), opts.GetUpperBound(), filterer, useFilter, internalOpts.stats)
}
if err != nil {
if rangeDelIter != nil {
_ = rangeDelIter.Close()
}
c.unrefValue(v)
return nil, nil, err
}
// NB: v.closeHook takes responsibility for calling unrefValue(v) here.
iter.SetCloseHook(func(i sstable.Iterator) error {
return v.closeHook(i)
})
// NB: v.closeHook takes responsibility for calling unrefValue(v) here. Take
// care to avoid introduceingan allocation here by adding a closure.
iter.SetCloseHook(v.closeHook)
atomic.AddInt32(&c.atomic.iterCount, 1)
atomic.AddInt32(dbOpts.atomic.iterCount, 1)
@@ -390,19 +437,7 @@ func (c *tableCacheShard) newIters(
c.mu.iters[iter] = debug.Stack()
c.mu.Unlock()
}
// NB: range-del iterator does not maintain a reference to the table, nor
// does it need to read from it after creation.
rangeDelIter, err := v.reader.NewRawRangeDelIter()
if err != nil {
_ = iter.Close()
return nil, nil, err
}
if rangeDelIter != nil {
return iter, rangeDelIter, nil
}
// NB: Translate a nil range-del iterator into a nil interface.
return iter, nil, nil
return iter, rangeDelIter, nil
}
func (c *tableCacheShard) newRangeKeyIter(
@@ -421,8 +456,13 @@ func (c *tableCacheShard) newRangeKeyIter(
ok := true
var err error
if opts != nil {
ok, _, err = c.checkAndIntersectFilters(v, nil, opts.RangeKeyFilters)
// Don't filter a table's range keys if the file contains RANGEKEYDELs.
// The RANGEKEYDELs may delete range keys in other levels. Skipping the
// file's range key blocks may surface deleted range keys below. This is
// done here, rather than deferring to the block-property collector in order
// to maintain parity with point keys and the treatment of RANGEDELs.
if opts != nil && v.reader.Properties.NumRangeKeyDels == 0 {
ok, _, err = c.checkAndIntersectFilters(v, nil, opts.RangeKeyFilters, nil)
}
if err != nil {
c.unrefValue(v)
@@ -435,27 +475,14 @@ func (c *tableCacheShard) newRangeKeyIter(
return emptyKeyspanIter, err
}
var iter sstable.FragmentIterator
// TODO(bilal): We are currently passing through the raw blockIter for range
// keys. This iter does not support bounds (eg. SetBounds will panic).
// Any future users of the iter returned by this function need to make any
// bounds-specific optimizations themselves.
var iter keyspan.FragmentIterator
iter, err = v.reader.NewRawRangeKeyIter()
if err != nil || iter == nil {
c.unrefValue(v)
return nil, err
}
// NB: v.closeHook takes responsibility for calling unrefValue(v) here.
iter.SetCloseHook(func(i keyspan.FragmentIterator) error {
return v.closeHook(i)
})
// iter is a block iter that holds the entire value of the block in memory.
// No need to hold onto a ref of the cache value.
c.unrefValue(v)
atomic.AddInt32(&c.atomic.iterCount, 1)
atomic.AddInt32(dbOpts.atomic.iterCount, 1)
if invariants.RaceEnabled {
c.mu.Lock()
c.mu.iters[iter] = debug.Stack()
c.mu.Unlock()
if err != nil || iter == nil {
return nil, err
}
return iter, nil
@@ -607,7 +634,7 @@ func (c *tableCacheShard) findNode(meta *fileMetadata, dbOpts *tableCacheOpts) *
}
// Cache the closure invoked when an iterator is closed. This avoids an
// allocation on every call to newIters.
v.closeHook = func(i io.Closer) error {
v.closeHook = func(i sstable.Iterator) error {
if invariants.RaceEnabled {
c.mu.Lock()
delete(c.mu.iters, i)
@@ -829,15 +856,19 @@ func (c *tableCacheShard) Close() error {
// complete. This behavior is used by iterator leak tests. Leaking the
// goroutine for these tests is less bad not closing the iterator which
// triggers other warnings about block cache handles not being released.
if err == nil {
close(c.releasingCh)
if err != nil {
c.releasing.Wait()
return err
}
close(c.releasingCh)
c.releasing.Wait()
c.releaseLoopExit.Wait()
return err
}
type tableCacheValue struct {
closeHook func(i io.Closer) error
closeHook func(i sstable.Iterator) error
reader *sstable.Reader
filename string
err error

View File

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

View File

@@ -110,6 +110,9 @@ type versionSet struct {
writing bool
writerCond sync.Cond
// State for deciding when to write a snapshot. Protected by mu.
lastSnapshotFileCount int64
editsSinceLastSnapshotFileCount int64
}
func (vs *versionSet) init(
@@ -391,13 +394,76 @@ func (vs *versionSet) logAndApply(
currentVersion := vs.currentVersion()
var newVersion *version
// Generate a new manifest if we don't currently have one, or the current one
// is too large.
// Generate a new manifest if we don't currently have one, or forceRotation
// is true, or the current one is too large.
//
// For largeness, we do not exclusively use MaxManifestFileSize size
// threshold since we have had incidents where due to either large keys or
// large numbers of files, each edit results in a snapshot + write of the
// edit. This slows the system down since each flush or compaction is
// writing a new manifest snapshot. The primary goal of the size-based
// rollover logic is to ensure that when reopening a DB, the number of edits
// that need to be replayed on top of the snapshot is "sane". Rolling over
// to a new manifest after each edit is not relevant to that goal.
//
// Consider the following cases:
// - The number of live files F in the DB is roughly stable: after writing
// the snapshot (with F files), say we require that there be enough edits
// such that the cumulative number of files in those edits, E, be greater
// than F. This will ensure that the total amount of time in logAndApply
// that is spent in snapshot writing is ~50%.
//
// - The number of live files F in the DB is shrinking drastically, say from
// F to F/10: This can happen for various reasons, like wide range
// tombstones, or large numbers of smaller than usual files that are being
// merged together into larger files. And say the new files generated
// during this shrinkage is insignificant compared to F/10, and so for
// this example we will assume it is effectively 0. After this shrinking,
// E = 0.9F, and so if we used the previous snapshot file count, F, as the
// threshold that needs to be exceeded, we will further delay the snapshot
// writing. Which means on DB reopen we will need to replay 0.9F edits to
// get to a version with 0.1F files. It would be better to create a new
// snapshot when E exceeds the number of files in the current version.
//
// - The number of live files F in the DB is growing via perfect ingests
// into L6: Say we wrote the snapshot when there were F files and now we
// have 10F files, so E = 9F. We will further delay writing a new
// snapshot. This case can be critiqued as contrived, but we consider it
// nonetheless.
//
// The logic below uses the min of the last snapshot file count and the file
// count in the current version.
editCount := int64(len(ve.DeletedFiles) + len(ve.NewFiles))
vs.editsSinceLastSnapshotFileCount += editCount
sizeExceeded := vs.manifest.Size() >= vs.opts.MaxManifestFileSize
requireRotation := forceRotation || vs.manifest == nil
computeNextSnapshotFileCount := func() int64 {
var count int64
for i := range vs.metrics.Levels {
count += vs.metrics.Levels[i].NumFiles
}
return count
}
var nextSnapshotFileCount int64
if sizeExceeded && !requireRotation {
if vs.editsSinceLastSnapshotFileCount > vs.lastSnapshotFileCount {
requireRotation = true
} else {
nextSnapshotFileCount = computeNextSnapshotFileCount()
if vs.editsSinceLastSnapshotFileCount > nextSnapshotFileCount {
requireRotation = true
}
}
}
var newManifestFileNum FileNum
var prevManifestFileSize uint64
if forceRotation || vs.manifest == nil || vs.manifest.Size() >= vs.opts.MaxManifestFileSize {
if requireRotation {
newManifestFileNum = vs.getNextFileNum()
prevManifestFileSize = uint64(vs.manifest.Size())
if nextSnapshotFileCount == 0 {
// Haven't computed it, or happens to be 0.
nextSnapshotFileCount = computeNextSnapshotFileCount()
}
}
// Grab certain values before releasing vs.mu, in case createManifest() needs
@@ -418,7 +484,7 @@ func (vs *versionSet) logAndApply(
var err error
newVersion, zombies, err = bve.Apply(currentVersion, vs.cmp, vs.opts.Comparer.FormatKey, vs.opts.FlushSplitBytes, vs.opts.Experimental.ReadCompactionRate)
if err != nil {
return err
return errors.Wrap(err, "MANIFEST apply failed")
}
if newManifestFileNum != 0 {
@@ -429,13 +495,13 @@ func (vs *versionSet) logAndApply(
FileNum: newManifestFileNum,
Err: err,
})
return err
return errors.Wrap(err, "MANIFEST create failed")
}
}
w, err := vs.manifest.Next()
if err != nil {
return err
return errors.Wrap(err, "MANIFEST next record write failed")
}
// NB: Any error from this point on is considered fatal as we don't now if
// the MANIFEST write occurred or not. Trying to determine that is
@@ -443,22 +509,18 @@ func (vs *versionSet) logAndApply(
// database is open. In particular, that mechanism generates a new MANIFEST
// and ensures it is synced.
if err := ve.Encode(w); err != nil {
vs.opts.Logger.Fatalf("MANIFEST write failed: %v", err)
return err
return errors.Wrap(err, "MANIFEST write failed")
}
if err := vs.manifest.Flush(); err != nil {
vs.opts.Logger.Fatalf("MANIFEST flush failed: %v", err)
return err
return errors.Wrap(err, "MANIFEST flush failed")
}
if err := vs.manifestFile.Sync(); err != nil {
vs.opts.Logger.Fatalf("MANIFEST sync failed: %v", err)
return err
return errors.Wrap(err, "MANIFEST sync failed")
}
if newManifestFileNum != 0 {
// NB: setCurrent is responsible for syncing the data directory.
if err := vs.setCurrent(newManifestFileNum); err != nil {
vs.opts.Logger.Fatalf("MANIFEST set current failed: %v", err)
return err
return errors.Wrap(err, "MANIFEST set current failed")
}
vs.opts.EventListener.ManifestCreated(ManifestCreateInfo{
JobID: jobID,
@@ -468,9 +530,21 @@ func (vs *versionSet) logAndApply(
}
return nil
}(); err != nil {
// Any error encountered during any of the operations in the previous
// closure are considered fatal. Treating such errors as fatal is preferred
// to attempting to unwind various file and b-tree reference counts, and
// re-generating L0 sublevel metadata. This may change in the future, if
// certain manifest / WAL operations become retryable. For more context, see
// #1159 and #1792.
vs.opts.Logger.Fatalf("%s", err)
return err
}
if requireRotation {
// Successfully rotated.
vs.lastSnapshotFileCount = nextSnapshotFileCount
vs.editsSinceLastSnapshotFileCount = editCount
}
// Now that DB.mu is held again, initialize compacting file info in
// L0Sublevels.
inProgress := inProgressCompactions()
@@ -527,7 +601,7 @@ func (vs *versionSet) logAndApply(
return nil
}
func (vs *versionSet) incrementCompactions(kind compactionKind) {
func (vs *versionSet) incrementCompactions(kind compactionKind, extraLevels []*compactionLevel) {
switch kind {
case compactionKindDefault:
vs.metrics.Compact.Count++
@@ -556,6 +630,9 @@ func (vs *versionSet) incrementCompactions(kind compactionKind) {
vs.metrics.Compact.Count++
vs.metrics.Compact.RewriteCount++
}
if len(extraLevels) > 0 {
vs.metrics.Compact.MultiLevelCount++
}
}
func (vs *versionSet) incrementCompactionBytes(numBytes int64) {

View File

@@ -5,7 +5,7 @@
package vfs
import (
"io/ioutil"
"io"
"sort"
"github.com/cockroachdb/errors/oserror"
@@ -94,7 +94,7 @@ func Clone(srcFS, dstFS FS, srcPath, dstPath string, opts ...CloneOption) (bool,
return true, nil
}
data, err := ioutil.ReadAll(srcFile)
data, err := io.ReadAll(srcFile)
if err != nil {
return false, err
}

View File

@@ -35,7 +35,7 @@ func (defaultFS) GetDiskUsage(path string) (DiskUsage, error) {
// [2] https://man7.org/linux/man-pages/man3/statvfs.3.html
freeBytes := uint64(stat.Frsize) * uint64(stat.Bfree)
availBytes := uint64(stat.Frsize) * uint64(stat.Bavail)
totalBytes := uint64(stat.Bsize) * uint64(stat.Blocks)
totalBytes := uint64(stat.Frsize) * uint64(stat.Blocks)
return DiskUsage{
AvailBytes: availBytes,
TotalBytes: totalBytes,

View File

@@ -78,10 +78,23 @@ type MemFS struct {
strict bool
ignoreSyncs bool
// Windows has peculiar semantics with respect to hard links and deleting
// open files. In tests meant to exercise this behavior, this flag can be
// set to error if removing an open file.
windowsSemantics bool
}
var _ FS = &MemFS{}
// UseWindowsSemantics configures whether the MemFS implements Windows-style
// semantics, in particular with respect to whether any of an open file's links
// may be removed. Windows semantics default to off.
func (y *MemFS) UseWindowsSemantics(windowsSemantics bool) {
y.mu.Lock()
defer y.mu.Unlock()
y.windowsSemantics = windowsSemantics
}
// String dumps the contents of the MemFS.
func (y *MemFS) String() string {
y.mu.Lock()
@@ -303,11 +316,14 @@ func (y *MemFS) Remove(fullname string) error {
if !ok {
return oserror.ErrNotExist
}
// Disallow removal of open files/directories which implements Windows
// semantics. This ensures that we don't regress in the ordering of
// operations and try to remove a file while it is still open.
if n := atomic.LoadInt32(&child.refs); n > 0 {
return oserror.ErrInvalid
if y.windowsSemantics {
// Disallow removal of open files/directories which implements
// Windows semantics. This ensures that we don't regress in the
// ordering of operations and try to remove a file while it is
// still open.
if n := atomic.LoadInt32(&child.refs); n > 0 {
return oserror.ErrInvalid
}
}
if len(child.children) > 0 {
return errNotEmpty

View File

@@ -12,6 +12,7 @@ import (
// SyncingFileOptions holds the options for a syncingFile.
type SyncingFileOptions struct {
NoSyncOnClose bool
BytesPerSync int
PreallocateSize int
}
@@ -20,6 +21,8 @@ type syncingFile struct {
File
fd uintptr
useSyncRange bool
closing bool
noSyncOnClose bool
bytesPerSync int64
preallocateSize int64
atomic struct {
@@ -46,6 +49,7 @@ type syncingFile struct {
func NewSyncingFile(f File, opts SyncingFileOptions) File {
s := &syncingFile{
File: f,
noSyncOnClose: bool(opts.NoSyncOnClose),
bytesPerSync: int64(opts.BytesPerSync),
preallocateSize: int64(opts.PreallocateSize),
}
@@ -172,12 +176,16 @@ func (f *syncingFile) maybeSync() error {
}
func (f *syncingFile) Close() error {
// Sync any data that has been written but not yet synced. Note that if
// SyncFileRange was used, atomic.syncOffset will be less than
// Sync any data that has been written but not yet synced unless the file
// has noSyncOnClose option explicitly set.
// Note that if SyncFileRange was used, atomic.syncOffset will be less than
// atomic.offset. See syncingFile.syncToRange.
if atomic.LoadInt64(&f.atomic.offset) > atomic.LoadInt64(&f.atomic.syncOffset) {
if err := f.Sync(); err != nil {
return errors.WithStack(err)
f.closing = true
if !f.noSyncOnClose || f.useSyncRange {
if atomic.LoadInt64(&f.atomic.offset) > atomic.LoadInt64(&f.atomic.syncOffset) {
if err := f.Sync(); err != nil {
return errors.WithStack(err)
}
}
}
return errors.WithStack(f.File.Close())

View File

@@ -76,6 +76,14 @@ func (f *syncingFile) syncToRange(offset int64) error {
// waitAfter = 0x4
)
// The flags for the sync_file_range system call. Unless the file has
// noSyncOnClose explicitly set and it is being closed, the waitBefore
// flag will be set which may block the call.
flags := write
if !f.noSyncOnClose || !f.closing {
flags |= waitBefore
}
// Note that syncToRange is only called with an offset that is guaranteed to
// be less than atomic.offset (i.e. the write offset). This implies the
// syncingFile.Close will Sync the rest of the data, as well as the file's
@@ -91,7 +99,7 @@ func (f *syncingFile) syncToRange(offset int64) error {
// data accumulates, impacting other I/O operations.
var err error
f.timeDiskOp(func() {
err = syscall.SyncFileRange(int(f.fd), 0, offset, write|waitBefore)
err = syscall.SyncFileRange(int(f.fd), 0, offset, flags)
})
return err
}

Some files were not shown because too many files have changed in this diff Show More