0

Upgrade versions of vendored libs

This commit is contained in:
eleksir
2022-11-04 16:20:16 +03:00
parent d9de157ad8
commit 76a660ebed
931 changed files with 215108 additions and 23070 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

@@ -21,7 +21,7 @@ all:
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
@@ -46,14 +46,17 @@ crossversion-meta:
${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:
@@ -80,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
@@ -90,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

@@ -855,16 +855,37 @@ func (b *Batch) NewIter(o *IterOptions) *Iterator {
// contents of the batch.
func (b *Batch) newInternalIter(o *IterOptions) *batchIter {
iter := &batchIter{}
b.initInternalIter(o, iter, b.nextSeqNum())
b.initInternalIter(o, iter)
return iter
}
func (b *Batch) initInternalIter(o *IterOptions, iter *batchIter, batchSnapshot uint64) {
func (b *Batch) initInternalIter(o *IterOptions, iter *batchIter) {
*iter = batchIter{
cmp: b.cmp,
batch: b,
iter: b.index.NewIter(o.GetLowerBound(), o.GetUpperBound()),
snapshot: batchSnapshot,
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,
}
}
@@ -934,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.
@@ -1009,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
}
@@ -1244,90 +1265,89 @@ func (i *batchIter) String() string {
return "batch"
}
func (i *batchIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, []byte) {
// Ignore trySeekUsingNext since the batch may have changed, so using Next
// would be incorrect.
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()
}
i.err = nil // clear cached iteration error
ikey := i.iter.SeekGE(key)
ikey := i.iter.SeekGE(key, flags)
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Next()
}
if ikey == nil {
return nil, nil
return nil, base.LazyValue{}
}
return ikey, i.Value()
return ikey, base.MakeInPlaceValue(i.value())
}
func (i *batchIter) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*base.InternalKey, base.LazyValue) {
i.err = nil // clear cached iteration error
return i.SeekGE(key, flags)
}
func (i *batchIter) SeekLT(key []byte, flags base.SeekLTFlags) (*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)
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Prev()
}
if ikey == nil {
return nil, nil
return nil, base.LazyValue{}
}
return ikey, i.Value()
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()
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Next()
}
if ikey == nil {
return nil, nil
return nil, base.LazyValue{}
}
return ikey, i.Value()
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()
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Prev()
}
if ikey == nil {
return nil, nil
return nil, base.LazyValue{}
}
return ikey, i.Value()
return ikey, base.MakeInPlaceValue(i.value())
}
func (i *batchIter) Next() (*InternalKey, []byte) {
func (i *batchIter) Next() (*InternalKey, base.LazyValue) {
ikey := i.iter.Next()
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Next()
}
if ikey == nil {
return nil, nil
return nil, base.LazyValue{}
}
return ikey, i.Value()
return ikey, base.MakeInPlaceValue(i.value())
}
func (i *batchIter) Prev() (*InternalKey, []byte) {
func (i *batchIter) Prev() (*InternalKey, base.LazyValue) {
ikey := i.iter.Prev()
for ikey != nil && ikey.SeqNum() >= i.snapshot {
ikey = i.iter.Prev()
}
if ikey == nil {
return nil, nil
return nil, base.LazyValue{}
}
return ikey, i.Value()
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 {
@@ -1348,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
}
@@ -1642,34 +1658,38 @@ func (i *flushableBatchIter) String() string {
// SeekGE implements internalIterator.SeekGE, as documented in the pebble
// 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, []byte) {
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, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*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, flags base.SeekLTFlags) (*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 {
@@ -1677,80 +1697,80 @@ func (i *flushableBatchIter) SeekLT(key []byte, flags base.SeekLTFlags) (*Intern
})
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 {
@@ -1760,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
@@ -1784,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 {
@@ -1823,27 +1839,27 @@ func (i *flushFlushableBatchIter) String() string {
func (i *flushFlushableBatchIter) SeekGE(
key []byte, flags base.SeekGEFlags,
) (*InternalKey, []byte) {
) (*InternalKey, base.LazyValue) {
panic("pebble: SeekGE unimplemented")
}
func (i *flushFlushableBatchIter) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*base.InternalKey, base.LazyValue) {
panic("pebble: SeekPrefixGE unimplemented")
}
func (i *flushFlushableBatchIter) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*InternalKey, []byte) {
) (*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()
@@ -1852,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")
}

View File

@@ -47,7 +47,7 @@ func expandedCompactionByteSizeLimit(opts *Options, level int, availBytes uint64
// compactions to half of available disk space. Note that this will not
// prevent compaction picking from pursuing compactions that are larger
// than this threshold before expansion.
diskMax := (availBytes / 2) / uint64(opts.MaxConcurrentCompactions)
diskMax := (availBytes / 2) / uint64(opts.MaxConcurrentCompactions())
if v > diskMax {
v = diskMax
}
@@ -342,7 +342,7 @@ func rangeKeyCompactionTransform(
usedLen := 0
for i >= 0 {
start := j
for j < len(s.Keys) && !base.Visible(s.Keys[j].SeqNum(), snapshots[i]) {
for j < len(s.Keys) && !base.Visible(s.Keys[j].SeqNum(), snapshots[i], base.InternalKeySeqNumMax) {
// Include j in current partition.
j++
}
@@ -379,9 +379,11 @@ type compaction struct {
kind compactionKind
cmp Compare
equal Equal
comparer *base.Comparer
formatKey base.FormatKey
logger Logger
version *version
stats base.InternalIteratorStats
score float64
@@ -462,7 +464,11 @@ type compaction struct {
// grandparent and lower levels. See setupInuseKeyRanges() for the
// construction. Used by elideTombstone() and elideRangeTombstone() to
// determine if keys affected by a tombstone possibly exist at a lower level.
inuseKeyRanges []manifest.UserKeyRange
inuseKeyRanges []manifest.UserKeyRange
// inuseEntireRange is set if the above inuse key ranges wholly contain the
// compaction's key range. This allows compactions in higher levels to often
// elide key comparisons.
inuseEntireRange bool
elideTombstoneIndex int
// allowedZeroSeqNum is true if seqnums can be zeroed if there are no
@@ -511,6 +517,7 @@ func newCompaction(pc *pickedCompaction, opts *Options) *compaction {
kind: compactionKindDefault,
cmp: pc.cmp,
equal: opts.equal(),
comparer: opts.Comparer,
formatKey: opts.Comparer.FormatKey,
score: pc.score,
inputs: pc.inputs,
@@ -554,6 +561,7 @@ func newDeleteOnlyCompaction(opts *Options, cur *version, inputs []compactionLev
kind: compactionKindDeleteOnly,
cmp: opts.Comparer.Compare,
equal: opts.equal(),
comparer: opts.Comparer,
formatKey: opts.Comparer.FormatKey,
logger: opts.Logger,
version: cur,
@@ -641,6 +649,7 @@ func newFlush(opts *Options, cur *version, baseLevel int, flushing flushableList
kind: compactionKindFlush,
cmp: opts.Comparer.Compare,
equal: opts.equal(),
comparer: opts.Comparer,
formatKey: opts.Comparer.FormatKey,
logger: opts.Logger,
version: cur,
@@ -736,9 +745,19 @@ func (c *compaction) setupInuseKeyRanges() {
if c.outputLevel.level == 0 {
level = 0
}
// calculateInuseKeyRanges will return a series of sorted spans. Overlapping
// or abutting spans have already been merged.
c.inuseKeyRanges = calculateInuseKeyRanges(
c.version, c.cmp, level, numLevels-1, c.smallest.UserKey, c.largest.UserKey,
)
// Check if there's a single in-use span that encompasses the entire key
// range of the compaction. This is an optimization to avoid key comparisons
// against inuseKeyRanges during the compaction when every key within the
// compaction overlaps with an in-use span.
if len(c.inuseKeyRanges) > 0 {
c.inuseEntireRange = c.cmp(c.inuseKeyRanges[0].Start, c.smallest.UserKey) <= 0 &&
c.cmp(c.inuseKeyRanges[0].End, c.largest.UserKey) >= 0
}
}
func calculateInuseKeyRanges(
@@ -935,7 +954,7 @@ func (c *compaction) allowZeroSeqNum() bool {
// key. The keys in multiple invocations to elideTombstone must be supplied in
// order.
func (c *compaction) elideTombstone(key []byte) bool {
if len(c.flushing) != 0 {
if c.inuseEntireRange || len(c.flushing) != 0 {
return false
}
@@ -1010,12 +1029,12 @@ func (c *compaction) newInputIter(
iter := f.newFlushIter(nil, &c.bytesIterated)
if rangeDelIter := f.newRangeDelIter(nil); rangeDelIter != nil {
c.rangeDelIter.Init(c.cmp, rangeDelIter)
iter = newMergingIter(c.logger, c.cmp, nil, iter, &c.rangeDelIter)
iter = newMergingIter(c.logger, &c.stats, c.cmp, nil, iter, &c.rangeDelIter)
}
if rangeKeyIter := f.newRangeKeyIter(nil); rangeKeyIter != nil {
mi := &keyspan.MergingIter{}
mi.Init(c.cmp, rangeKeyCompactionTransform(snapshots, c.elideRangeKey), rangeKeyIter)
c.rangeKeyInterleaving.Init(c.cmp, base.WrapIterWithStats(iter), mi, nil /* hooks */, nil /* lowerBound */, nil /* upperBound */)
c.rangeKeyInterleaving.Init(c.comparer, iter, mi, nil /* hooks */, nil /* lowerBound */, nil /* upperBound */)
iter = &c.rangeKeyInterleaving
}
return iter, nil
@@ -1038,11 +1057,11 @@ func (c *compaction) newInputIter(
c.rangeDelIter.Init(c.cmp, rangeDelIters...)
iters = append(iters, &c.rangeDelIter)
}
var iter base.InternalIteratorWithStats = newMergingIter(c.logger, c.cmp, nil, iters...)
var iter internalIterator = newMergingIter(c.logger, &c.stats, c.cmp, nil, iters...)
if len(rangeKeyIters) > 0 {
mi := &keyspan.MergingIter{}
mi.Init(c.cmp, rangeKeyCompactionTransform(snapshots, c.elideRangeKey), rangeKeyIters...)
c.rangeKeyInterleaving.Init(c.cmp, base.WrapIterWithStats(iter), mi, nil /* hooks */, nil /* lowerBound */, nil /* upperBound */)
c.rangeKeyInterleaving.Init(c.comparer, iter, mi, nil /* hooks */, nil /* lowerBound */, nil /* upperBound */)
iter = &c.rangeKeyInterleaving
}
return iter, nil
@@ -1112,9 +1131,10 @@ func (c *compaction) newInputIter(
// internal iterator interface). The resulting merged rangedel iterator is
// then included with the point levels in a single mergingIter.
newRangeDelIter := func(
f *manifest.FileMetadata, slice manifest.LevelSlice, _ *IterOptions, bytesIterated *uint64,
f manifest.LevelFile, _ *IterOptions, bytesIterated *uint64,
) (keyspan.FragmentIterator, error) {
iter, rangeDelIter, err := newIters(f, nil /* iter options */, &c.bytesIterated)
iter, rangeDelIter, err := newIters(f.FileMetadata, nil, /* iter options */
internalIterOpts{bytesIterated: &c.bytesIterated})
if err == nil {
// TODO(peter): It is mildly wasteful to open the point iterator only to
// immediately close it. One way to solve this would be to add new
@@ -1135,15 +1155,43 @@ func (c *compaction) newInputIter(
rangeDelIter = noCloseIter{rangeDelIter}
// Truncate the range tombstones returned by the iterator to the
// upper bound of the atomic compaction unit. Note that we need do
// this truncation at read time in order to handle sstables
// generated by RocksDB and earlier versions of Pebble which do not
// truncate range tombstones to atomic compaction unit boundaries at
// write time.
// upper bound of the atomic compaction unit of the file. We want to
// truncate the range tombstone to the bounds of the file, but files
// with split user keys pose an obstacle: The file's largest bound
// is inclusive whereas the range tombstone's end is exclusive.
//
// Consider the example:
//
// 000001:[b-f#200] range del [c,k)
// 000002:[f#190-g#inf] range del [c,k)
// 000003:[g#500-i#3]
//
// Files 000001 and 000002 contain the untruncated range tombstones
// [c,k). While the keyspace covered by 000003 was at one point
// deleted by the tombstone [c,k), the tombstone may have already
// been compacted away and the file does not contain an untruncated
// range tombstone. We want to bound 000001's tombstone to the file
// bounds, but it's not possible to encode a range tombstone with an
// end boundary within a user key (eg, between sequence numbers
// f#200 and f#190). Instead, we expand 000001 to its atomic
// compaction unit (000001 and 000002) and truncate the tombstone to
// g#inf.
//
// NB: We must not use the atomic compaction unit of the entire
// compaction, because the [c,k) tombstone contained in the file
// 000001 ≥ g. If 000001, 000002 and 000003 are all included in the
// same compaction, the compaction's atomic compaction unit includes
// 000003. However 000003's keys must not be covered by 000001's
// untruncated range tombstone.
//
// Note that we need do this truncation at read time in order to
// handle sstables generated by RocksDB and earlier versions of
// Pebble which do not truncate range tombstones to atomic
// compaction unit boundaries at write time.
//
// The current Pebble compaction logic DOES truncate tombstones to
// atomic unit boundaries at compaction time too.
atomicUnit, _ := expandToAtomicUnit(c.cmp, slice, true /* disableIsCompacting */)
atomicUnit, _ := expandToAtomicUnit(c.cmp, f.Slice(), true /* disableIsCompacting */)
lowerBound, upperBound := manifest.KeyRange(c.cmp, atomicUnit.Iter())
// Range deletion tombstones are often written to sstables
// untruncated on the end key side. However, they are still only
@@ -1166,20 +1214,54 @@ func (c *compaction) newInputIter(
addItersForLevel := func(level *compactionLevel, l manifest.Level) error {
iters = append(iters, newLevelIter(iterOpts, c.cmp, nil /* split */, newIters,
level.files.Iter(), l, &c.bytesIterated))
// Create a wrapping closure to turn newRangeDelIter into a
// keyspan.TableNewSpanIter, and return a LevelIter that lazily creates
// rangedel iterators. This is safe now that range deletions are truncated
// at file bounds; the merging iterator no longer needs to see all range
// deletes for correctness.
wrapper := func(file *manifest.FileMetadata, iterOptions *keyspan.SpanIterOptions) (keyspan.FragmentIterator, error) {
return newRangeDelIter(file, level.files, nil, &c.bytesIterated)
// TODO(jackson): Use keyspan.LevelIter to avoid loading all the range
// deletions into memory upfront. (See #2015, which reverted this.)
// There will be no user keys that are split between sstables
// within a level in Cockroach 23.1, which unblocks this optimization.
// Add the range deletion iterator for each file as an independent level
// in mergingIter, as opposed to making a levelIter out of those. This
// is safer as levelIter expects all keys coming from underlying
// iterators to be in order. Due to compaction / tombstone writing
// logic in finishOutput(), it is possible for range tombstones to not
// be strictly ordered across all files in one level.
//
// Consider this example from the metamorphic tests (also repeated in
// finishOutput()), consisting of three L3 files with their bounds
// specified in square brackets next to the file name:
//
// ./000240.sst [tmgc#391,MERGE-tmgc#391,MERGE]
// tmgc#391,MERGE [786e627a]
// tmgc-udkatvs#331,RANGEDEL
//
// ./000241.sst [tmgc#384,MERGE-tmgc#384,MERGE]
// tmgc#384,MERGE [666c7070]
// tmgc-tvsalezade#383,RANGEDEL
// tmgc-tvsalezade#331,RANGEDEL
//
// ./000242.sst [tmgc#383,RANGEDEL-tvsalezade#72057594037927935,RANGEDEL]
// tmgc-tvsalezade#383,RANGEDEL
// tmgc#375,SET [72646c78766965616c72776865676e79]
// tmgc-tvsalezade#356,RANGEDEL
//
// Here, the range tombstone in 000240.sst falls "after" one in
// 000241.sst, despite 000240.sst being ordered "before" 000241.sst for
// levelIter's purposes. While each file is still consistent before its
// bounds, it's safer to have all rangedel iterators be visible to
// mergingIter.
iter := level.files.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
rangeDelIter, err := newRangeDelIter(iter.Take(), nil, &c.bytesIterated)
if err != nil {
return errors.Wrapf(err, "pebble: could not open table %s", errors.Safe(f.FileNum))
}
if rangeDelIter != emptyKeyspanIter {
rangeDelIters = append(rangeDelIters, rangeDelIter)
}
}
li := &keyspan.LevelIter{}
li.Init(keyspan.SpanIterOptions{}, c.cmp, wrapper, level.files.Iter(), l, c.logger, manifest.KeyTypePoint)
rangeDelIters = append(rangeDelIters, li)
// Check if this level has any range keys.
hasRangeKeys := false
iter := level.files.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
if f.HasRangeKeys {
hasRangeKeys = true
@@ -1245,13 +1327,13 @@ func (c *compaction) newInputIter(
c.rangeDelIter.Init(c.cmp, rangeDelIters...)
iters = append(iters, &c.rangeDelIter)
}
pointKeyIter := newMergingIter(c.logger, c.cmp, nil, iters...)
pointKeyIter := newMergingIter(c.logger, &c.stats, c.cmp, nil, iters...)
if len(rangeKeyIters) > 0 {
mi := &keyspan.MergingIter{}
mi.Init(c.cmp, rangeKeyCompactionTransform(snapshots, c.elideRangeKey), rangeKeyIters...)
di := &keyspan.DefragmentingIter{}
di.Init(c.cmp, mi, keyspan.DefragmentInternal, keyspan.StaticDefragmentReducer)
c.rangeKeyInterleaving.Init(c.cmp, pointKeyIter, di, nil /* hooks */, nil /* lowerBound */, nil /* upperBound */)
di.Init(c.comparer, mi, keyspan.DefragmentInternal, keyspan.StaticDefragmentReducer)
c.rangeKeyInterleaving.Init(c.comparer, pointKeyIter, di, nil /* hooks */, nil /* lowerBound */, nil /* upperBound */)
return &c.rangeKeyInterleaving, nil
}
@@ -1306,10 +1388,10 @@ func (d *DB) addInProgressCompaction(c *compaction) {
for _, cl := range c.inputs {
iter := cl.files.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
if f.Compacting {
if f.IsCompacting() {
d.opts.Logger.Fatalf("L%d->L%d: %s already being compacted", c.startLevel.level, c.outputLevel.level, f.FileNum)
}
f.Compacting = true
f.SetCompactionState(manifest.CompactionStateCompacting)
if c.startLevel != nil && c.outputLevel != nil && c.startLevel.level == 0 {
if c.outputLevel.level == 0 {
f.IsIntraL0Compacting = true
@@ -1357,18 +1439,33 @@ func (d *DB) addInProgressCompaction(c *compaction) {
}
}
// Removes compaction markers from files in a compaction.
// Removes compaction markers from files in a compaction. The rollback parameter
// indicates whether the compaction state should be rolled back to its original
// state in the case of an unsuccessful compaction.
//
// DB.mu must be held when calling this method. All writes to the manifest
// for this compaction should have completed by this point.
func (d *DB) removeInProgressCompaction(c *compaction) {
// DB.mu must be held when calling this method. All writes to the manifest for
// this compaction should have completed by this point.
func (d *DB) removeInProgressCompaction(c *compaction, rollback bool) {
for _, cl := range c.inputs {
iter := cl.files.Iter()
for f := iter.First(); f != nil; f = iter.Next() {
if !f.Compacting {
if !f.IsCompacting() {
d.opts.Logger.Fatalf("L%d->L%d: %s not being compacted", c.startLevel.level, c.outputLevel.level, f.FileNum)
}
f.Compacting = false
if !rollback {
// On success all compactions other than move-compactions transition the
// file into the Compacted state. Move-compacted files become eligible
// for compaction again and transition back to NotCompacting.
if c.kind != compactionKindMove {
f.SetCompactionState(manifest.CompactionStateCompacted)
} else {
f.SetCompactionState(manifest.CompactionStateNotCompacting)
}
} else {
// Else, on rollback, all input files unconditionally transition back to
// NotCompacting.
f.SetCompactionState(manifest.CompactionStateNotCompacting)
}
f.IsIntraL0Compacting = false
}
}
@@ -1453,7 +1550,7 @@ func (d *DB) passedFlushThreshold() bool {
return size >= minFlushSize
}
func (d *DB) maybeScheduleDelayedFlush(tbl *memTable) {
func (d *DB) maybeScheduleDelayedFlush(tbl *memTable, dur time.Duration) {
var mem *flushableEntry
for _, m := range d.mu.mem.queue {
if m.flushable == tbl {
@@ -1461,12 +1558,17 @@ func (d *DB) maybeScheduleDelayedFlush(tbl *memTable) {
break
}
}
if mem == nil || mem.flushForced || mem.delayedFlushForced {
if mem == nil || mem.flushForced {
return
}
mem.delayedFlushForced = true
deadline := d.timeNow().Add(dur)
if !mem.delayedFlushForcedAt.IsZero() && deadline.After(mem.delayedFlushForcedAt) {
// Already scheduled to flush sooner than within `dur`.
return
}
mem.delayedFlushForcedAt = deadline
go func() {
timer := time.NewTimer(d.opts.Experimental.DeleteRangeFlushDelay)
timer := time.NewTimer(dur)
defer timer.Stop()
select {
@@ -1481,11 +1583,11 @@ func (d *DB) maybeScheduleDelayedFlush(tbl *memTable) {
defer d.mu.Unlock()
// NB: The timer may fire concurrently with a call to Close. If a
// Close call beat us to acquiring d.mu, d.closed is 1, and it's
// too late to flush anything. Otherwise, the Close call will
// block on locking d.mu until we've finished scheduling the flush
// and set `d.mu.compact.flushing` to true. Close will wait for
// the current flush to complete.
// Close call beat us to acquiring d.mu, d.closed holds ErrClosed,
// and it's too late to flush anything. Otherwise, the Close call
// will block on locking d.mu until we've finished scheduling the
// flush and set `d.mu.compact.flushing` to true. Close will wait
// for the current flush to complete.
if d.closed.Load() != nil {
return
}
@@ -1609,7 +1711,7 @@ func (d *DB) flush1() (bytesFlushed uint64, err error) {
bytesFlushed = c.bytesIterated
d.maybeUpdateDeleteCompactionHints(c)
d.removeInProgressCompaction(c)
d.removeInProgressCompaction(c, err != nil)
d.mu.versions.incrementCompactions(c.kind, c.extraLevels)
d.mu.versions.incrementCompactionBytes(-c.bytesWritten)
@@ -1683,7 +1785,8 @@ func (d *DB) maybeScheduleCompactionPicker(
if d.closed.Load() != nil || d.opts.ReadOnly {
return
}
if d.mu.compact.compactingCount >= d.opts.MaxConcurrentCompactions {
maxConcurrentCompactions := d.opts.MaxConcurrentCompactions()
if d.mu.compact.compactingCount >= maxConcurrentCompactions {
if len(d.mu.compact.manual) > 0 {
// Inability to run head blocks later manual compactions.
d.mu.compact.manual[0].retries++
@@ -1712,7 +1815,7 @@ func (d *DB) maybeScheduleCompactionPicker(
// Check for delete-only compactions first, because they're expected to be
// cheap and reduce future compaction work.
if len(d.mu.compact.deletionHints) > 0 &&
d.mu.compact.compactingCount < d.opts.MaxConcurrentCompactions &&
d.mu.compact.compactingCount < maxConcurrentCompactions &&
!d.opts.DisableAutomaticCompactions {
v := d.mu.versions.currentVersion()
snapshots := d.mu.snapshots.toSlice()
@@ -1727,7 +1830,7 @@ func (d *DB) maybeScheduleCompactionPicker(
}
}
for len(d.mu.compact.manual) > 0 && d.mu.compact.compactingCount < d.opts.MaxConcurrentCompactions {
for len(d.mu.compact.manual) > 0 && d.mu.compact.compactingCount < maxConcurrentCompactions {
manual := d.mu.compact.manual[0]
env.inProgressCompactions = d.getInProgressCompactionInfoLocked(nil)
pc, retryLater := d.mu.versions.picker.pickManual(env, manual)
@@ -1748,7 +1851,7 @@ func (d *DB) maybeScheduleCompactionPicker(
}
}
for !d.opts.DisableAutomaticCompactions && d.mu.compact.compactingCount < d.opts.MaxConcurrentCompactions {
for !d.opts.DisableAutomaticCompactions && d.mu.compact.compactingCount < maxConcurrentCompactions {
env.inProgressCompactions = d.getInProgressCompactionInfoLocked(nil)
env.readCompactionEnv = readCompactionEnv{
readCompactions: &d.mu.compact.readCompactions,
@@ -2012,7 +2115,7 @@ func checkDeleteCompactionHints(
overlaps := v.Overlaps(l, cmp, h.start, h.end, true /* exclusiveEnd */)
iter := overlaps.Iter()
for m := iter.First(); m != nil; m = iter.Next() {
if m.Compacting || !h.canDelete(cmp, m, snapshots) || files[m] {
if m.IsCompacting() || !h.canDelete(cmp, m, snapshots) || files[m] {
continue
}
if files == nil {
@@ -2098,7 +2201,7 @@ func (d *DB) compact1(c *compaction, errChannel chan error) (err error) {
}
d.maybeUpdateDeleteCompactionHints(c)
d.removeInProgressCompaction(c)
d.removeInProgressCompaction(c, err != nil)
d.mu.versions.incrementCompactions(c.kind, c.extraLevels)
d.mu.versions.incrementCompactionBytes(-c.bytesWritten)
@@ -2276,10 +2379,10 @@ func (d *DB) runCompaction(
// must be careful, because the byte slice returned by UnsafeKey
// points directly into the Writer's block buffer.
var prevPointKey sstable.PreviousPointKeyOpt
var additionalCPUProcs int
var cpuWorkHandle CPUWorkHandle
defer func() {
if additionalCPUProcs > 0 {
d.opts.Experimental.CPUWorkPermissionGranter.ReturnProcs(additionalCPUProcs)
if cpuWorkHandle != nil {
d.opts.Experimental.CPUWorkPermissionGranter.CPUWorkDone(cpuWorkHandle)
}
}()
@@ -2318,12 +2421,15 @@ func (d *DB) runCompaction(
filenames = append(filenames, filename)
cacheOpts := private.SSTableCacheOpts(d.cacheID, fileNum).(sstable.WriterOption)
internalTableOpt := private.SSTableInternalTableOpt.(sstable.WriterOption)
if d.opts.Experimental.CPUWorkPermissionGranter != nil {
additionalCPUProcs = d.opts.Experimental.CPUWorkPermissionGranter.TryGetProcs(1)
}
const MaxFileWriteAdditionalCPUTime = time.Millisecond * 100
cpuWorkHandle = d.opts.Experimental.CPUWorkPermissionGranter.GetPermission(
MaxFileWriteAdditionalCPUTime,
)
writerOpts.Parallelism =
d.opts.Experimental.MaxWriterConcurrency > 0 &&
(additionalCPUProcs > 0 || d.opts.Experimental.ForceWriterParallelism)
(cpuWorkHandle.Permitted() || d.opts.Experimental.ForceWriterParallelism)
tw = sstable.NewWriter(file, writerOpts, cacheOpts, internalTableOpt, &prevPointKey)
fileMeta.CreationTime = time.Now().Unix()
@@ -2428,10 +2534,8 @@ func (d *DB) runCompaction(
tw = nil
return err
}
if additionalCPUProcs > 0 {
d.opts.Experimental.CPUWorkPermissionGranter.ReturnProcs(additionalCPUProcs)
additionalCPUProcs = 0
}
d.opts.Experimental.CPUWorkPermissionGranter.CPUWorkDone(cpuWorkHandle)
cpuWorkHandle = nil
writerMeta, err := tw.Metadata()
if err != nil {
tw = nil
@@ -2737,12 +2841,19 @@ func validateVersionEdit(
// and adds those to the internal lists of obsolete files. Note that the files
// are not actually deleted by this method. A subsequent call to
// deleteObsoleteFiles must be performed. Must be not be called concurrently
// with compactions and flushes. db.mu must be held when calling this function,
// however as this function is expected to only be called during Open(), no
// other operations should be contending on it just yet.
// with compactions and flushes. db.mu must be held when calling this function.
func (d *DB) scanObsoleteFiles(list []string) {
// Disable automatic compactions temporarily to avoid concurrent compactions /
// flushes from interfering. The original value is restored on completion.
disabledPrev := d.opts.DisableAutomaticCompactions
defer func() {
d.opts.DisableAutomaticCompactions = disabledPrev
}()
d.opts.DisableAutomaticCompactions = true
// Wait for any ongoing compaction to complete before continuing.
if d.mu.compact.compactingCount > 0 || d.mu.compact.flushing {
panic("pebble: cannot scan obsolete files concurrently with compaction/flushing")
d.mu.compact.cond.Wait()
}
liveFileNums := make(map[FileNum]struct{})

View File

@@ -5,6 +5,7 @@
package pebble
import (
"fmt"
"io"
"sort"
"strconv"
@@ -12,6 +13,7 @@ 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"
)
@@ -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
@@ -252,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)
}
@@ -464,7 +475,12 @@ func (i *compactionIter) skipInStripe() {
}
func (i *compactionIter) iterNext() bool {
i.iterKey, i.iterValue = i.iter.Next()
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
}
@@ -491,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
}
@@ -734,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 {

View File

@@ -19,8 +19,6 @@ import (
// heuristic.
const minIntraL0Count = 4
const levelMultiplier = 10
type compactionEnv struct {
earliestUnflushedSeqNum uint64
earliestSnapshotSeqNum uint64
@@ -294,7 +292,9 @@ func (pc *pickedCompaction) setupInputs(
// 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
@@ -504,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 {
@@ -522,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 {
@@ -538,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
}
@@ -576,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
}
@@ -590,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{}
@@ -607,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{} {
@@ -622,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
}
@@ -783,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).
@@ -795,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
@@ -823,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
@@ -836,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
@@ -863,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])
@@ -987,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
@@ -1020,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()
}
@@ -1082,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]),
)
@@ -1223,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
@@ -1322,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)
@@ -1360,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
}
@@ -1662,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 (
@@ -168,18 +169,40 @@ type Writer interface {
RangeKeyDelete(start, end []byte, opts *WriteOptions) error
}
// CPUWorkPermissionGranter is used to request permission to opportunistically
// use additional CPUs to speed up internal background work. Each granted "proc"
// can be used to spin up a CPU bound goroutine, i.e, if scheduled each such
// goroutine can consume one P in the goroutine scheduler. The calls to
// ReturnProcs can be a bit delayed, since Pebble interacts with this interface
// in a coarse manner. So one should assume that the total number of granted
// procs is a non tight upper bound on the CPU that will get consumed.
type CPUWorkPermissionGranter interface {
TryGetProcs(count int) int
ReturnProcs(count int)
// 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
@@ -341,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 {
@@ -523,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,
}
@@ -769,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()
}
@@ -902,10 +935,8 @@ 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,
@@ -920,6 +951,9 @@ func (d *DB) newIterInternal(batch *Batch, s *Snapshot, o *IterOptions) *Iterato
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()
}
@@ -959,6 +993,8 @@ func finishInitializingIter(buf *iterAlloc) *Iterator {
}
if dbi.opts.rangeKeys() {
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
@@ -969,7 +1005,8 @@ func finishInitializingIter(buf *iterAlloc) *Iterator {
// contains any range keys.
useLazyCombinedIteration := dbi.rangeKey == nil &&
dbi.opts.KeyTypes == IterKeyTypePointsAndRanges &&
(dbi.batch == nil || dbi.batch.countRangeKeys == 0)
(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
@@ -993,10 +1030,15 @@ func finishInitializingIter(buf *iterAlloc) *Iterator {
}
dbi.iter = &dbi.lazyCombinedIter
} else {
dbi.lazyCombinedIter.combinedIterState = combinedIterState{
initialized: true,
}
if dbi.rangeKey == nil {
dbi.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
dbi.rangeKey.init(dbi.cmp, dbi.split, &dbi.opts)
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
@@ -1006,8 +1048,8 @@ func finishInitializingIter(buf *iterAlloc) *Iterator {
// 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.cmp, dbi.iter, dbi.rangeKey.rangeKeyIter, dbi.rangeKey,
dbi.opts.LowerBound, dbi.opts.UpperBound)
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 {
@@ -1025,6 +1067,10 @@ func (i *Iterator) constructPointIter(memtables flushableList, buf *iterAlloc) {
// Already have one.
return
}
internalOpts := internalIterOpts{stats: &i.stats.InternalStats}
if i.opts.RangeKeyMasking.Filter != nil {
internalOpts.boundLimitedFilter = &i.rangeKeyMasking
}
// Merging levels and levels from iterAlloc.
mlevels := buf.mlevels[:0]
@@ -1068,7 +1114,7 @@ func (i *Iterator) constructPointIter(memtables flushableList, buf *iterAlloc) {
rangeDelIter: newErrorKeyspanIter(ErrNotIndexed),
})
} else {
i.batch.initInternalIter(&i.opts, &i.batchPointIter, i.batchSeqNum)
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
@@ -1080,7 +1126,7 @@ func (i *Iterator) constructPointIter(memtables flushableList, buf *iterAlloc) {
rangeDelIter = &i.batchRangeDelIter
}
mlevels = append(mlevels, mergingIterLevel{
iter: base.WrapIterWithStats(&i.batchPointIter),
iter: &i.batchPointIter,
rangeDelIter: rangeDelIter,
})
}
@@ -1090,7 +1136,7 @@ func (i *Iterator) constructPointIter(memtables flushableList, buf *iterAlloc) {
for j := len(memtables) - 1; j >= 0; j-- {
mem := memtables[j]
mlevels = append(mlevels, mergingIterLevel{
iter: base.WrapIterWithStats(mem.newIter(&i.opts)),
iter: mem.newIter(&i.opts),
rangeDelIter: mem.newRangeDelIter(&i.opts),
})
}
@@ -1100,11 +1146,10 @@ func (i *Iterator) constructPointIter(memtables flushableList, buf *iterAlloc) {
levelsIndex := len(levels)
mlevels = mlevels[:numMergingLevels]
levels = levels[:numLevelIters]
addLevelIterForFiles := func(files manifest.LevelIterator, level manifest.Level) {
li := &levels[levelsIndex]
li.init(i.opts, i.cmp, i.split, i.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.initBoundaryContext(&mlevels[mlevelsIndex].levelIterBoundaryContext)
li.initCombinedIterState(&i.lazyCombinedIter.combinedIterState)
@@ -1127,11 +1172,13 @@ func (i *Iterator) constructPointIter(memtables flushableList, buf *iterAlloc) {
}
addLevelIterForFiles(current.Levels[level].Iter(), manifest.Level(level))
}
buf.merging.init(&i.opts, i.cmp, i.split, mlevels...)
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
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
@@ -1190,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 {
@@ -1279,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
}
@@ -1466,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()
}
@@ -1539,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()
@@ -1831,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()
@@ -1924,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

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, flags base.SeekGEFlags) (*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, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
return nil, nil
) (*base.InternalKey, base.LazyValue) {
return nil, base.LazyValue{}
}
func (c *errorIter) SeekLT(key []byte, flags base.SeekLTFlags) (*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

View File

@@ -5,6 +5,9 @@
package pebble
import (
"fmt"
"sort"
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/keyspan"
@@ -12,12 +15,59 @@ import (
"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.
//
@@ -27,8 +77,8 @@ import (
func NewExternalIter(
o *Options,
iterOpts *IterOptions,
files []sstable.ReadableFile,
extraReaderOpts ...sstable.ReaderOption,
files [][]sstable.ReadableFile,
extraOpts ...ExternalIterOption,
) (it *Iterator, err error) {
if iterOpts != nil {
if err := validateExternalIterOpts(iterOpts); err != nil {
@@ -36,17 +86,32 @@ func NewExternalIter(
}
}
var readers []*sstable.Reader
var readers [][]*sstable.Reader
// Ensure we close all the opened readers if we error out.
defer func() {
if err != nil {
for i := range readers {
_ = readers[i].Close()
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
}
@@ -55,10 +120,8 @@ 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,
@@ -67,7 +130,7 @@ func NewExternalIter(
// 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, bytesIterated *uint64) (internalIterator, keyspan.FragmentIterator, error) {
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
@@ -82,6 +145,9 @@ func NewExternalIter(
dbi.opts = *iterOpts
dbi.saveBounds(iterOpts.LowerBound, iterOpts.UpperBound)
}
for i := range extraOpts {
extraOpts[i].iterApply(dbi)
}
finishInitializingExternal(dbi)
return dbi, nil
}
@@ -102,71 +168,147 @@ func validateExternalIterOpts(iterOpts *IterOptions) error {
return nil
}
func finishInitializingExternal(it *Iterator) {
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.
mlevels := it.alloc.mlevels[:0]
if !it.opts.pointKeys() {
it.pointIter = emptyIter
} else if it.pointIter == nil {
if len(it.externalReaders) > cap(mlevels) {
mlevels = make([]mergingIterLevel, 0, len(it.externalReaders))
}
for _, r := range it.externalReaders {
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 {
var (
rangeDelIter keyspan.FragmentIterator
pointIter internalIterator
err error
)
pointIter, err = r.NewIter(it.opts.LowerBound, it.opts.UpperBound)
if err == nil {
rangeDelIter, err = r.NewRawRangeDelIter()
}
pointIter, err = r.NewIterWithBlockPropertyFilters(
it.opts.LowerBound,
it.opts.UpperBound,
nil, /* BlockPropertiesFilterer */
false, /* useFilterBlock */
&it.stats.InternalStats,
)
if err != nil {
pointIter = &errorIter{err: err}
rangeDelIter = &errorKeyspanIter{err: err}
return nil, err
}
rangeDelIter, err = r.NewRawRangeDelIter()
if err != nil {
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,
})
}
it.alloc.merging.init(&it.opts, it.cmp, it.split, mlevels...)
it.alloc.merging.snapshot = base.InternalKeySeqNumMax
it.alloc.merging.elideRangeTombstones = true
it.pointIter = &it.alloc.merging
if len(combinedIters) == 1 {
mlevels = append(mlevels, mergingIterLevel{
iter: combinedIters[0],
})
} else if len(combinedIters) > 1 {
sli := &simpleLevelIter{
cmp: it.cmp,
iters: combinedIters,
}
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 {
it.rangeKey = iterRangeKeyStateAllocPool.Get().(*iteratorRangeKeyState)
it.rangeKey.init(it.cmp, it.split, &it.opts)
it.rangeKey.rangeKeyIter = it.rangeKey.iterConfig.Init(
it.cmp,
base.InternalKeySeqNumMax,
)
for _, r := range it.externalReaders {
if rki, err := r.NewRawRangeKeyIter(); err != nil {
it.rangeKey.iterConfig.AddLevel(&errorKeyspanIter{err: err})
} else if rki != nil {
it.rangeKey.iterConfig.AddLevel(rki)
// 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])
}
}
}
it.rangeKey.iiter.Init(it.cmp, it.iter, it.rangeKey.rangeKeyIter, it.rangeKey,
it.opts.LowerBound, it.opts.UpperBound)
it.iter = &it.rangeKey.iiter
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
}
}
}
func openExternalTables(
o *Options,
files []sstable.ReadableFile,
seqNumOffset int,
readerOpts sstable.ReaderOptions,
extraReaderOpts ...sstable.ReaderOption,
) (readers []*sstable.Reader, err error) {
@@ -178,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

@@ -7,6 +7,7 @@ package pebble
import (
"fmt"
"sync/atomic"
"time"
"github.com/cockroachdb/pebble/internal/keyspan"
)
@@ -37,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,14 +82,17 @@ 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
@@ -92,11 +100,28 @@ const (
// 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 = FormatMinTableFormatPebblev1
FormatNewest FormatMajorVersion = FormatPrePebblev1MarkedCompacted
)
// MaxTableFormat returns the maximum sstable.TableFormat that can be used at
@@ -106,9 +131,11 @@ 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, FormatMinTableFormatPebblev1:
case FormatRangeKeys, FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
FormatPrePebblev1MarkedCompacted:
return sstable.TableFormatPebblev2
default:
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
@@ -121,9 +148,11 @@ func (v FormatMajorVersion) MinTableFormat() sstable.TableFormat {
switch v {
case FormatDefault, FormatMostCompatible, formatVersionedManifestMarker,
FormatVersioned, FormatSetWithDelete, FormatBlockPropertyCollector,
FormatSplitUserKeysMarked, FormatMarkedCompacted, FormatRangeKeys:
FormatSplitUserKeysMarked, FormatSplitUserKeysMarkedCompacted,
FormatRangeKeys:
return sstable.TableFormatLevelDB
case FormatMinTableFormatPebblev1:
case FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
FormatPrePebblev1MarkedCompacted:
return sstable.TableFormatPebblev1
default:
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
@@ -209,19 +238,19 @@ 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)
@@ -229,6 +258,23 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
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`
@@ -339,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 {
@@ -362,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() {
@@ -407,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
@@ -418,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

@@ -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, flags base.SeekGEFlags) (*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, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*base.InternalKey, base.LazyValue) {
panic("pebble: SeekPrefixGE unimplemented")
}
func (g *getIter) SeekLT(key []byte, flags base.SeekLTFlags) (*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()
}
@@ -84,7 +84,7 @@ func (g *getIter) Next() (*InternalKey, []byte) {
if g.rangeDelIter != nil {
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
}
@@ -98,10 +98,10 @@ func (g *getIter) Next() (*InternalKey, []byte) {
// 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,7 +113,7 @@ 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{}
}
}
@@ -121,11 +121,16 @@ func (g *getIter) Next() (*InternalKey, []byte) {
if g.batch != nil {
if g.batch.index == nil {
g.err = ErrNotIndexed
g.iterKey, g.iterValue = nil, nil
return nil, nil
g.iterKey, g.iterValue = nil, base.LazyValue{}
return nil, base.LazyValue{}
}
g.iter = g.batch.newInternalIter(nil)
g.rangeDelIter = g.batch.newRangeDelIter(nil, g.batch.nextSeqNum())
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
continue
@@ -134,7 +139,7 @@ func (g *getIter) Next() (*InternalKey, []byte) {
// If we have a tombstone from a previous level it is guaranteed to delete
// keys in lower levels.
if g.tombstone != nil && g.tombstone.VisibleAt(g.snapshot) {
return nil, nil
return nil, base.LazyValue{}
}
// Create iterators from memtables from newest to oldest.
@@ -154,7 +159,7 @@ 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, base.SeekGEFlagsNone)
@@ -164,7 +169,7 @@ func (g *getIter) Next() (*InternalKey, []byte) {
}
if g.level >= numLevels {
return nil, nil
return nil, base.LazyValue{}
}
if g.version.Levels[g.level].Empty() {
g.level++
@@ -173,7 +178,7 @@ 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
@@ -181,18 +186,10 @@ func (g *getIter) Next() (*InternalKey, []byte) {
}
}
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

@@ -511,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
}

View File

@@ -33,8 +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, flags base.SeekGEFlags) (*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, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*base.InternalKey, base.LazyValue) {
panic("pebble: SeekPrefixGE unimplemented")
}
func (it *flushIterator) SeekLT(key []byte, flags base.SeekLTFlags) (*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, flags base.SeekLTFlags) (*base.Inter
// 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, flags base.SeekGEFlags) (*base.InternalKey, []byte) {
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, flags base.SeekGEFlags) (*base.InternalKe
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
@@ -120,7 +120,7 @@ func (it *Iterator) SeekGE(key []byte, flags base.SeekGEFlags) (*base.InternalKe
// internal/base.InternalIterator interface.
func (it *Iterator) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*base.InternalKey, base.LazyValue) {
return it.SeekGE(key, flags)
}
@@ -128,85 +128,85 @@ func (it *Iterator) SeekPrefixGE(
// 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, flags base.SeekLTFlags) (*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,17 +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.
SeekGE(key []byte, flags SeekGEFlags) (*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.
//
@@ -104,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
@@ -116,52 +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.
SeekPrefixGE(prefix, key []byte, flags SeekGEFlags) (*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, flags SeekLTFlags) (*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
@@ -190,6 +193,7 @@ type SeekGEFlags uint8
const (
seekGEFlagTrySeekUsingNext uint8 = iota
seekGEFlagRelativeSeek
seekGEFlagBatchJustRefreshed
)
// SeekGEFlagsNone is the default value of SeekGEFlags, with all flags disabled.
@@ -234,6 +238,13 @@ func (s SeekGEFlags) TrySeekUsingNext() bool { return (s & (1 << seekGEFlagTrySe
// 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.
@@ -259,6 +270,19 @@ 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
@@ -295,21 +319,14 @@ func (s SeekLTFlags) DisableRelativeSeek() SeekLTFlags {
return s &^ (1 << seekLTFlagRelativeSeek)
}
// InternalIteratorWithStats extends InternalIterator to expose stats.
type InternalIteratorWithStats interface {
InternalIterator
Stats() InternalIteratorStats
ResetStats()
}
// InternalIteratorStats contains miscellaneous stats produced by
// InternalIterators that are part of the InternalIterator tree. Not every
// field is relevant for an InternalIterator implementation. The field values
// 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
@@ -341,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

@@ -21,26 +21,30 @@ const bufferReuseMaxCapacity = 10 << 10 // 10 KB
type DefragmentMethod interface {
// ShouldDefragment takes two abutting spans and returns whether the two
// spans should be combined into a single, defragmented Span.
ShouldDefragment(cmp base.Compare, left, right *Span) bool
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(cmp base.Compare, left, right *Span) bool
type DefragmentMethodFunc func(equal base.Equal, left, right *Span) bool
// ShouldDefragment calls f(cmp, left, right).
func (f DefragmentMethodFunc) ShouldDefragment(cmp base.Compare, left, right *Span) bool {
return f(cmp, left, right)
// 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 = DefragmentMethodFunc(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
}
@@ -48,7 +52,7 @@ var DefragmentInternal DefragmentMethod = DefragmentMethodFunc(func(cmp base.Com
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) {
@@ -114,7 +118,8 @@ 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
iterPos iterPos
@@ -149,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,
method: equal,
reduce: reducer,
comparer: comparer,
equal: comparer.Equal,
iter: iter,
method: equal,
reduce: reducer,
}
}
@@ -176,11 +182,14 @@ func (i *DefragmentingIter) SeekGE(key []byte) *Span {
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.iter.Prev()
if i.iterSpan != nil && 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
@@ -206,6 +215,9 @@ func (i *DefragmentingIter) SeekLT(key []byte) *Span {
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()
@@ -355,7 +367,7 @@ func (i *DefragmentingIter) Prev() *Span {
// 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 (!left.Empty() && !right.Empty()) && i.method.ShouldDefragment(i.cmp, i.iterSpan, &i.curr)
return (!left.Empty() && !right.Empty()) && i.method.ShouldDefragment(i.equal, i.iterSpan, &i.curr)
}
// defragmentForward defragments spans in the forward direction, starting from
@@ -374,7 +386,7 @@ func (i *DefragmentingIter) defragmentForward() *Span {
i.iterPos = iterPosNext
i.iterSpan = i.iter.Next()
for i.iterSpan != nil {
if i.cmp(i.curr.End, i.iterSpan.Start) != 0 {
if !i.equal(i.curr.End, i.iterSpan.Start) {
// Not a continuation.
break
}
@@ -407,7 +419,7 @@ func (i *DefragmentingIter) defragmentBackward() *Span {
i.iterPos = iterPosPrev
i.iterSpan = i.iter.Prev()
for i.iterSpan != nil {
if i.cmp(i.curr.Start, i.iterSpan.End) != 0 {
if !i.equal(i.curr.Start, i.iterSpan.End) {
// Not a continuation.
break
}
@@ -438,8 +450,9 @@ func (i *DefragmentingIter) saveCurrent() {
return
}
i.curr = Span{
Start: i.saveBytes(i.iterSpan.Start),
End: i.saveBytes(i.iterSpan.End),
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{

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); {

View File

@@ -35,14 +35,6 @@ type SpanMask interface {
SkipPoint(userKey []byte) bool
}
type maskState int8
const (
maskStale maskState = iota
maskCleared
maskCurrent
)
// InterleavingIter combines an iterator over point keys with an iterator over
// key spans.
//
@@ -101,7 +93,8 @@ const (
// All spans containing keys are exposed during iteration.
type InterleavingIter struct {
cmp base.Compare
pointIter base.InternalIteratorWithStats
comparer *base.Comparer
pointIter base.InternalIterator
keyspanIter FragmentIterator
mask SpanMask
@@ -111,9 +104,13 @@ type InterleavingIter struct {
// to truncate a span. The byte slices backing a SeekGE/SeekPrefixGE search
// keys can come directly from the end user, so they're copied into keyBuf
// to ensure key stability.
keyBuf []byte
pointKey *base.InternalKey
pointVal []byte
keyBuf []byte
// nextPrefixBuf is used during SeekPrefixGE calls to store the truncated
// upper bound of the returned spans. SeekPrefixGE truncates the returned
// spans to an upper bound of the seeked prefix's immediate successor.
nextPrefixBuf []byte
pointKey *base.InternalKey
pointVal base.LazyValue
// span holds the span at the keyspanIter's current position. If the span is
// wholly contained within the iterator bounds, this span is directly
// returned to the iterator consumer through Span(). If either bound needed
@@ -158,32 +155,15 @@ type InterleavingIter struct {
// span's start bound marker to the search key. It's returned to false on
// the next repositioning of the keyspan iterator.
spanMarkerTruncated bool
// maskState holds a tri-state enum describing the state of the state
// machine managing invocations to i.mask.SpanChanged. When i.span is
// updated, `maskState` is set to the starting state `maskStale` to reflect
// that the mask has not been informed of the new span. From `maskStale`,
// the state transitions to `maskCleared` if the next returned key does not
// fall within i.span's bounds. The state transitions to `maskCurrent` if
// the next returned key does fall within i.span's bounds.
//
// return key outside [s,e)
// keyspan.Iter.{Next,Prev}
// _________________________________
// \/ |
// _____________ |
// | maskStale | |
// |_____________| |
// return key / \ return key |
// outside [s,e) / \ within [s,e) |
// \/ \/ |
// ________________________ __________________________ |
// | SpanChanged(nil) | | SpanChanged(i.span) | __|
// | maskState = maskCleared| | maskState = maskCurrent |
// |________________________| |__________________________|
// |__________________________^
// return key within [s,e)
//
maskState maskState
// maskSpanChangedCalled records whether or not the last call to
// SpanMask.SpanChanged provided the current span (i.span) or not.
maskSpanChangedCalled bool
// prefix records whether the iteator is in prefix mode. During prefix mode,
// Pebble will truncate spans to the next prefix. If the iterator
// subsequently leaves prefix mode, the existing span cached in i.span must
// be invalidated because its bounds do not reflect the original span's true
// bounds.
prefix bool
// dir indicates the direction of iteration: forward (+1) or backward (-1)
dir int8
}
@@ -197,14 +177,15 @@ var _ base.InternalIterator = &InterleavingIter{}
// The point iterator must already have the provided bounds. Init does not
// propagate the bounds down the iterator stack.
func (i *InterleavingIter) Init(
cmp base.Compare,
pointIter base.InternalIteratorWithStats,
comparer *base.Comparer,
pointIter base.InternalIterator,
keyspanIter FragmentIterator,
mask SpanMask,
lowerBound, upperBound []byte,
) {
*i = InterleavingIter{
cmp: cmp,
cmp: comparer.Compare,
comparer: comparer,
pointIter: pointIter,
keyspanIter: keyspanIter,
mask: mask,
@@ -224,16 +205,18 @@ func (i *InterleavingIter) Init(
// It allows for seeding the iterator with the current position of the point
// iterator.
func (i *InterleavingIter) InitSeekGE(
key []byte, pointKey *base.InternalKey, pointValue []byte,
) (*base.InternalKey, []byte) {
prefix, key []byte, pointKey *base.InternalKey, pointValue base.LazyValue,
) (*base.InternalKey, base.LazyValue) {
i.dir = +1
i.clearMask()
i.prefix = prefix != nil
i.pointKey, i.pointVal = pointKey, pointValue
i.pointKeyInterleaved = false
// NB: This keyspanSeekGE call will truncate the span to the seek key if
// necessary. This truncation is important for cases where a switch to
// combined iteration is made during a user-initiated SeekGE.
i.keyspanSeekGE(key)
return i.interleaveForward(key)
i.keyspanSeekGE(key, prefix)
return i.interleaveForward(key, prefix)
}
// InitSeekLT may be called after Init but before any positioning method.
@@ -247,9 +230,10 @@ func (i *InterleavingIter) InitSeekGE(
// It allows for seeding the iterator with the current position of the point
// iterator.
func (i *InterleavingIter) InitSeekLT(
key []byte, pointKey *base.InternalKey, pointValue []byte,
) (*base.InternalKey, []byte) {
key []byte, pointKey *base.InternalKey, pointValue base.LazyValue,
) (*base.InternalKey, base.LazyValue) {
i.dir = -1
i.clearMask()
i.pointKey, i.pointVal = pointKey, pointValue
i.pointKeyInterleaved = false
i.keyspanSeekLT(key)
@@ -265,14 +249,30 @@ func (i *InterleavingIter) InitSeekLT(
// the span returned by Span.
//
// NB: In accordance with the base.InternalIterator contract:
// i.lower ≤ key
func (i *InterleavingIter) SeekGE(key []byte, flags base.SeekGEFlags) (*base.InternalKey, []byte) {
i.maybeUpdateMask(false /* covered */)
//
// i.lower ≤ key
func (i *InterleavingIter) SeekGE(
key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, base.LazyValue) {
i.clearMask()
i.disablePrefixMode()
i.pointKey, i.pointVal = i.pointIter.SeekGE(key, flags)
i.pointKeyInterleaved = false
i.keyspanSeekGE(key)
// We need to seek the keyspan iterator too. If the keyspan iterator was
// already positioned at a span, we might be able to avoid the seek if the
// seek key falls within the existing span's bounds.
if i.span != nil && i.cmp(key, i.span.End) < 0 && i.cmp(key, i.span.Start) >= 0 {
// We're seeking within the existing span's bounds. We still might need
// truncate the span to the iterator's bounds.
i.checkForwardBound(nil /* prefix */)
i.savedKeyspan()
} else {
i.keyspanSeekGE(key, nil /* prefix */)
}
i.dir = +1
return i.interleaveForward(key)
return i.interleaveForward(key, nil /* prefix */)
}
// SeekPrefixGE implements (base.InternalIterator).SeekPrefixGE.
@@ -284,43 +284,113 @@ func (i *InterleavingIter) SeekGE(key []byte, flags base.SeekGEFlags) (*base.Int
// to the span returned by Span.
//
// NB: In accordance with the base.InternalIterator contract:
// i.lower ≤ key
//
// i.lower ≤ key
func (i *InterleavingIter) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
i.maybeUpdateMask(false /* covered */)
) (*base.InternalKey, base.LazyValue) {
i.clearMask()
i.pointKey, i.pointVal = i.pointIter.SeekPrefixGE(prefix, key, flags)
i.pointKeyInterleaved = false
i.keyspanSeekGE(key)
i.prefix = true
// We need to seek the keyspan iterator too. If the keyspan iterator was
// already positioned at a span, we might be able to avoid the seek if the
// entire seek prefix key falls within the existing span's bounds.
//
// During a SeekPrefixGE, Pebble defragments range keys within the bounds of
// the prefix. For example, a SeekPrefixGE('c', 'c@8') must defragment the
// any overlapping range keys within the bounds of [c,c\00).
//
// If range keys are fragmented within a prefix (eg, because a version
// within a prefix was chosen as an sstable boundary), then it's possible
// the seek key falls into the current i.span, but the current i.span does
// not wholly cover the seek prefix.
//
// For example, a SeekPrefixGE('d@5') may only defragment a range key to
// the bounds of [c@2,e). A subsequent SeekPrefixGE('c@0') must re-seek the
// keyspan iterator, because although 'c@0' is contained within [c@2,e), the
// full span of the prefix is not.
//
// Similarly, a SeekPrefixGE('a@3') may only defragment a range key to the
// bounds [a,c@8). A subsequent SeekPrefixGE('c@10') must re-seek the
// keyspan iterator, because although 'c@10' is contained within [a,c@8),
// the full span of the prefix is not.
seekKeyspanIter := true
if i.span != nil && i.cmp(prefix, i.span.Start) >= 0 {
if ei := i.comparer.Split(i.span.End); i.cmp(prefix, i.span.End[:ei]) < 0 {
// We're seeking within the existing span's bounds. We still might need
// truncate the span to the iterator's bounds.
i.checkForwardBound(prefix)
i.savedKeyspan()
seekKeyspanIter = false
}
}
if seekKeyspanIter {
i.keyspanSeekGE(key, prefix)
}
i.dir = +1
return i.interleaveForward(key)
return i.interleaveForward(key, prefix)
}
// SeekLT implements (base.InternalIterator).SeekLT.
func (i *InterleavingIter) SeekLT(key []byte, flags base.SeekLTFlags) (*base.InternalKey, []byte) {
i.maybeUpdateMask(false /* covered */)
func (i *InterleavingIter) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*base.InternalKey, base.LazyValue) {
i.clearMask()
i.disablePrefixMode()
i.pointKey, i.pointVal = i.pointIter.SeekLT(key, flags)
i.pointKeyInterleaved = false
i.keyspanSeekLT(key)
// We need to seek the keyspan iterator too. If the keyspan iterator was
// already positioned at a span, we might be able to avoid the seek if the
// seek key falls within the existing span's bounds.
if i.span != nil && i.cmp(key, i.span.Start) > 0 && i.cmp(key, i.span.End) < 0 {
// We're seeking within the existing span's bounds. We still might need
// truncate the span to the iterator's bounds.
i.checkBackwardBound()
// The span's start key is still not guaranteed to be less than key,
// because of the bounds enforcement. Consider the following example:
//
// Bounds are set to [d,e). The user performs a SeekLT(d). The
// FragmentIterator.SeekLT lands on a span [b,f). This span has a start
// key less than d, as expected. Above, checkBackwardBound truncates the
// span to match the iterator's current bounds, modifying the span to
// [d,e), which does not overlap the search space of [-∞, d).
//
// This problem is a consequence of the SeekLT's exclusive search key
// and the fact that we don't perform bounds truncation at every leaf
// iterator.
if i.span != nil && i.truncated && i.cmp(i.truncatedSpan.Start, key) >= 0 {
i.span = nil
}
i.savedKeyspan()
} else {
i.keyspanSeekLT(key)
}
i.dir = -1
return i.interleaveBackward()
}
// First implements (base.InternalIterator).First.
func (i *InterleavingIter) First() (*base.InternalKey, []byte) {
i.maybeUpdateMask(false /* covered */)
func (i *InterleavingIter) First() (*base.InternalKey, base.LazyValue) {
i.clearMask()
i.disablePrefixMode()
i.pointKey, i.pointVal = i.pointIter.First()
i.pointKeyInterleaved = false
i.span = i.keyspanIter.First()
i.checkForwardBound()
i.checkForwardBound(nil /* prefix */)
i.savedKeyspan()
i.dir = +1
return i.interleaveForward(i.lower)
return i.interleaveForward(i.lower, nil /* prefix */)
}
// Last implements (base.InternalIterator).Last.
func (i *InterleavingIter) Last() (*base.InternalKey, []byte) {
i.maybeUpdateMask(false /* covered */)
func (i *InterleavingIter) Last() (*base.InternalKey, base.LazyValue) {
i.clearMask()
i.disablePrefixMode()
i.pointKey, i.pointVal = i.pointIter.Last()
i.pointKeyInterleaved = false
i.span = i.keyspanIter.Last()
@@ -331,11 +401,18 @@ func (i *InterleavingIter) Last() (*base.InternalKey, []byte) {
}
// Next implements (base.InternalIterator).Next.
func (i *InterleavingIter) Next() (*base.InternalKey, []byte) {
func (i *InterleavingIter) Next() (*base.InternalKey, base.LazyValue) {
if i.dir == -1 {
// Switching directions.
i.dir = +1
if i.mask != nil {
// Clear the mask while we reposition the point iterator. While
// switching directions, we may move the point iterator outside of
// i.span's bounds.
i.clearMask()
}
// The existing point key (denoted below with *) is either the last
// key we returned (the current iterator position):
// points: x (y*) z
@@ -358,7 +435,7 @@ func (i *InterleavingIter) Next() (*base.InternalKey, []byte) {
// There was no span in the reverse direction, but there may be
// a span in the forward direction.
i.span = i.keyspanIter.Next()
i.checkForwardBound()
i.checkForwardBound(nil /* prefix */)
i.savedKeyspan()
} else {
// Regardless of the current iterator state, we mark any existing
@@ -396,18 +473,25 @@ func (i *InterleavingIter) Next() (*base.InternalKey, []byte) {
if i.keyspanInterleaved && i.pointKey != nil && i.span != nil &&
i.cmp(i.pointKey.UserKey, i.span.End) >= 0 {
i.span = i.keyspanIter.Next()
i.checkForwardBound()
i.checkForwardBound(nil /* prefix */)
i.savedKeyspan()
}
return i.interleaveForward(i.lower)
return i.interleaveForward(i.lower, nil /* prefix */)
}
// Prev implements (base.InternalIterator).Prev.
func (i *InterleavingIter) Prev() (*base.InternalKey, []byte) {
func (i *InterleavingIter) Prev() (*base.InternalKey, base.LazyValue) {
if i.dir == +1 {
// Switching directions.
i.dir = -1
if i.mask != nil {
// Clear the mask while we reposition the point iterator. While
// switching directions, we may move the point iterator outside of
// i.span's bounds.
i.clearMask()
}
if i.keyspanInterleaved {
// The current span's start key has already been interleaved in the
// forward direction. The start key may have been interleaved a
@@ -490,7 +574,9 @@ func (i *InterleavingIter) Prev() (*base.InternalKey, []byte) {
return i.interleaveBackward()
}
func (i *InterleavingIter) interleaveForward(lowerBound []byte) (*base.InternalKey, []byte) {
func (i *InterleavingIter) interleaveForward(
lowerBound []byte, prefix []byte,
) (*base.InternalKey, base.LazyValue) {
// This loop determines whether a point key or a span marker key should be
// interleaved on each iteration. If masking is disabled and the span is
// nonempty, this loop executes for exactly one iteration. If masking is
@@ -536,7 +622,7 @@ func (i *InterleavingIter) interleaveForward(lowerBound []byte) (*base.InternalK
// advancing past the current point key.
if i.keyspanInterleaved {
i.span = i.keyspanIter.Next()
i.checkForwardBound()
i.checkForwardBound(prefix)
i.savedKeyspan()
if i.span == nil {
return i.yieldNil()
@@ -558,7 +644,7 @@ func (i *InterleavingIter) interleaveForward(lowerBound []byte) (*base.InternalK
// keyspanInterleaved would likely trip up the invariant check
// above.
i.span = i.keyspanIter.Next()
i.checkForwardBound()
i.checkForwardBound(prefix)
i.savedKeyspan()
} else {
i.keyspanInterleaved = true
@@ -600,7 +686,7 @@ func (i *InterleavingIter) interleaveForward(lowerBound []byte) (*base.InternalK
// reestablish it before the next iteration.
if i.pointKey != nil && i.cmp(i.pointKey.UserKey, i.span.End) >= 0 {
i.span = i.keyspanIter.Next()
i.checkForwardBound()
i.checkForwardBound(prefix)
i.savedKeyspan()
}
continue
@@ -614,7 +700,7 @@ func (i *InterleavingIter) interleaveForward(lowerBound []byte) (*base.InternalK
}
}
func (i *InterleavingIter) interleaveBackward() (*base.InternalKey, []byte) {
func (i *InterleavingIter) interleaveBackward() (*base.InternalKey, base.LazyValue) {
// This loop determines whether a point key or a span's start key should be
// interleaved on each iteration. If masking is disabled and the span is
// nonempty, this loop executes for exactly one iteration. If masking is
@@ -697,7 +783,7 @@ func (i *InterleavingIter) interleaveBackward() (*base.InternalKey, []byte) {
// keyspanSeekGE seeks the keyspan iterator to the first span covering k ≥ key.
// Note that this differs from the FragmentIterator.SeekGE semantics, which
// seek to the first span with a start key ≥ key.
func (i *InterleavingIter) keyspanSeekGE(key []byte) {
func (i *InterleavingIter) keyspanSeekGE(key []byte, prefix []byte) {
// Seek using SeekLT to look for a span that starts before key, with an end
// boundary extending beyond key.
i.span = i.keyspanIter.SeekLT(key)
@@ -706,7 +792,7 @@ func (i *InterleavingIter) keyspanSeekGE(key []byte) {
// found ends before key. Next to the first key with a start ≥ key.
i.span = i.keyspanIter.Next()
}
i.checkForwardBound()
i.checkForwardBound(prefix)
i.savedKeyspan()
}
@@ -733,7 +819,7 @@ func (i *InterleavingIter) keyspanSeekLT(key []byte) {
i.savedKeyspan()
}
func (i *InterleavingIter) checkForwardBound() {
func (i *InterleavingIter) checkForwardBound(prefix []byte) {
i.truncated = false
i.truncatedSpan = Span{}
if i.span == nil {
@@ -765,7 +851,23 @@ func (i *InterleavingIter) checkForwardBound() {
}
i.truncatedSpan.End = i.upper
}
if i.truncated && i.cmp(i.truncatedSpan.Start, i.truncatedSpan.End) == 0 {
// If this is a part of a SeekPrefixGE call, we may also need to truncate to
// the prefix's bounds.
if prefix != nil {
if !i.truncated {
i.truncated = true
i.truncatedSpan = *i.span
}
if i.cmp(prefix, i.truncatedSpan.Start) > 0 {
i.truncatedSpan.Start = prefix
}
i.nextPrefixBuf = i.comparer.ImmediateSuccessor(i.nextPrefixBuf[:0], prefix)
if i.truncated && i.cmp(i.nextPrefixBuf, i.truncatedSpan.End) < 0 {
i.truncatedSpan.End = i.nextPrefixBuf
}
}
if i.truncated && i.comparer.Equal(i.truncatedSpan.Start, i.truncatedSpan.End) {
i.span = nil
}
}
@@ -802,25 +904,27 @@ func (i *InterleavingIter) checkBackwardBound() {
}
i.truncatedSpan.End = i.upper
}
if i.truncated && i.cmp(i.truncatedSpan.Start, i.truncatedSpan.End) == 0 {
if i.truncated && i.comparer.Equal(i.truncatedSpan.Start, i.truncatedSpan.End) {
i.span = nil
}
}
func (i *InterleavingIter) yieldNil() (*base.InternalKey, []byte) {
func (i *InterleavingIter) yieldNil() (*base.InternalKey, base.LazyValue) {
i.spanCoversKey = false
i.maybeUpdateMask(false /* covered */)
return i.verify(nil, nil)
i.clearMask()
return i.verify(nil, base.LazyValue{})
}
func (i *InterleavingIter) yieldPointKey(covered bool) (*base.InternalKey, []byte) {
func (i *InterleavingIter) yieldPointKey(covered bool) (*base.InternalKey, base.LazyValue) {
i.pointKeyInterleaved = true
i.spanCoversKey = covered
i.maybeUpdateMask(covered)
return i.verify(i.pointKey, i.pointVal)
}
func (i *InterleavingIter) yieldSyntheticSpanMarker(lowerBound []byte) (*base.InternalKey, []byte) {
func (i *InterleavingIter) yieldSyntheticSpanMarker(
lowerBound []byte,
) (*base.InternalKey, base.LazyValue) {
i.spanMarker.UserKey = i.startKey()
i.spanMarker.Trailer = base.MakeTrailer(base.InternalKeySeqNumMax, i.span.Keys[0].Kind())
i.keyspanInterleaved = true
@@ -836,7 +940,7 @@ func (i *InterleavingIter) yieldSyntheticSpanMarker(lowerBound []byte) (*base.In
// bound for truncating a span. The span a-z will be truncated to [k,
// z). If i.upper == k, we'd mistakenly try to return a span [k, k), an
// invariant violation.
if i.cmp(lowerBound, i.upper) == 0 {
if i.comparer.Equal(lowerBound, i.upper) {
return i.yieldNil()
}
@@ -856,10 +960,21 @@ func (i *InterleavingIter) yieldSyntheticSpanMarker(lowerBound []byte) (*base.In
i.spanMarkerTruncated = true
}
i.maybeUpdateMask(true /* covered */)
return i.verify(&i.spanMarker, nil)
return i.verify(&i.spanMarker, base.LazyValue{})
}
func (i *InterleavingIter) verify(k *base.InternalKey, v []byte) (*base.InternalKey, []byte) {
func (i *InterleavingIter) disablePrefixMode() {
if i.prefix {
i.prefix = false
// Clear the existing span. It may not hold the true end bound of the
// underlying span.
i.span = nil
}
}
func (i *InterleavingIter) verify(
k *base.InternalKey, v base.LazyValue,
) (*base.InternalKey, base.LazyValue) {
// Wrap the entire function body in the invariants build tag, so that
// production builds elide this entire function.
if invariants.Enabled {
@@ -893,38 +1008,32 @@ func (i *InterleavingIter) verify(k *base.InternalKey, v []byte) (*base.Internal
func (i *InterleavingIter) savedKeyspan() {
i.keyspanInterleaved = false
i.spanMarkerTruncated = false
if i.maskState == maskCurrent {
// There's a span set as current. If the keyspan iterator was
// exhausted or moved to an empty keyspan, the current mask will need to be
// cleared. If the keyspan iterator was moved to a new keyspan, then we'll need
// to set the new mask once we step into the new keyspan's bounds.
//
// Either way, we need to reset to to `maskStale`.
i.maskState = maskStale
}
i.maskSpanChangedCalled = false
}
// maybeUpdateMask updates the current mask, if a mask is configured and
// the mask hasn't been updated with the current keyspan yet.
func (i *InterleavingIter) maybeUpdateMask(covered bool) {
if i.mask == nil {
return
if i.mask != nil {
if !covered || i.span.Empty() {
i.clearMask()
} else if !i.maskSpanChangedCalled {
if i.truncated {
i.mask.SpanChanged(&i.truncatedSpan)
} else {
i.mask.SpanChanged(i.span)
}
i.maskSpanChangedCalled = true
}
}
}
// Although covered=true, the covering span might be empty. Empty span's
// don't mask.
covered = covered && !i.span.Empty()
if covered && i.maskState != maskCurrent {
// We're currently positioned within the bounds of i.span, and we
// haven't updated the mask to this span yet. Update it now.
i.mask.SpanChanged(i.span)
i.maskState = maskCurrent
} else if !covered && i.maskState != maskCleared {
// We're not positioned beneath i.span, so we shouldn't set the mask to
// i.span. However, we've never cleared the previous mask.
// clearMask clears the current mask, if a mask is configured and no mask should
// be active.
func (i *InterleavingIter) clearMask() {
if i.mask != nil {
i.maskSpanChangedCalled = false
i.mask.SpanChanged(nil)
i.maskState = maskCleared
}
}
@@ -955,6 +1064,16 @@ func (i *InterleavingIter) Span() *Span {
func (i *InterleavingIter) SetBounds(lower, upper []byte) {
i.lower, i.upper = lower, upper
i.pointIter.SetBounds(lower, upper)
i.Invalidate()
}
// Invalidate invalidates the interleaving iterator's current position, clearing
// its state. This prevents optimizations such as reusing the current span on
// seek.
func (i *InterleavingIter) Invalidate() {
i.span = nil
i.pointKey = nil
i.pointVal = base.LazyValue{}
}
// Error implements (base.InternalIterator).Error.
@@ -974,18 +1093,6 @@ func (i *InterleavingIter) String() string {
return fmt.Sprintf("keyspan-interleaving(%q)", i.pointIter.String())
}
var _ base.InternalIteratorWithStats = &InterleavingIter{}
// Stats implements InternalIteratorWithStats.
func (i *InterleavingIter) Stats() base.InternalIteratorStats {
return i.pointIter.Stats()
}
// ResetStats implements InternalIteratorWithStats.
func (i *InterleavingIter) ResetStats() {
i.pointIter.ResetStats()
}
func firstError(err0, err1 error) error {
if err0 != nil {
return err0

View File

@@ -38,57 +38,57 @@ func (i *InternalIteratorShim) Span() *Span {
// SeekGE implements (base.InternalIterator).SeekGE.
func (i *InternalIteratorShim) SeekGE(
key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
// SeekPrefixGE implements (base.InternalIterator).SeekPrefixGE.
func (i *InternalIteratorShim) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*base.InternalKey, base.LazyValue) {
panic("unimplemented")
}
// SeekLT implements (base.InternalIterator).SeekLT.
func (i *InternalIteratorShim) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*base.InternalKey, []byte) {
) (*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 != nil && i.span.Empty() {
i.span = i.miter.Next()
}
if i.span == nil {
return nil, 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 != nil && i.span.Empty() {
i.span = i.miter.Next()
}
if i.span == nil {
return nil, 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

@@ -52,7 +52,11 @@ func visibleTransform(snapshot uint64) Transformer {
dst.Start, dst.End = s.Start, s.End
dst.Keys = dst.Keys[:0]
for _, k := range s.Keys {
if base.Visible(k.SeqNum(), snapshot) {
// 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)
}
}
@@ -68,6 +72,10 @@ func visibleTransform(snapshot uint64) Transformer {
// 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
@@ -210,9 +218,9 @@ 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.
@@ -809,15 +817,22 @@ 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.
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.transformer.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, nil
}

View File

@@ -31,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.
@@ -69,9 +83,9 @@ func (k Key) Kind() base.InternalKeyKind {
// 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(cmp base.Compare, b Key) bool {
func (k Key) Equal(equal base.Equal, b Key) bool {
return k.Trailer == b.Trailer &&
cmp(k.Suffix, b.Suffix) == 0 &&
equal(k.Suffix, b.Suffix) &&
bytes.Equal(k.Value, b.Value)
}
@@ -90,10 +104,13 @@ func (s *Span) Empty() bool {
}
// SmallestKey returns the smallest internal key defined by the span's keys.
// It panics if the span contains no keys.
// 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{
@@ -108,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.
// 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()
@@ -119,19 +139,26 @@ 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.
// 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.
// 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()
}
@@ -140,11 +167,16 @@ func (s *Span) LargestSeqNum() uint64 {
// that avoid the need to construct a new Span.
// Visible returns a span with the subset of keys visible at the provided
// sequence number.
// 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
@@ -214,7 +246,13 @@ func (s Span) Visible(snapshot uint64) Span {
// 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 {
@@ -235,9 +273,10 @@ func (s *Span) VisibleAt(snapshot uint64) bool {
// None of the key byte slices are cloned (see Span.DeepClone).
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
@@ -248,9 +287,10 @@ func (s *Span) ShallowClone() Span {
// because it is allocation heavy.
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)
@@ -274,7 +314,13 @@ func (s *Span) Contains(cmp base.Compare, key []byte) bool {
}
// 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
}
@@ -284,7 +330,13 @@ func (s Span) Covers(seqNum uint64) bool {
//
// 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 {
@@ -336,8 +388,8 @@ func (s prettySpan) Format(fs fmt.State, c rune) {
fmt.Fprintf(fs, "}")
}
// SortKeys sorts a keys slice by trailer.
func SortKeys(keys *[]Key) {
// 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)

View File

@@ -724,7 +724,7 @@ func (s *L0Sublevels) InitCompactingFileInfo(inProgress []L0Compaction) {
s.formatKey(s.orderedIntervals[f.maxIntervalIndex+1].startKey.key), s.formatKey(f.Smallest.UserKey)))
}
}
if !f.Compacting {
if !f.IsCompacting() {
continue
}
for i := f.minIntervalIndex; i <= f.maxIntervalIndex; i++ {
@@ -805,7 +805,7 @@ func (s *L0Sublevels) describe(verbose bool) string {
}
sumIntervals += intervals
totalBytes += f.Size
if f.Compacting {
if f.IsCompacting() {
numCompactingFiles++
}
}
@@ -1052,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",
@@ -1396,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
@@ -1428,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
@@ -1549,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
@@ -1600,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)
@@ -1624,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
@@ -1676,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++
@@ -1946,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
@@ -1998,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

@@ -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
@@ -183,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
@@ -794,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

@@ -18,33 +18,36 @@ import (
// 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
defragBufA keysBySuffix
defragBufB keysBySuffix
// defragBufAlloc defines two arrays used to preallocate defragBuf{A,B} keys
// slices to prevent additional allocations during iteration. Each array
// preallocates up to two keys.
defragBufAlloc [2][2]keyspan.Key
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.
// 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 (ui *UserIteratorConfig) Init(
cmp base.Compare, snapshot uint64, iters ...keyspan.FragmentIterator,
comparer *base.Comparer,
snapshot uint64,
lower, upper []byte,
hasPrefix *bool,
prefix *[]byte,
iters ...keyspan.FragmentIterator,
) keyspan.FragmentIterator {
ui.snapshot = snapshot
ui.defragBufA.keys = ui.defragBufAlloc[0][:0]
ui.defragBufB.keys = ui.defragBufAlloc[1][:0]
ui.miter.Init(cmp, ui, iters...)
ui.diter.Init(cmp, &ui.miter, ui, keyspan.StaticDefragmentReducer)
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
}
@@ -66,21 +69,31 @@ func (ui *UserIteratorConfig) NewLevelIter() *keyspan.LevelIter {
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.
// 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
if err := Coalesce(cmp, s.Visible(ui.snapshot).Keys, &dst.Keys); err != nil {
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.
@@ -89,8 +102,14 @@ func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *k
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:
@@ -100,6 +119,8 @@ func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *k
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
}
@@ -107,50 +128,45 @@ func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *k
// 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 (UserIterationConfig).Transform, 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.
//
// This implementation is stateful, and must not be used on multiple
// DefragmentingIters concurrently.
func (ui *UserIteratorConfig) ShouldDefragment(cmp base.Compare, a, b *keyspan.Span) bool {
// 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 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.
// 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.
ui.defragBufA.cmp = cmp
ui.defragBufA.keys = append(ui.defragBufA.keys[:0], a.Keys...)
sort.Sort(&ui.defragBufA)
ui.defragBufB.cmp = cmp
ui.defragBufB.keys = append(ui.defragBufB.keys[:0], b.Keys...)
sort.Sort(&ui.defragBufB)
for i := range ui.defragBufA.keys {
if ui.defragBufA.keys[i].Kind() != base.InternalKeyKindRangeKeySet ||
ui.defragBufB.keys[i].Kind() != base.InternalKeyKindRangeKeySet {
panic("pebble: unexpected non-RangeKeySet during defragmentation")
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 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")
}
}
if cmp(ui.defragBufA.keys[i].Suffix, ui.defragBufB.keys[i].Suffix) != 0 {
return false
if !equal(a.Keys[i].Suffix, b.Keys[i].Suffix) {
ret = false
break
}
if !bytes.Equal(ui.defragBufA.keys[i].Value, ui.defragBufB.keys[i].Value) {
return false
if !bytes.Equal(a.Keys[i].Value, b.Keys[i].Value) {
ret = false
break
}
}
return true
return ret
}
// Coalesce imposes range key semantics and coalesces range keys with the same
@@ -186,14 +202,24 @@ func (ui *UserIteratorConfig) ShouldDefragment(cmp base.Compare, a, b *keyspan.S
// 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.
//
// 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)[: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(keys) && !deleted; i++ {
k := keys[i]
@@ -215,7 +241,7 @@ func Coalesce(cmp base.Compare, keys []keyspan.Key, dst *[]keyspan.Key) error {
continue
}
keysBySuffix.keys = append(keysBySuffix.keys, k)
sort.Sort(&keysBySuffix)
sort.Sort(keysBySuffix)
case base.InternalKeyKindRangeKeyUnset:
n := len(keysBySuffix.keys)
@@ -225,7 +251,7 @@ func Coalesce(cmp base.Compare, keys []keyspan.Key, dst *[]keyspan.Key) error {
continue
}
keysBySuffix.keys = append(keysBySuffix.keys, k)
sort.Sort(&keysBySuffix)
sort.Sort(keysBySuffix)
case base.InternalKeyKindRangeKeyDelete:
// All remaining range keys in this span have been deleted by this
// RangeKeyDelete. There's no need to continue looping, because all
@@ -239,22 +265,13 @@ func Coalesce(cmp base.Compare, keys []keyspan.Key, dst *[]keyspan.Key) error {
}
}
// Update the span with the (potentially reduced) keys slice, and re-sort it
// by Trailer.
// 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
keyspan.SortKeys(dst)
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

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

@@ -52,7 +52,7 @@ type simpleMergingIterLevel struct {
levelIterBoundaryContext
iterKey *InternalKey
iterValue []byte
iterValue base.LazyValue
tombstone *keyspan.Span
}
@@ -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",
@@ -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
}
@@ -634,7 +639,7 @@ 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.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
mlevelAlloc[0].iter = li
@@ -648,7 +653,7 @@ 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.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
mlevelAlloc[0].iter = li

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
@@ -81,7 +91,7 @@ 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
// filteredIter is an optional interface that may be implemented by internal
// iterators that perform filtering of keys. When a new file's iterator is
@@ -107,8 +117,6 @@ 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::levelIterBoundaryContext`.
// We populate it with the corresponding bounds for the currently opened file. It is used for
@@ -158,8 +166,9 @@ type levelIter struct {
// - `*Boundary` is not exposed to the next higher-level iterator, i.e., `mergingIter`.
boundaryContext *levelIterBoundaryContext
// 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
@@ -205,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
}
@@ -216,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
@@ -232,7 +241,7 @@ func (l *levelIter) init(
l.iterFile = nil
l.newIters = newIters
l.files = files
l.bytesIterated = bytesIterated
l.internalOpts = internalOpts
}
func (l *levelIter) initRangeDel(rangeDelIter *keyspan.FragmentIterator) {
@@ -260,9 +269,16 @@ func (l *levelIter) maybeTriggerCombinedIteration(file *fileMetadata, dir int) {
// 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.
//
// 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) {
(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
@@ -294,22 +310,64 @@ func (l *levelIter) maybeTriggerCombinedIteration(file *fileMetadata, dir int) {
}
}
func (l *levelIter) findFileGE(key []byte, isRelativeSeek bool) *fileMetadata {
// Find the earliest file whose largest key is >= ikey.
func (l *levelIter) findFileGE(key []byte, flags base.SeekGEFlags) *fileMetadata {
// Find the earliest file whose largest key is >= key.
// Ordinarily we seek the LevelIterator using SeekGE.
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.
//
// 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 end key, we
// need to check whether all of the files between the old position and the
// new position contain any range keys.
// 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.
//
// In this scenario, we don't seek the LevelIterator and instead we Next it,
// one file at a time, checking each for range keys.
nextInsteadOfSeek := isRelativeSeek && l.combinedIterState != nil && !l.combinedIterState.initialized
// 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 {
@@ -349,11 +407,22 @@ func (l *levelIter) findFileGE(key []byte, isRelativeSeek bool) *fileMetadata {
// 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 point key bound ≥ `key`. But the largest point key
// 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
@@ -374,7 +443,7 @@ func (l *levelIter) findFileGE(key []byte, isRelativeSeek bool) *fileMetadata {
return m
}
func (l *levelIter) findFileLT(key []byte, isRelativeSeek bool) *fileMetadata {
func (l *levelIter) findFileLT(key []byte, flags base.SeekLTFlags) *fileMetadata {
// Find the last file whose smallest key is < ikey.
// Ordinarily we seek the LevelIterator using SeekLT.
@@ -389,7 +458,7 @@ func (l *levelIter) findFileLT(key []byte, isRelativeSeek bool) *fileMetadata {
//
// 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 := isRelativeSeek && l.combinedIterState != nil && !l.combinedIterState.initialized
prevInsteadOfSeek := flags.RelativeSeek() && l.combinedIterState != nil && !l.combinedIterState.initialized
var m *fileMetadata
if prevInsteadOfSeek {
@@ -563,8 +632,8 @@ 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
}
@@ -594,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.
@@ -612,7 +681,7 @@ func (l *levelIter) verify(key *InternalKey, val []byte) (*InternalKey, []byte)
return key, val
}
func (l *levelIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, []byte) {
func (l *levelIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, base.LazyValue) {
l.err = nil // clear cached iteration error
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
@@ -621,9 +690,9 @@ func (l *levelIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, []
// NB: the top-level Iterator has already adjusted key based on
// IterOptions.LowerBound.
loadFileIndicator := l.loadFile(l.findFileGE(key, flags.RelativeSeek()), +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
@@ -638,7 +707,7 @@ func (l *levelIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey, []
func (l *levelIter) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*base.InternalKey, base.LazyValue) {
l.err = nil // clear cached iteration error
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
@@ -647,9 +716,9 @@ func (l *levelIter) SeekPrefixGE(
// NB: the top-level Iterator has already adjusted key based on
// IterOptions.LowerBound.
loadFileIndicator := l.loadFile(l.findFileGE(key, flags.RelativeSeek()), +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
@@ -673,7 +742,7 @@ func (l *levelIter) SeekPrefixGE(
l.boundaryContext.isSyntheticIterBoundsKey = true
l.boundaryContext.isIgnorableBoundaryKey = false
}
return l.verify(l.largestBoundary, nil)
return l.verify(l.largestBoundary, base.LazyValue{})
}
// Return the file's largest bound, ensuring this file stays open until
// the mergingIter advances beyond the file's bounds. We set
@@ -684,7 +753,7 @@ func (l *levelIter) SeekPrefixGE(
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
@@ -695,12 +764,12 @@ func (l *levelIter) SeekPrefixGE(
// 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.LargestPointKey.UserKey); l.cmp(prefix, l.iterFile.LargestPointKey.UserKey[:n]) < 0 {
return nil, nil
return nil, base.LazyValue{}
}
return l.verify(l.skipEmptyFileForward())
}
func (l *levelIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, []byte) {
func (l *levelIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, base.LazyValue) {
l.err = nil // clear cached iteration error
if l.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
@@ -709,8 +778,8 @@ func (l *levelIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, []
// NB: the top-level Iterator has already adjusted key based on
// IterOptions.UpperBound.
if l.loadFile(l.findFileLT(key, flags.RelativeSeek()), -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, flags); key != nil {
return l.verify(key, val)
@@ -718,7 +787,7 @@ func (l *levelIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, []
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.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
@@ -728,7 +797,7 @@ func (l *levelIter) First() (*InternalKey, []byte) {
// 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)
@@ -736,7 +805,7 @@ 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.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
@@ -746,7 +815,7 @@ func (l *levelIter) Last() (*InternalKey, []byte) {
// 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)
@@ -754,9 +823,9 @@ 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.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
@@ -774,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 {
@@ -783,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.
@@ -795,9 +864,9 @@ 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.boundaryContext != nil {
l.boundaryContext.isSyntheticIterBoundsKey = false
@@ -815,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 {
@@ -824,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.
@@ -836,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
@@ -875,18 +944,18 @@ func (l *levelIter) skipEmptyFileForward() (*InternalKey, []byte) {
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.LargestPointKey.Kind() == InternalKeyKindRangeDelete {
l.largestBoundary = &l.iterFile.LargestPointKey
return l.largestBoundary, nil
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
@@ -912,21 +981,21 @@ func (l *levelIter) skipEmptyFileForward() (*InternalKey, []byte) {
l.filteredIter.MaybeFilteredKeys() {
l.largestBoundary = &l.iterFile.Largest
l.boundaryContext.isIgnorableBoundaryKey = true
return l.largestBoundary, nil
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
@@ -962,18 +1031,18 @@ func (l *levelIter) skipEmptyFileBackward() (*InternalKey, []byte) {
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.SmallestPointKey.Kind() == InternalKeyKindRangeDelete {
l.smallestBoundary = &l.iterFile.SmallestPointKey
return l.smallestBoundary, nil
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
@@ -998,13 +1067,13 @@ func (l *levelIter) skipEmptyFileBackward() (*InternalKey, []byte) {
if *l.rangeDelIterPtr != nil && l.filteredIter != nil && l.filteredIter.MaybeFilteredKeys() {
l.smallestBoundary = &l.iterFile.Smallest
l.boundaryContext.isIgnorableBoundaryKey = true
return l.smallestBoundary, nil
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
@@ -1019,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
}
@@ -1060,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

@@ -336,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)
}

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,7 +24,7 @@ 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
// levelIterBoundaryContext's fields are set when using levelIter, in order
// to surface sstable boundary keys and file-level context. See levelIter
@@ -230,17 +230,18 @@ type levelIterBoundaryContext 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
@@ -264,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()
@@ -285,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 {
@@ -666,29 +677,48 @@ 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 already seeked the iterator due to a
// range tombstone and are now 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.
// For prefix iteration, stop if we've already exceeded the iterator's
// current prefix. There are two cases where we perform this check.
//
// 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.
if m.prefix != nil && reseeked {
// 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, nil
return nil, base.LazyValue{}
}
}
@@ -698,14 +728,14 @@ func (m *mergingIter) findNextEntry() (*InternalKey, []byte) {
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.
@@ -841,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 {
@@ -852,14 +882,14 @@ 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.
@@ -953,7 +983,7 @@ 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, flags base.SeekGEFlags) (*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 */, flags)
@@ -965,7 +995,7 @@ func (m *mergingIter) SeekGE(key []byte, flags base.SeekGEFlags) (*InternalKey,
// that key is greater than or equal to the lower bound.
func (m *mergingIter) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*base.InternalKey, base.LazyValue) {
m.err = nil // clear cached iteration error
m.prefix = prefix
m.seekGE(key, 0 /* start level */, flags)
@@ -1043,7 +1073,7 @@ func (m *mergingIter) seekLT(key []byte, level int, flags base.SeekLTFlags) {
// 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, flags base.SeekLTFlags) (*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 */, flags)
@@ -1053,7 +1083,7 @@ func (m *mergingIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey,
// 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]
@@ -1068,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 {
@@ -1079,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 {
@@ -1090,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])
@@ -1182,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/HdrHistogram/hdrhistogram-go"
"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/prometheus/client_golang/prometheus"
)
// CacheMetrics holds metrics for the block and table cache.
@@ -158,7 +160,8 @@ type Metrics struct {
Flush struct {
// The total number of flushes.
Count int64
Count int64
WriteThroughput ThroughputMetric
}
Filter FilterMetrics
@@ -178,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
@@ -222,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 {
@@ -424,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 (
@@ -419,8 +419,15 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
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)
@@ -538,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 {
@@ -720,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
@@ -505,6 +550,10 @@ 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.
@@ -527,6 +576,12 @@ type Options struct {
// 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
@@ -535,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
@@ -632,7 +699,8 @@ 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
@@ -727,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
}
}
@@ -832,8 +907,8 @@ func (o *Options) EnsureDefaults() *Options {
o.Merger = DefaultMerger
}
o.private.strictWALTail = true
if o.MaxConcurrentCompactions <= 0 {
o.MaxConcurrentCompactions = 1
if o.MaxConcurrentCompactions == nil {
o.MaxConcurrentCompactions = func() int { return 1 }
}
if o.NumPrevManifest <= 0 {
o.NumPrevManifest = 1
@@ -855,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
}
@@ -864,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
@@ -938,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)
@@ -947,13 +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_deletion_rate=%d\n", o.Experimental.MinDeletionRate)
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)
@@ -974,6 +1063,15 @@ func (o *Options) String() string {
fmt.Fprintf(&buf, " max_writer_concurrency=%d\n", o.Experimental.MaxWriterConcurrency)
fmt.Fprintf(&buf, " force_writer_parallelism=%t\n", o.Experimental.ForceWriterParallelism)
// 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]
fmt.Fprintf(&buf, "\n")
@@ -1111,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":
@@ -1141,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":
@@ -1159,6 +1273,8 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
case "min_flush_rate":
// 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":

View File

@@ -9,12 +9,15 @@ import (
"github.com/cockroachdb/pebble/internal/invariants"
"github.com/cockroachdb/pebble/internal/keyspan"
"github.com/cockroachdb/pebble/internal/manifest"
"github.com/cockroachdb/pebble/sstable"
)
// 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.cmp, i.seqNum)
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 i.batch != nil {
@@ -53,8 +56,13 @@ func (i *Iterator) constructRangeKeyIter() {
// 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.First(); f != nil; f = iter.Next() {
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 {
@@ -77,6 +85,302 @@ func (i *Iterator) constructRangeKeyIter() {
}
}
// 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
@@ -87,7 +391,7 @@ type lazyCombinedIter struct {
// iterator. It's used to mutate the internalIterator in use when switching
// to combined iteration.
parent *Iterator
pointIter internalIteratorWithStats
pointIter internalIterator
combinedIterState combinedIterState
}
@@ -134,8 +438,8 @@ var _ internalIterator = (*lazyCombinedIter)(nil)
// 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 []byte, seekKey []byte,
) (*InternalKey, []byte) {
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 {
@@ -147,19 +451,6 @@ func (i *lazyCombinedIter) initCombinedIteration(
}
}
// 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.cmp, i.parent.split, &i.parent.opts)
i.parent.constructRangeKeyIter()
// Initialize the Iterator's interleaving iterator.
i.parent.rangeKey.iiter.Init(
i.parent.cmp, i.parent.pointIter, i.parent.rangeKey.rangeKeyIter,
i.parent.rangeKey, i.parent.opts.LowerBound, i.parent.opts.UpperBound)
// 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.
@@ -205,6 +496,37 @@ func (i *lazyCombinedIter) initCombinedIteration(
}
}
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
@@ -225,12 +547,18 @@ func (i *lazyCombinedIter) initCombinedIteration(
// which case the range key will be interleaved next instead of the point
// key.
if dir == +1 {
return i.parent.rangeKey.iiter.InitSeekGE(seekKey, pointKey, pointValue)
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, []byte) {
func (i *lazyCombinedIter) SeekGE(
key []byte, flags base.SeekGEFlags,
) (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.SeekGE(key, flags)
}
@@ -243,7 +571,7 @@ func (i *lazyCombinedIter) SeekGE(key []byte, flags base.SeekGEFlags) (*Internal
func (i *lazyCombinedIter) SeekPrefixGE(
prefix, key []byte, flags base.SeekGEFlags,
) (*InternalKey, []byte) {
) (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.SeekPrefixGE(prefix, key, flags)
}
@@ -254,7 +582,9 @@ func (i *lazyCombinedIter) SeekPrefixGE(
return k, v
}
func (i *lazyCombinedIter) SeekLT(key []byte, flags base.SeekLTFlags) (*InternalKey, []byte) {
func (i *lazyCombinedIter) SeekLT(
key []byte, flags base.SeekLTFlags,
) (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.SeekLT(key, flags)
}
@@ -265,7 +595,7 @@ func (i *lazyCombinedIter) SeekLT(key []byte, flags base.SeekLTFlags) (*Internal
return k, v
}
func (i *lazyCombinedIter) First() (*InternalKey, []byte) {
func (i *lazyCombinedIter) First() (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.First()
}
@@ -276,7 +606,7 @@ func (i *lazyCombinedIter) First() (*InternalKey, []byte) {
return k, v
}
func (i *lazyCombinedIter) Last() (*InternalKey, []byte) {
func (i *lazyCombinedIter) Last() (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.Last()
}
@@ -287,7 +617,7 @@ func (i *lazyCombinedIter) Last() (*InternalKey, []byte) {
return k, v
}
func (i *lazyCombinedIter) Next() (*InternalKey, []byte) {
func (i *lazyCombinedIter) Next() (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.Next()
}
@@ -298,7 +628,7 @@ func (i *lazyCombinedIter) Next() (*InternalKey, []byte) {
return k, v
}
func (i *lazyCombinedIter) Prev() (*InternalKey, []byte) {
func (i *lazyCombinedIter) Prev() (*InternalKey, base.LazyValue) {
if i.combinedIterState.initialized {
return i.parent.rangeKey.iiter.Prev()
}
@@ -337,11 +667,3 @@ func (i *lazyCombinedIter) String() string {
}
return i.pointIter.String()
}
func (i *lazyCombinedIter) Stats() InternalIteratorStats {
return i.pointIter.Stats()
}
func (i *lazyCombinedIter) ResetStats() {
i.pointIter.ResetStats()
}

View File

@@ -13,10 +13,10 @@ import (
"sync/atomic"
"time"
"github.com/HdrHistogram/hdrhistogram-go"
"github.com/cockroachdb/errors"
"github.com/cockroachdb/pebble/internal/base"
"github.com/cockroachdb/pebble/internal/crc"
"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, flags base.SeekGEFlags) (*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, flags base.SeekGEFlags) (*InternalKey, []
// 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, flags base.SeekGEFlags,
) (*base.InternalKey, []byte) {
) (*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, flags base.SeekLTFlags) (*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, flags base.SeekLTFlags) (*InternalKey, []
// 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, flags base.SeekLTFlags) (*InternalKey, []
// 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, flags base.SeekLTFlags) (*InternalKey, []
}
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,7 +1019,7 @@ 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 nil
@@ -1028,6 +1030,7 @@ 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 nil
@@ -1037,7 +1040,8 @@ func (i *fragmentBlockIter) gatherForward(k *InternalKey, internalValue []byte)
// 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 {
@@ -1053,7 +1057,8 @@ func (i *fragmentBlockIter) gatherForward(k *InternalKey, internalValue []byte)
i.span = keyspan.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
@@ -1067,7 +1072,7 @@ 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 nil
@@ -1078,6 +1083,7 @@ 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 nil
@@ -1087,7 +1093,8 @@ func (i *fragmentBlockIter) gatherBackward(k *InternalKey, internalValue []byte)
// 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 {
@@ -1103,13 +1110,14 @@ func (i *fragmentBlockIter) gatherBackward(k *InternalKey, internalValue []byte)
i.span = keyspan.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
}
@@ -1171,7 +1179,8 @@ 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 (keyspan.FragmentIterator).Prev.
@@ -1204,7 +1213,8 @@ 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 (keyspan.FragmentIterator).SeekGE.

View File

@@ -61,19 +61,25 @@ import (
// re-applying the kv filter while reading results back from Pebble.
//
// b) Block property filtering may surface deleted key-value pairs if the
// the kv filter is not a strict function of the key's user key. A block
// kv filter is not a strict function of the key's user key. A block
// containing k.DEL may be filtered, while a block containing the deleted
// key k.SET may not be filtered, if the kv filter applies to one but not
// the other.
//
// This error may be avoided trivially by using a kv filter that is a pure
// function of the the user key. A filter that examines values or key kinds
// 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
@@ -140,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.
@@ -354,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
@@ -387,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
@@ -482,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{
@@ -492,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
}
@@ -546,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
@@ -560,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

@@ -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

@@ -178,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)
}
@@ -410,7 +411,11 @@ 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()

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,

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
}
@@ -1082,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
@@ -1141,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
@@ -1815,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()
@@ -1840,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

@@ -133,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(
@@ -299,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) {
@@ -309,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 {
@@ -318,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)
}
@@ -331,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 {
@@ -352,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
@@ -369,7 +376,8 @@ 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)
@@ -405,11 +413,11 @@ 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 {
@@ -454,7 +462,7 @@ func (c *tableCacheShard) newRangeKeyIter(
// 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)
ok, _, err = c.checkAndIntersectFilters(v, nil, opts.RangeKeyFilters, nil)
}
if err != nil {
c.unrefValue(v)
@@ -848,10 +856,14 @@ 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
}

View File

@@ -53,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
}
@@ -126,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()
@@ -174,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
}
@@ -216,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
}
@@ -270,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
}
@@ -305,7 +305,7 @@ func (d *DB) loadTablePointKeyStats(
func (d *DB) loadTableRangeDelStats(
r *sstable.Reader, v *version, level int, meta *fileMetadata, stats *manifest.TableStats,
) ([]deleteCompactionHint, error) {
iter, err := newCombinedDeletionKeyspanIter(d.cmp, r, meta)
iter, err := newCombinedDeletionKeyspanIter(d.opts.Comparer, r, meta)
if err != nil {
return nil, err
}
@@ -550,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
}
@@ -657,11 +655,11 @@ func estimateEntrySizes(
// corresponding to the largest and smallest sequence numbers encountered across
// the range deletes and range keys deletes that comprised the merged spans.
func newCombinedDeletionKeyspanIter(
cmp base.Compare, r *sstable.Reader, m *fileMetadata,
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.Compare, a, b *keyspan.Span) bool { return true })
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.
@@ -690,7 +688,7 @@ func newCombinedDeletionKeyspanIter(
smallest = last
}
}
if largest.Equal(cmp, smallest) {
if largest.Equal(comparer.Equal, smallest) {
current = append(current[:0], largest)
} else {
current = append(current[:0], largest, smallest)
@@ -703,11 +701,21 @@ func newCombinedDeletionKeyspanIter(
// 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(cmp, transform)
mIter.Init(comparer.Compare, transform)
iter, err := r.NewRawRangeDelIter()
if err != nil {
@@ -715,12 +723,12 @@ func newCombinedDeletionKeyspanIter(
}
if iter != nil {
dIter := &keyspan.DefragmentingIter{}
dIter.Init(cmp, iter, equal, reducer)
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(
cmp, iter, m.Smallest.UserKey, m.Largest.UserKey, nil, nil,
comparer.Compare, iter, m.Smallest.UserKey, m.Largest.UserKey, nil, nil,
)
mIter.AddLevel(iter)
}
@@ -744,10 +752,57 @@ func newCombinedDeletionKeyspanIter(
return len(out.Keys) > 0
})
dIter := &keyspan.DefragmentingIter{}
dIter.Init(cmp, iter, equal, reducer)
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
@@ -474,6 +540,11 @@ func (vs *versionSet) logAndApply(
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()

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

@@ -1,5 +1,9 @@
# redact
Utilities to redact Go strings for confidentiality
Godoc link: https://pkg.go.dev/github.com/cockroachdb/redact?tab=doc
![Build Status](https://github.com/cockroachdb/redact/actions/workflows/ci.yaml/badge.svg?branch=master)
[![Go Reference](https://pkg.go.dev/badge/github.com/cockroachdb/redact.svg)](https://pkg.go.dev/github.com/cockroachdb/redact)
Some explanations about how this is used in e.g. CockroachDB:
https://wiki.crdb.io/wiki/spaces/CRDB/pages/1824817806/Log+and+error+redactability

View File

@@ -14,22 +14,23 @@
package redact
import "fmt"
import (
"fmt"
"reflect"
"github.com/cockroachdb/redact/builder"
i "github.com/cockroachdb/redact/interfaces"
b "github.com/cockroachdb/redact/internal/buffer"
fw "github.com/cockroachdb/redact/internal/fmtforward"
m "github.com/cockroachdb/redact/internal/markers"
w "github.com/cockroachdb/redact/internal/redact"
ifmt "github.com/cockroachdb/redact/internal/rfmt"
)
// SafeFormatter is implemented by object types that want to separate
// safe and non-safe information when printed out by a Printf-like
// formatter.
type SafeFormatter interface {
// SafeFormat is like the Format method of fmt.Formatter, except
// that it operates using a SafePrinter instead of a fmt.State for
// output.
//
// The verb argument is the control character that defines
// the formatting mode in the surrounding Printf call.
// For example, if this method is called to format %03d,
// the verb is 'd'.
SafeFormat(s SafePrinter, verb rune)
}
type SafeFormatter = i.SafeFormatter
// SafeValue is a marker interface to be implemented by types that
// alias base Go types and whose natural representation via Printf is
@@ -48,17 +49,13 @@ type SafeFormatter interface {
// collect all the types that implement this interface, as well as all
// uses of this type, and produce a report. Changes to this report
// should receive maximal amount of scrutiny during code reviews.
type SafeValue interface {
SafeValue()
}
type SafeValue = i.SafeValue
// SafeMessager is an alternative to SafeFormatter used in previous
// versions of CockroachDB.
// NB: this interface is obsolete. Use SafeFormatter instead.
// TODO(knz): Remove this.
type SafeMessager interface {
SafeMessage() string
}
type SafeMessager = i.SafeMessager
// SafePrinter is a stateful helper that abstracts an output stream in
// the context of printf-like formatting, but with the ability to
@@ -67,60 +64,112 @@ type SafeMessager interface {
// This package provides one implementation of this using marker
// delimiters for unsafe data, see markers.go. We would like to aim
// for alternate implementations to generate more structured formats.
type SafePrinter interface {
// SafePrinter inherits fmt.State to access format flags, however
// calls to fmt.State's underlying Write() as unsafe.
fmt.State
// SafePrinter provides the SafeWriter interface.
SafeWriter
}
type SafePrinter = i.SafePrinter
// SafeWriter provides helper functions for use in implementations of
// SafeFormatter, to format mixes of safe and unsafe strings.
type SafeWriter interface {
// SafeString emits a safe string.
SafeString(SafeString)
type SafeWriter = i.SafeWriter
// SafeRune emits a safe rune.
SafeRune(SafeRune)
// SafeString represents a string that is not a sensitive value.
type SafeString = i.SafeString
// Print emits its arguments separated by spaces.
// For each argument it dynamically checks for the SafeFormatter or
// SafeValue interface and either use that, or mark the argument
// payload as unsafe.
Print(args ...interface{})
// SafeInt represents an integer that is not a sensitive value.
type SafeInt = i.SafeInt
// For printf, a linter checks that the format string is
// a constant literal, so the implementation can assume it's always
// safe.
Printf(format string, arg ...interface{})
// SafeUint represents an integer that is not a sensitive value.
type SafeUint = i.SafeUint
// UnsafeString writes an unsafe string.
UnsafeString(string)
// UnsafeByte writes an unsafe byte.
UnsafeByte(byte)
// UnsafeBytes writes an unsafe byte slice.
UnsafeBytes([]byte)
// UnsafeRune writes an unsafe rune.
UnsafeRune(rune)
}
// SafeString aliases string. This is not meant to be used directly;
// the type definition ensures that SafePrinter's SafeString method
// can only take constant string literals as arguments. Typically, a
// Go linter would ensure that ConstantString is never used to cast a
// value.
type SafeString string
// SafeValue makes SafeString a SafeValue.
func (SafeString) SafeValue() {}
// SafeFloat represents a floating-point value that is not a sensitive value.
type SafeFloat = i.SafeFloat
// SafeRune aliases rune. See the explanation for SafeString.
type SafeRune rune
type SafeRune = i.SafeRune
// SafeValue makes SafeRune a SafeValue.
func (SafeRune) SafeValue() {}
// RedactableString is a string that contains a mix of safe and unsafe
// bits of data, but where it is known that unsafe bits are enclosed
// by redaction markers ‹ and ›, and occurrences of the markers
// inside the original data items have been escaped.
//
// Instances of RedactableString should not be constructed directly;
// instead use the facilities from print.go (Sprint, Sprintf)
// or the methods below.
type RedactableString = m.RedactableString
// RedactableBytes is like RedactableString but is a byte slice.
//
// Instances of RedactableBytes should not be constructed directly;
// instead use the facilities from print.go (Sprint, Sprintf)
// or the methods below.
type RedactableBytes = m.RedactableBytes
// StartMarker returns the start delimiter for an unsafe string.
func StartMarker() []byte { return m.StartMarker() }
// EndMarker returns the end delimiter for an unsafe string.
func EndMarker() []byte { return m.EndMarker() }
// RedactedMarker returns the special string used by Redact.
func RedactedMarker() []byte { return m.RedactedMarker() }
// EscapeMarkers escapes the special delimiters from the provided
// byte slice.
func EscapeMarkers(s []byte) []byte { return m.EscapeMarkers(s) }
// EscapeBytes escapes markers inside the given byte slice and encloses
// the entire byte slice between redaction markers.
// EscapeBytes escapes markers inside the given byte slice and encloses
// the entire byte slice between redaction markers.
func EscapeBytes(s []byte) RedactableBytes { return ifmt.EscapeBytes(s) }
// ManualBuffer is a variable-sized buffer of bytes with
// Write methods. Writes are escaped in different ways
// depending on the output mode.
// Note: This type is only meant for "advanced" usage.
// For most common uses, consider using StringBuilder instead.
type ManualBuffer = b.Buffer
// Unsafe turns any value that would otherwise be considered safe,
// into an unsafe value.
func Unsafe(a interface{}) interface{} { return w.Unsafe(a) }
// Safe turns any value into an object that is considered as safe by
// the formatter.
//
// This is provided as an “escape hatch” for cases where the other
// interfaces and conventions fail. Increased usage of this mechanism
// should be taken as a signal that a new abstraction is missing.
// The implementation is also slow.
func Safe(a interface{}) SafeValue { return w.Safe(a) }
// RegisterRedactErrorFn registers an error redaction function for use
// during automatic redaction by this package.
// Provided e.g. by cockroachdb/errors.
func RegisterRedactErrorFn(fn func(err error, p i.SafePrinter, verb rune)) {
ifmt.RegisterRedactErrorFn(fn)
}
// MakeFormat is a helper for use by implementations of the
// SafeFormatter interface. It reproduces the format currently active
// in fmt.State and verb. This is provided because Go's standard
// fmt.State does not make the original format string available to us.
//
// If the return value justV is true, then the current state
// was found to be %v exactly; in that case the caller
// can avoid a full-blown Printf call and use just Print instead
// to take a shortcut.
func MakeFormat(s fmt.State, verb rune) (justV bool, format string) {
return fw.MakeFormat(s, verb)
}
// StringBuilder accumulates strings with optional redaction markers.
//
// It implements io.Writer but marks direct writes as redactable.
// To distinguish safe and unsafe bits, it also implements the SafeWriter
// interface.
type StringBuilder = builder.StringBuilder
// RegisterSafeType registers a data type to always be considered safe
// during the production of redactable strings.
func RegisterSafeType(t reflect.Type) {
ifmt.RegisterSafeType(t)
}

View File

@@ -12,13 +12,15 @@
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package redact
package builder
import (
"bytes"
"fmt"
"io"
"unicode/utf8"
i "github.com/cockroachdb/redact/interfaces"
ib "github.com/cockroachdb/redact/internal/buffer"
ifmt "github.com/cockroachdb/redact/internal/rfmt"
)
// StringBuilder accumulates strings with optional redaction markers.
@@ -27,26 +29,14 @@ import (
// To distinguish safe and unsafe bits, it also implements the SafeWriter
// interface.
type StringBuilder struct {
// we use bytes.Buffer internally to simplify the implementation of
// the SafeWriter interface.
buf bytes.Buffer
ib.Buffer
}
// String returns the accumulated string, with redaction markers stripped out.
// To obtain the redactable string, call RedactableString().
func (b StringBuilder) String() string { return b.RedactableString().StripMarkers() }
var _ fmt.Stringer = StringBuilder{}
var _ fmt.Stringer = (*StringBuilder)(nil)
// RedactableString returns the accumulated string, including redaction markers.
func (b StringBuilder) RedactableString() RedactableString { return RedactableString(b.buf.String()) }
// RedactableBytes returns the accumulated bytes, including redaction markers.
func (b StringBuilder) RedactableBytes() RedactableBytes { return RedactableBytes(b.buf.Bytes()) }
// SafeFormat implements SafeFormatter.
func (b StringBuilder) SafeFormat(p SafePrinter, _ rune) {
func (b StringBuilder) SafeFormat(p i.SafePrinter, _ rune) {
// We only support the %v / %s natural print here.
// Go supports other formatting verbs for strings: %x/%X/%q.
//
@@ -64,20 +54,8 @@ func (b StringBuilder) SafeFormat(p SafePrinter, _ rune) {
p.Print(b.RedactableString())
}
var _ SafeFormatter = StringBuilder{}
var _ SafeFormatter = (*StringBuilder)(nil)
// Len returns the number of accumulated bytes, including redaction
// markers; b.Len() == len(b.RedactableString()).
func (b *StringBuilder) Len() int { return b.buf.Len() }
// Cap returns the capacity of the builder's underlying byte slice. It is the
// total space allocated for the string being built and includes any bytes
// already written.
func (b *StringBuilder) Cap() int { return b.buf.Cap() }
// Reset resets the Builder to be empty.
func (b *StringBuilder) Reset() { b.buf.Reset() }
var _ i.SafeFormatter = StringBuilder{}
var _ i.SafeFormatter = (*StringBuilder)(nil)
// StringBuilder implements io.Writer.
// Direct Write() calls are considered unsafe.
@@ -85,65 +63,75 @@ var _ io.Writer = (*StringBuilder)(nil)
// Write implements the io.Writer interface.
func (b *StringBuilder) Write(s []byte) (int, error) {
b.UnsafeBytes(s)
return len(s), nil
b.SetMode(ib.UnsafeEscaped)
return b.Buffer.Write(s)
}
// StringBuilder implements SafeWriter.
var _ SafeWriter = (*StringBuilder)(nil)
var _ i.SafeWriter = (*StringBuilder)(nil)
// Print is part of the SafeWriter interface.
func (b *StringBuilder) Print(args ...interface{}) {
_, _ = Fprint(&b.buf, args...)
b.SetMode(ib.PreRedactable)
_, _ = ifmt.Fprint(&b.Buffer, args...)
}
// Printf is part of the SafeWriter interface.
func (b *StringBuilder) Printf(format string, args ...interface{}) {
_, _ = Fprintf(&b.buf, format, args...)
b.SetMode(ib.PreRedactable)
_, _ = ifmt.Fprintf(&b.Buffer, format, args...)
}
// SafeString is part of the SafeWriter interface.
func (b *StringBuilder) SafeString(s SafeString) {
w := escapeWriter{w: &b.buf, enclose: false}
_, _ = w.Write([]byte(s))
func (b *StringBuilder) SafeString(s i.SafeString) {
b.SetMode(ib.SafeEscaped)
_, _ = b.Buffer.WriteString(string(s))
}
// SafeInt is part of the SafeWriter interface.
func (b *StringBuilder) SafeInt(s i.SafeInt) {
b.SetMode(ib.SafeEscaped)
_, _ = ifmt.Fprintf(&b.Buffer, "%d", s)
}
// SafeUint is part of the SafeWriter interface.
func (b *StringBuilder) SafeUint(s i.SafeUint) {
b.SetMode(ib.SafeEscaped)
_, _ = ifmt.Fprintf(&b.Buffer, "%d", s)
}
// SafeFloat is part of the SafeWriter interface.
func (b *StringBuilder) SafeFloat(s i.SafeFloat) {
b.SetMode(ib.SafeEscaped)
_, _ = ifmt.Fprintf(&b.Buffer, "%v", s)
}
// SafeRune is part of the SafeWriter interface.
func (b *StringBuilder) SafeRune(s SafeRune) {
if s == startRedactable || s == endRedactable {
s = escapeMark
}
_, _ = b.buf.WriteRune(rune(s))
func (b *StringBuilder) SafeRune(s i.SafeRune) {
b.SetMode(ib.SafeEscaped)
_ = b.Buffer.WriteRune(rune(s))
}
// UnsafeString is part of the SafeWriter interface.
func (b *StringBuilder) UnsafeString(s string) {
w := escapeWriter{w: &b.buf, enclose: true, strip: true}
_, _ = w.Write([]byte(s))
b.SetMode(ib.UnsafeEscaped)
_, _ = b.Buffer.WriteString(s)
}
// UnsafeRune is part of the SafeWriter interface.
func (b *StringBuilder) UnsafeRune(s rune) {
_, _ = b.buf.WriteRune(startRedactable)
b.SafeRune(SafeRune(s))
_, _ = b.buf.WriteRune(endRedactable)
b.SetMode(ib.UnsafeEscaped)
_ = b.Buffer.WriteRune(s)
}
// UnsafeByte is part of the SafeWriter interface.
func (b *StringBuilder) UnsafeByte(s byte) {
_, _ = b.buf.WriteRune(startRedactable)
if s >= utf8.RuneSelf ||
s == startRedactableBytes[0] || s == endRedactableBytes[0] {
// Unsafe byte. Escape it.
_, _ = b.buf.Write(escapeBytes)
} else {
_ = b.buf.WriteByte(s)
}
_, _ = b.buf.WriteRune(endRedactable)
b.SetMode(ib.UnsafeEscaped)
_ = b.Buffer.WriteByte(s)
}
// UnsafeBytes is part of the SafeWriter interface.
func (b *StringBuilder) UnsafeBytes(s []byte) {
w := escapeWriter{w: &b.buf, enclose: true, strip: true}
_, _ = w.Write(s)
b.SetMode(ib.UnsafeEscaped)
_, _ = b.Buffer.Write(s)
}

View File

@@ -0,0 +1,149 @@
// 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 interfaces
import "fmt"
// SafeFormatter is implemented by object types that want to separate
// safe and non-safe information when printed out by a Printf-like
// formatter.
type SafeFormatter interface {
// SafeFormat is like the Format method of fmt.Formatter, except
// that it operates using a SafePrinter instead of a fmt.State for
// output.
//
// The verb argument is the control character that defines
// the formatting mode in the surrounding Printf call.
// For example, if this method is called to format %03d,
// the verb is 'd'.
SafeFormat(s SafePrinter, verb rune)
}
// SafePrinter is a stateful helper that abstracts an output stream in
// the context of printf-like formatting, but with the ability to
// separate safe and unsafe bits of data.
//
// This package provides one implementation of this using marker
// delimiters for unsafe data, see markers.go. We would like to aim
// for alternate implementations to generate more structured formats.
type SafePrinter interface {
// SafePrinter inherits fmt.State to access format flags, however
// calls to fmt.State's underlying Write() as unsafe.
fmt.State
// SafePrinter provides the SafeWriter interface.
SafeWriter
}
// SafeWriter provides helper functions for use in implementations of
// SafeFormatter, to format mixes of safe and unsafe strings.
type SafeWriter interface {
// SafeString emits a safe string.
SafeString(SafeString)
// SafeInt emits a safe integer.
SafeInt(SafeInt)
// SafeUint emits a safe unsigned integer.
SafeUint(SafeUint)
// SafeFloat emits a safe floating-point value.
SafeFloat(SafeFloat)
// SafeRune emits a safe rune.
SafeRune(SafeRune)
// Print emits its arguments separated by spaces.
// For each argument it dynamically checks for the SafeFormatter or
// SafeValue interface and either use that, or mark the argument
// payload as unsafe.
Print(args ...interface{})
// For printf, a linter checks that the format string is
// a constant literal, so the implementation can assume it's always
// safe.
Printf(format string, arg ...interface{})
// UnsafeString writes an unsafe string.
UnsafeString(string)
// UnsafeByte writes an unsafe byte.
UnsafeByte(byte)
// UnsafeBytes writes an unsafe byte slice.
UnsafeBytes([]byte)
// UnsafeRune writes an unsafe rune.
UnsafeRune(rune)
}
// SafeString represents a string that is not a sensitive value.
type SafeString string
// SafeValue makes SafeString a SafeValue.
func (SafeString) SafeValue() {}
// SafeInt represents an integer that is not a sensitive value.
type SafeInt int64
// SafeValue makes SafeInt a SafeValue.
func (SafeInt) SafeValue() {}
// SafeUint represents an integer that is not a sensitive value.
type SafeUint uint64
// SafeValue makes SafeUint a SafeValue.
func (SafeUint) SafeValue() {}
// SafeFloat represents a floating-point value that is not a sensitive value.
type SafeFloat float64
// SafeValue makes SafeFloat a SafeValue.
func (SafeFloat) SafeValue() {}
// SafeRune aliases rune. See the explanation for SafeString.
type SafeRune rune
// SafeValue makes SafeRune a SafeValue.
func (SafeRune) SafeValue() {}
// SafeValue is a marker interface to be implemented by types that
// alias base Go types and whose natural representation via Printf is
// always safe for reporting.
//
// This is recognized by the SafePrinter interface as an alternative
// to SafeFormatter.
//
// It is provided to decorate "leaf" Go types, such as aliases to int.
//
// Typically, a linter enforces that a type can only implement this
// interface if it aliases a base go type. More complex types should
// implement SafeFormatter instead.
//
// It is advised to build an automatic process during builds to
// collect all the types that implement this interface, as well as all
// uses of this type, and produce a report. Changes to this report
// should receive maximal amount of scrutiny during code reviews.
type SafeValue interface {
SafeValue()
}
// SafeMessager is an alternative to SafeFormatter used in previous
// versions of CockroachDB.
// NB: this interface is obsolete. Use SafeFormatter instead.
// TODO(knz): Remove this.
type SafeMessager = interface {
SafeMessage() string
}

View File

@@ -0,0 +1,333 @@
// 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 buffer
import (
origFmt "fmt"
"unicode/utf8"
"unsafe"
"github.com/cockroachdb/redact/internal/escape"
m "github.com/cockroachdb/redact/internal/markers"
)
// Buffer is a variable-sized buffer of bytes with a
// Write method. Writes are escaped in different ways
// depending on the output mode.
type Buffer struct {
buf []byte
validUntil int // exclusive upper bound of data that's already validated
mode OutputMode
markerOpen bool
}
// OutputMode determines how writes are processed in the Buffer.
type OutputMode int
const (
// UnsafeEscaped says that written data is unsafe for reporting,
// so it should be enclosed in redaction markers,
// and may contain redaction markers itself, and so it should be escaped.
UnsafeEscaped OutputMode = iota
// SafeEscaped says that written data is safe for reporting
// (should not be enclosed in redaction markers) but may itself
// contain redaction markers, and so it should be escaped.
SafeEscaped
// SafeRaw says that written data is safe for reporting
// (should not be enclosed in redaction markers) and is
// guaranteed not to contain redaction markers, so it
// needs not be escaped.
SafeRaw
// PreRedactable says that written data already contains a mixed
// of safe and unsafe information with suitably placed redaction markers,
// and so should be inlined.
PreRedactable = SafeRaw
)
// RedactableBytes returns the bytes in the buffer.
func (b Buffer) RedactableBytes() m.RedactableBytes {
// NB: we're dependent on the fact this is a copy of the original
// buffer. The finalize() method should not be called
// in a conceputally read-only accessor like RedactableBytes().
b.finalize()
return m.RedactableBytes(b.buf)
}
// RedactableString returns the bytes in the buffer.
func (b Buffer) RedactableString() m.RedactableString {
// NB: we're dependent on the fact this is a copy of the original
// buffer. The finalize() method should not be called
// in a conceputally read-only accessor like RedactableString().
b.finalize()
return m.RedactableString(b.buf)
}
// String implemens fmt.Stringer.
func (b Buffer) String() string {
// NB: we're dependent on the fact this is a copy of the original
// buffer. The finalize() method should not be called
// in a conceputally read-only accessor like String().
b.finalize()
return m.RedactableString(b.buf).StripMarkers()
}
// TakeRedactableBytes returns the buffer
// contents and reinitializes the buffer. This saves
// a memory allocation compared to RedactableBytes().
func (b *Buffer) TakeRedactableBytes() m.RedactableBytes {
b.finalize()
r := b.buf
b.buf = nil
b.validUntil = 0
b.mode = UnsafeEscaped
return m.RedactableBytes(r)
}
// TakeRedactableString returns the buffer
// contents and reinitializes the buffer. This saves
// a memory allocation compared to RedactableString().
func (b *Buffer) TakeRedactableString() m.RedactableString {
if b == nil {
// Special case, useful in debugging.
return "<nil>"
}
b.finalize()
r := *(*m.RedactableString)(unsafe.Pointer(&b.buf))
b.buf = nil
b.validUntil = 0
b.mode = UnsafeEscaped
return r
}
// Len returns the number of bytes in the buffer.
func (b *Buffer) Len() int {
copy := *b
copy.finalize()
return len(copy.buf)
}
// Cap returns the capacity of the buffer's underlying byte slice,
// that is, the total space allocated for the buffer's data.
func (b *Buffer) Cap() int {
return cap(b.buf)
}
// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.
func (b *Buffer) Write(p []byte) (n int, err error) {
b.startWrite()
m, ok := b.tryGrowByReslice(len(p))
if !ok {
m = b.grow(len(p))
}
return copy(b.buf[m:], p), nil
}
// WriteString appends the contents of s to the buffer, growing the buffer as
// needed. The return value n is the length of s; err is always nil. If the
// buffer becomes too large, WriteString will panic with ErrTooLarge.
func (b *Buffer) WriteString(s string) (n int, err error) {
b.startWrite()
m, ok := b.tryGrowByReslice(len(s))
if !ok {
m = b.grow(len(s))
}
return copy(b.buf[m:], s), nil
}
// WriteByte emits a single byte.
func (b *Buffer) WriteByte(s byte) error {
b.startWrite()
if b.mode == UnsafeEscaped &&
(s >= utf8.RuneSelf ||
s == m.StartS[0] || s == m.EndS[0]) {
// Unsafe byte. Escape it.
_, err := b.WriteString(m.EscapeMarkS)
return err
}
m, ok := b.tryGrowByReslice(1)
if !ok {
m = b.grow(1)
}
b.buf[m] = s
return nil
}
// WriteRune emits a single rune.
func (b *Buffer) WriteRune(s rune) error {
b.startWrite()
l := utf8.RuneLen(s)
m, ok := b.tryGrowByReslice(l)
if !ok {
m = b.grow(l)
}
_ = utf8.EncodeRune(b.buf[m:], s)
return nil
}
// finalize ensures that all the buffer is properly
// marked.
func (b *Buffer) finalize() {
if b.mode == SafeRaw {
b.validUntil = len(b.buf)
} else {
b.escapeToEnd(b.mode == UnsafeEscaped /* breakNewLines */)
}
if b.markerOpen {
b.endRedactable()
b.validUntil = len(b.buf)
}
}
// endRedactable adds the closing redaction marker.
func (b *Buffer) endRedactable() {
if len(b.buf) == 0 {
return
}
p, ok := b.tryGrowByReslice(len(m.EndS))
if !ok {
p = b.grow(len(m.EndS))
}
copy(b.buf[p:], m.EndS)
b.markerOpen = false
}
func (b *Buffer) startWrite() {
if b.mode == UnsafeEscaped && !b.markerOpen {
b.startRedactable()
b.validUntil = len(b.buf)
}
}
// endRedactable adds the closing redaction marker.
func (b *Buffer) startRedactable() {
p, ok := b.tryGrowByReslice(len(m.StartS))
if !ok {
p = b.grow(len(m.StartS))
}
copy(b.buf[p:], m.StartS)
b.markerOpen = true
}
// escapeToEnd escapes occurrences of redaction markers in
// b.buf[b.validUntil:] and advances b.validUntil until the end.
func (b *Buffer) escapeToEnd(breakNewLines bool) {
b.buf = escape.InternalEscapeBytes(b.buf, b.validUntil, breakNewLines, breakNewLines)
b.validUntil = len(b.buf)
}
// GetMode retrieves the output mode.
func (b *Buffer) GetMode() OutputMode {
return b.mode
}
// SetMode changes the output mode.
func (b *Buffer) SetMode(newMode OutputMode) {
if b.mode == newMode {
// noop
return
}
if b.mode == UnsafeEscaped || b.mode == SafeEscaped {
b.escapeToEnd(b.mode == UnsafeEscaped /* breakNewLines */)
}
if b.markerOpen {
b.endRedactable()
}
b.validUntil = len(b.buf)
b.mode = newMode
}
// Reset resets the buffer to be empty,
// but it retains the underlying storage for use by future writes.
// It also resets the output mode to UnsafeEscaped.
func (b *Buffer) Reset() {
b.buf = b.buf[:0]
b.validUntil = 0
b.mode = UnsafeEscaped
b.markerOpen = false
}
// tryGrowByReslice is a inlineable version of grow for the fast-case where the
// internal buffer only needs to be resliced.
// It returns the index where bytes should be written and whether it succeeded.
func (b *Buffer) tryGrowByReslice(n int) (int, bool) {
if l := len(b.buf); n <= cap(b.buf)-l {
b.buf = b.buf[:l+n]
return l, true
}
return 0, false
}
// grow grows the buffer to guarantee space for n more bytes.
// It returns the index where bytes should be written.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) grow(n int) int {
m := len(b.buf)
// Try to grow by means of a reslice.
if i, ok := b.tryGrowByReslice(n); ok {
return i
}
if b.buf == nil && n <= smallBufferSize {
b.buf = make([]byte, n, smallBufferSize)
return 0
}
c := cap(b.buf)
const maxInt = int(^uint(0) >> 1)
if n <= c/2-m {
// let capacity get twice as large.
} else if c > maxInt-c-n {
panic(ErrTooLarge)
} else {
// Not enough space anywhere, we need to allocate.
buf := makeSlice(2*c + n)
copy(buf, b.buf)
b.buf = buf
}
b.buf = b.buf[:m+n]
return m
}
// Grow grows the buffer's capacity, if necessary, to guarantee space for
// another n bytes. After Grow(n), at least n bytes can be written to the
// buffer without another allocation.
// If n is negative, Grow will panic.
// If the buffer can't grow it will panic with ErrTooLarge.
func (b *Buffer) Grow(n int) {
if n < 0 {
panic(origFmt.Errorf("redact.Buffer.Grow: negative count"))
}
m := b.grow(n)
b.buf = b.buf[:m]
}
// makeSlice allocates a slice of size n. If the allocation fails, it panics
// with ErrTooLarge.
func makeSlice(n int) []byte {
// If the make fails, give a known error.
defer func() {
if recover() != nil {
panic(ErrTooLarge)
}
}()
return make([]byte, n)
}
// ErrTooLarge is passed to panic if memory cannot be allocated to
// store data in a buffer.
var ErrTooLarge = origFmt.Errorf("redact.Buffer: too large")
// smallBufferSize is an initial allocation minimal capacity.
const smallBufferSize = 64

View File

@@ -0,0 +1,129 @@
// Copyright 2020 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 escape
import (
"bytes"
"unicode/utf8"
m "github.com/cockroachdb/redact/internal/markers"
)
// InternalEscapeBytes escapes redaction markers in the provided buf
// starting at the location startLoc.
// The bytes before startLoc are considered safe (already escaped).
//
// If breakNewLines is set, a closing redaction marker
// is placed before sequences of one or more newline characters,
// and an open redaction marker is placed afterwards.
func InternalEscapeBytes(b []byte, startLoc int, breakNewLines, strip bool) (res []byte) {
// Note: we use len(...RedactableS) and not len(...RedactableBytes)
// because the ...S variant is a compile-time constant so this
// accelerates the loops below.
start, ls := m.StartBytes, len(m.StartS)
end, le := m.EndBytes, len(m.EndS)
escape := m.EscapeMarkBytes
// Trim final newlines/spaces, for convenience.
if strip {
end := len(b)
for i := end - 1; i >= startLoc; i-- {
if b[i] == '\n' || b[i] == ' ' {
end = i
} else {
break
}
}
b = b[:end]
}
// res is the output slice. In the common case where there is
// nothing to escape, the input slice is returned directly
// and no allocation takes place.
res = b
// copied is true if and only if `res` is a copy of `b`. It only
// turns to true if the loop below finds something to escape.
copied := false
// k is the index in b up to (and excluding) the byte which we've
// already copied into res (if copied=true).
k := 0
for i := startLoc; i < len(b); i++ {
if breakNewLines && b[i] == '\n' {
if !copied {
// We only allocate an output slice when we know we definitely
// need it.
res = make([]byte, 0, len(b))
copied = true
}
res = append(res, b[k:i]...)
res = append(res, end...)
// Advance to the last newline character. We want to forward
// them all in a single call to doWrite, for performance.
lastNewLine := i
for lastNewLine < len(b) && b[lastNewLine] == '\n' {
lastNewLine++
}
res = append(res, b[i:lastNewLine]...)
res = append(res, start...)
k = lastNewLine
i = lastNewLine - 1
} else
// Ensure that occurrences of the delimiter inside the string get
// escaped.
// Reminder: ls and le are likely greater than 1, as we are scanning
// utf-8 encoded delimiters (the utf-8 encoding is multibyte).
if i+ls <= len(b) && bytes.Equal(b[i:i+ls], start) {
if !copied {
// We only allocate an output slice when we know we definitely
// need it.
res = make([]byte, 0, len(b)+len(escape))
copied = true
}
res = append(res, b[k:i]...)
res = append(res, escape...)
// Advance the counters by the length (in bytes) of the delimiter.
k = i + ls
i += ls - 1 /* -1 because we have i++ at the end of every iteration */
} else if i+le <= len(b) && bytes.Equal(b[i:i+le], end) {
if !copied {
// See the comment above about res allocation.
res = make([]byte, 0, len(b)+len(escape))
copied = true
}
res = append(res, b[k:i]...)
res = append(res, escape...)
// Advance the counters by the length (in bytes) of the delimiter.
k = i + le
i += le - 1 /* -1 because we have i++ at the end of every iteration */
}
}
// If the string terminates with an invalid utf-8 sequence, we
// want to avoid a run-in with a subsequent redaction marker.
if r, s := utf8.DecodeLastRune(b); s == 1 && r == utf8.RuneError {
if !copied {
// See the comment above about res allocation.
res = make([]byte, 0, len(b)+len(escape))
copied = true
}
res = append(res, b[k:]...)
res = append(res, escape...)
k = len(b)
}
if copied {
res = append(res, b[k:]...)
}
return
}

View File

@@ -12,7 +12,7 @@
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package redact
package fmtforward
import (
"fmt"
@@ -21,7 +21,10 @@ import (
"strings"
)
func reproducePrintf(w io.Writer, s fmt.State, verb rune, arg interface{}) {
// ReproducePrintf calls fmt.Print/fmt.Printf on the argument
// based on the details present in the fmt.State and
// writes to the io.Writer.
func ReproducePrintf(w io.Writer, s fmt.State, verb rune, arg interface{}) {
justV, revFmt := MakeFormat(s, verb)
if justV {
// Common case, avoids generating then parsing the format again.
@@ -31,8 +34,7 @@ func reproducePrintf(w io.Writer, s fmt.State, verb rune, arg interface{}) {
}
}
// MakeFormat is a helper for use by implementations of the
// SafeFormatter interface. It reproduces the format currently active
// MakeFormat reproduces the format currently active
// in fmt.State and verb. This is provided because Go's standard
// fmt.State does not make the original format string available to us.
//

View File

@@ -1,83 +0,0 @@
// Copyright 2020 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 fmt
// printArg overrides the original implementation by redirecting the
// call to a substitute function in the printer, if available.
func (p *pp) printArg(a interface{}, verb rune) {
if p.printArgSubstituteFn != nil {
p.substituteState = p.printArgSubstituteFn(p, a, verb)
} else {
p.printArgOrig(a, verb)
}
}
// InternalPrinter exposes pp to the redact package.
type InternalPrinter = pp
// InternalBuffer exposes buffer to the redact package.
type InternalBuffer = buffer
// NewInternalPrinter exposes pp allocation to the redact package.
func NewInternalPrinter() *InternalPrinter { return newPrinter() }
// SetHook connects an outer printer to the inner printer.
func SetHook(
p *InternalPrinter, fn func(p *InternalPrinter, arg interface{}, verb rune) (newState int),
) {
p.printArgSubstituteFn = fn
}
// Free exposes pp deallocation to the redact package.
func Free(p *InternalPrinter) {
p.substituteState = 0
p.printArgSubstituteFn = nil
p.free()
}
// Buf exposes the string buffer to the redact package.
func Buf(p *InternalPrinter) []byte { return []byte(p.buf) }
// GetState exposes the state to the redact package.
func GetState(p *InternalPrinter) int {
return p.substituteState
}
// SetState exposes the string buffer to the redact package.
func SetState(p *InternalPrinter, b []byte) {
p.buf = buffer(b)
p.substituteState = len(p.buf)
}
// Append adds bytes to the buffer.
func Append(p *InternalPrinter, b []byte) { p.buf.write(b) }
// PrintArg exposes the printArgOrig() method to the redact package.
func PrintArg(p *InternalPrinter, a interface{}, verb rune) { p.printArgOrig(a, verb) }
// DoPrint exposes the doPrint() method to the redact package.
func DoPrint(p *InternalPrinter, a []interface{}) { p.doPrint(a) }
// DoPrintf exposes the doPrintf() method to the redact package.
func DoPrintf(p *InternalPrinter, format string, a []interface{}) { p.doPrintf(format, a) }
// SetCollectError enables wrapped error collection.
func SetCollectError(p *InternalPrinter) { p.wrapErrs = true }
// CollectingError exposes wrapped error collection.
func CollectingError(p *InternalPrinter) bool { return p.wrapErrs }
// WrappedError retrieves the wrapped error if found.
func WrappedError(p *InternalPrinter) error { return p.wrappedErr }

View File

@@ -0,0 +1,38 @@
// Copyright 2020 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 markers
import "regexp"
// Internal constants.
const (
Start = '‹'
StartS = string(Start)
End = '›'
EndS = string(End)
EscapeMark = '?'
EscapeMarkS = string(EscapeMark)
RedactedS = StartS + "×" + EndS
)
// Internal variables.
var (
StartBytes = []byte(StartS)
EndBytes = []byte(EndS)
EscapeMarkBytes = []byte(EscapeMarkS)
RedactedBytes = []byte(RedactedS)
ReStripSensitive = regexp.MustCompile(StartS + "[^" + StartS + EndS + "]*" + EndS)
ReStripMarkers = regexp.MustCompile("[" + StartS + EndS + "]")
)

View File

@@ -12,9 +12,9 @@
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package redact
package markers
import "bytes"
import i "github.com/cockroachdb/redact/interfaces"
// RedactableString is a string that contains a mix of safe and unsafe
// bits of data, but where it is known that unsafe bits are enclosed
@@ -30,13 +30,13 @@ type RedactableString string
// RedactableString. This returns an unsafe string where all safe and
// unsafe bits are mixed together.
func (s RedactableString) StripMarkers() string {
return reStripMarkers.ReplaceAllString(string(s), "")
return ReStripMarkers.ReplaceAllString(string(s), "")
}
// Redact replaces all occurrences of unsafe substrings by the
// “Redacted” marker, ‹×›. The result string is still safe.
func (s RedactableString) Redact() RedactableString {
return RedactableString(reStripSensitive.ReplaceAllString(string(s), redactedS))
return RedactableString(ReStripSensitive.ReplaceAllString(string(s), RedactedS))
}
// ToBytes converts the string to a byte slice.
@@ -45,7 +45,7 @@ func (s RedactableString) ToBytes() RedactableBytes {
}
// SafeFormat formats the redactable safely.
func (s RedactableString) SafeFormat(sp SafePrinter, _ rune) {
func (s RedactableString) SafeFormat(sp i.SafePrinter, _ rune) {
// As per annotateArgs() in markers_internal_print.go,
// we consider the redactable string not further formattable.
sp.Print(s)
@@ -62,13 +62,13 @@ type RedactableBytes []byte
// RedactableBytes. This returns an unsafe string where all safe and
// unsafe bits are mixed together.
func (s RedactableBytes) StripMarkers() []byte {
return reStripMarkers.ReplaceAll([]byte(s), nil)
return ReStripMarkers.ReplaceAll([]byte(s), nil)
}
// Redact replaces all occurrences of unsafe substrings by the
// “Redacted” marker, ‹×›.
func (s RedactableBytes) Redact() RedactableBytes {
return RedactableBytes(reStripSensitive.ReplaceAll(s, redactedBytes))
return RedactableBytes(ReStripSensitive.ReplaceAll(s, RedactedBytes))
}
// ToString converts the byte slice to a string.
@@ -77,33 +77,23 @@ func (s RedactableBytes) ToString() RedactableString {
}
// SafeFormat formats the redactable safely.
func (s RedactableBytes) SafeFormat(sp SafePrinter, _ rune) {
func (s RedactableBytes) SafeFormat(sp i.SafePrinter, _ rune) {
// As per annotateArgs() in markers_internal_print.go,
// we consider the redactable bytes not further formattable.
sp.Print(s)
}
// EscapeBytes escapes markers inside the given byte slice and encloses
// the entire byte slice between redaction markers.
func EscapeBytes(s []byte) RedactableBytes {
var buf bytes.Buffer
buf.Grow(len(s) + len(startRedactableS) + len(endRedactableS))
e := escapeWriter{w: &buf, enclose: true, strip: false}
_, _ = e.Write(s)
return RedactableBytes(buf.Bytes())
}
// StartMarker returns the start delimiter for an unsafe string.
func StartMarker() []byte { return startRedactableBytes }
func StartMarker() []byte { return []byte(StartS) }
// EndMarker returns the end delimiter for an unsafe string.
func EndMarker() []byte { return endRedactableBytes }
func EndMarker() []byte { return []byte(EndS) }
// RedactedMarker returns the special string used by Redact.
func RedactedMarker() []byte { return redactedBytes }
func RedactedMarker() []byte { return []byte(RedactedS) }
// EscapeMarkers escapes the special delimiters from the provided
// byte slice.
func EscapeMarkers(s []byte) []byte {
return reStripMarkers.ReplaceAll(s, escapeBytes)
return ReStripMarkers.ReplaceAll(s, EscapeMarkBytes)
}

View File

@@ -1,58 +0,0 @@
--- print.go.orig 2020-08-26 14:49:30.396184000 +0200
+++ print.go 2020-08-26 14:50:29.330829000 +0200
@@ -1,3 +1,6 @@
+// Code generated from print.go.orig. DO NOT EDIT
+// GENERATED FILE DO NOT EDIT
+//
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@@ -5,12 +8,16 @@
package fmt
import (
- "internal/fmtsort"
+ // CUSTOM: needed to avoid a type mismatch on Formatter.
+ origFmt "fmt"
"io"
"os"
"reflect"
"sync"
"unicode/utf8"
+
+ // CUSTOM: our own import since we can't use internal from go stdlib.
+ "github.com/cockroachdb/redact/internal/fmtsort"
)
// Strings for use with buffer.WriteString.
@@ -51,7 +58,8 @@
// The implementation of Format may call Sprint(f) or Fprint(f) etc.
// to generate its output.
type Formatter interface {
- Format(f State, c rune)
+ // CUSTOM: refer to the original type, not the one defined here.
+ Format(f origFmt.State, c rune)
}
// Stringer is implemented by any value that has a String method,
@@ -105,6 +113,10 @@
type pp struct {
buf buffer
+ // CUSTOM: hook fn for the redact package.
+ printArgSubstituteFn func(p *pp, a interface{}, verb rune) (newState int)
+ substituteState int
+
// arg holds the current item, as an interface{}.
arg interface{}
@@ -635,7 +647,8 @@
return false
}
-func (p *pp) printArg(arg interface{}, verb rune) {
+// CUSTOM: printArg() renamed to printArgOrig().
+func (p *pp) printArgOrig(arg interface{}, verb rune) {
p.arg = arg
p.value = reflect.Value{}

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.
// Package redact is a helper internal package that provides the
// Unsafe and Safe value wrappers. Its name is 'redact' even though a
// better name would be 'wrappers' so as to ensure that the type name
// of generated values contains the 'redact.' prefix.
package redact

View File

@@ -14,7 +14,12 @@
package redact
import "fmt"
import (
origFmt "fmt"
i "github.com/cockroachdb/redact/interfaces"
"github.com/cockroachdb/redact/internal/fmtforward"
)
// Unsafe turns any value that would otherwise be considered safe,
// into an unsafe value.
@@ -22,12 +27,20 @@ func Unsafe(a interface{}) interface{} {
return unsafeWrap{a}
}
// UnsafeWrap is the type of wrapper produced by Unsafe.
// This is exported only for use by the rfmt package.
// Client packages should not make assumptions about
// the concrete return type of Unsafe().
type UnsafeWrap = unsafeWrap
type unsafeWrap struct {
a interface{}
}
func (w unsafeWrap) Format(s fmt.State, verb rune) {
reproducePrintf(s, s, verb, w.a)
func (w unsafeWrap) GetValue() interface{} { return w.a }
func (w unsafeWrap) Format(s origFmt.State, verb rune) {
fmtforward.ReproducePrintf(s, s, verb, w.a)
}
// Safe turns any value into an object that is considered as safe by
@@ -37,27 +50,35 @@ func (w unsafeWrap) Format(s fmt.State, verb rune) {
// interfaces and conventions fail. Increased usage of this mechanism
// should be taken as a signal that a new abstraction is missing.
// The implementation is also slow.
func Safe(a interface{}) SafeValue {
func Safe(a interface{}) i.SafeValue {
return safeWrapper{a}
}
// SafeWrapper is the type of wrapper produced by Safe.
// This is exported only for use by the rfmt package.
// Client packages should not make assumptions about
// the concrete return type of Safe().
type SafeWrapper = safeWrapper
type safeWrapper struct {
a interface{}
}
var _ SafeValue = safeWrapper{}
var _ fmt.Formatter = safeWrapper{}
var _ SafeMessager = safeWrapper{}
var _ i.SafeValue = safeWrapper{}
var _ origFmt.Formatter = safeWrapper{}
var _ i.SafeMessager = safeWrapper{}
func (w safeWrapper) GetValue() interface{} { return w.a }
// SafeValue implements the SafeValue interface.
func (w safeWrapper) SafeValue() {}
// Format implements the fmt.Formatter interface.
func (w safeWrapper) Format(s fmt.State, verb rune) {
reproducePrintf(s, s, verb, w.a)
func (w safeWrapper) Format(s origFmt.State, verb rune) {
fmtforward.ReproducePrintf(s, s, verb, w.a)
}
// SafeMessage implements SafeMessager.
func (w safeWrapper) SafeMessage() string {
return fmt.Sprintf("%v", w.a)
return origFmt.Sprintf("%v", w.a)
}

View File

@@ -24,15 +24,6 @@ Overall, the original Go code is structured as follows:
characters from the format string, as well as certain *constant*
error strings (e.g. `%(BADPREC)`).
At this point **we make the following assumption**: there is no data
from the printed *arguments* that is emitted to `pp.buf` directly by a
`doPrint()` method. This is true of the implementation as of Go 1.14.
From this point, this package contains a *custom patch* onto the
default implementation: the calls to `p.printArg` performed by the
`doPrint()` methods are redirected to a function callback that is
injected by the `redact` package.
Refreshing the sources
======================

View File

@@ -1,10 +1,11 @@
// Code generated from the Go standard library. DO NOT EDIT
// GENERATED FILE DO NOT EDIT
//
// Copyright 2009 The Go 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 fmt
package rfmt
import (
"strconv"
@@ -67,25 +68,17 @@ func (f *fmt) writePadding(n int) {
if n <= 0 { // No padding bytes needed.
return
}
buf := *f.buf
oldLen := len(buf)
newLen := oldLen + n
// Make enough room for padding.
if newLen > cap(buf) {
buf = make(buffer, cap(buf)*2+n)
copy(buf, *f.buf)
}
f.buf.Grow(n)
// Decide which byte the padding should be filled with.
padByte := byte(' ')
if f.zero {
padByte = byte('0')
}
// Fill padding with padByte.
padding := buf[oldLen:newLen]
for i := range padding {
padding[i] = padByte
for i := 0; i < n; i++ {
f.buf.writeByte(padByte)
}
*f.buf = buf[:newLen]
}
// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus).
@@ -403,19 +396,20 @@ func (f *fmt) fmtSbx(s string, b []byte, digits string) {
f.writePadding(f.wid - width)
}
// Write the encoding directly into the output buffer.
buf := *f.buf
if f.sharp {
// Add leading 0x or 0X.
buf = append(buf, '0', digits[16])
f.buf.WriteByte('0')
f.buf.WriteByte(digits[16])
}
var c byte
for i := 0; i < length; i++ {
if f.space && i > 0 {
// Separate elements with a space.
buf = append(buf, ' ')
f.buf.WriteByte(' ')
if f.sharp {
// Add leading 0x or 0X for each element.
buf = append(buf, '0', digits[16])
f.buf.WriteByte('0')
f.buf.WriteByte(digits[16])
}
}
if b != nil {
@@ -424,9 +418,9 @@ func (f *fmt) fmtSbx(s string, b []byte, digits string) {
c = s[i] // Take a byte from the input string.
}
// Encode each byte as two hexadecimal digits.
buf = append(buf, digits[c>>4], digits[c&0xF])
f.buf.WriteByte(digits[c>>4])
f.buf.WriteByte(digits[c&0xF])
}
*f.buf = buf
// Handle padding to the right.
if f.widPresent && f.wid > width && f.minus {
f.writePadding(f.wid - width)

View File

@@ -0,0 +1,75 @@
--- format.go.orig 2021-06-18 16:45:51.065116000 +0200
+++ format.go 2021-06-18 17:30:58.628179000 +0200
@@ -1,3 +1,6 @@
+// Code generated from the Go standard library. DO NOT EDIT
+// GENERATED FILE DO NOT EDIT
+//
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@@ -65,25 +68,17 @@
if n <= 0 { // No padding bytes needed.
return
}
- buf := *f.buf
- oldLen := len(buf)
- newLen := oldLen + n
// Make enough room for padding.
- if newLen > cap(buf) {
- buf = make(buffer, cap(buf)*2+n)
- copy(buf, *f.buf)
- }
+ f.buf.Grow(n)
// Decide which byte the padding should be filled with.
padByte := byte(' ')
if f.zero {
padByte = byte('0')
}
// Fill padding with padByte.
- padding := buf[oldLen:newLen]
- for i := range padding {
- padding[i] = padByte
+ for i := 0; i < n; i++ {
+ f.buf.writeByte(padByte)
}
- *f.buf = buf[:newLen]
}
// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus).
@@ -401,19 +396,20 @@
f.writePadding(f.wid - width)
}
// Write the encoding directly into the output buffer.
- buf := *f.buf
if f.sharp {
// Add leading 0x or 0X.
- buf = append(buf, '0', digits[16])
+ f.buf.WriteByte('0')
+ f.buf.WriteByte(digits[16])
}
var c byte
for i := 0; i < length; i++ {
if f.space && i > 0 {
// Separate elements with a space.
- buf = append(buf, ' ')
+ f.buf.WriteByte(' ')
if f.sharp {
// Add leading 0x or 0X for each element.
- buf = append(buf, '0', digits[16])
+ f.buf.WriteByte('0')
+ f.buf.WriteByte(digits[16])
}
}
if b != nil {
@@ -422,9 +418,9 @@
c = s[i] // Take a byte from the input string.
}
// Encode each byte as two hexadecimal digits.
- buf = append(buf, digits[c>>4], digits[c&0xF])
+ f.buf.WriteByte(digits[c>>4])
+ f.buf.WriteByte(digits[c&0xF])
}
- *f.buf = buf
// Handle padding to the right.
if f.widPresent && f.wid > width && f.minus {
f.writePadding(f.wid - width)

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