Upgrade versions of vendored libs
This commit is contained in:
13
vendor/github.com/cockroachdb/errors/README.md
generated
vendored
13
vendor/github.com/cockroachdb/errors/README.md
generated
vendored
@@ -209,12 +209,12 @@ return errors.Wrap(foo(), "foo")
|
||||
- how to access the detail: `Error()`, regular Go formatting, Sentry Report.
|
||||
|
||||
- `WithDetail(error, string) error`, `WithDetailf(error, string, ...interface{}) error`, user-facing detail with contextual information.
|
||||
- **when to use: need to embark a message string to output when the error is presented to a human.**
|
||||
- **when to use: need to embark a message string to output when the error is presented to a developer.**
|
||||
- what it does: captures detail strings.
|
||||
- how to access the detail: `errors.GetAllDetails()`, `errors.FlattenDetails()` (all details are preserved), format with `%+v`. Not included in Sentry reports.
|
||||
|
||||
- `WithHint(error, string) error`, `WithHintf(error, string, ...interface{}) error`: user-facing detail with suggestion for action to take.
|
||||
- **when to use: need to embark a message string to output when the error is presented to a human.**
|
||||
- **when to use: need to embark a message string to output when the error is presented to an end user.**
|
||||
- what it does: captures hint strings.
|
||||
- how to access the detail: `errors.GetAllHints()`, `errors.FlattenHints()` (hints are de-duplicated), format with `%+v`. Not included in Sentry reports.
|
||||
|
||||
@@ -536,7 +536,8 @@ Example use:
|
||||
| `WrapWithDepthf` | `WithMessagef` + `WithStackDepth` |
|
||||
| `AssertionFailedWithDepthf` | `NewWithDepthf` + `WithAssertionFailure` |
|
||||
| `NewAssertionErrorWithWrappedErrf` | `HandledWithMessagef` (barrier) + `WrapWithDepthf` + `WithAssertionFailure` |
|
||||
|
||||
| `Join` | `JoinWithDepth` (see below) |
|
||||
| `JoinWithDepth` | multi-cause wrapper + `WithStackDepth` |
|
||||
## API (not constructing error objects)
|
||||
|
||||
The following is a summary of the non-constructor API functions, grouped by category.
|
||||
@@ -573,10 +574,16 @@ func RegisterLeafDecoder(typeName TypeKey, decoder LeafDecoder)
|
||||
func RegisterLeafEncoder(typeName TypeKey, encoder LeafEncoder)
|
||||
func RegisterWrapperDecoder(typeName TypeKey, decoder WrapperDecoder)
|
||||
func RegisterWrapperEncoder(typeName TypeKey, encoder WrapperEncoder)
|
||||
func RegisterWrapperEncoderWithMessageOverride (typeName TypeKey, encoder WrapperEncoderWithMessageOverride)
|
||||
func RegisterMultiCauseEncoder(theType TypeKey, encoder MultiCauseEncoder)
|
||||
func RegisterMultiCauseDecoder(theType TypeKey, decoder MultiCauseDecoder)
|
||||
type LeafEncoder = func(ctx context.Context, err error) (msg string, safeDetails []string, payload proto.Message)
|
||||
type LeafDecoder = func(ctx context.Context, msg string, safeDetails []string, payload proto.Message) error
|
||||
type WrapperEncoder = func(ctx context.Context, err error) (msgPrefix string, safeDetails []string, payload proto.Message)
|
||||
type WrapperEncoderWithMessageOverride = func(ctx context.Context, err error) (msgPrefix string, safeDetails []string, payload proto.Message, overrideError bool)
|
||||
type WrapperDecoder = func(ctx context.Context, cause error, msgPrefix string, safeDetails []string, payload proto.Message) error
|
||||
type MultiCauseEncoder = func(ctx context.Context, err error) (msg string, safeDetails []string, payload proto.Message)
|
||||
type MultiCauseDecoder = func(ctx context.Context, causes []error, msgPrefix string, safeDetails []string, payload proto.Message) error
|
||||
|
||||
// Registering package renames for custom error types.
|
||||
func RegisterTypeMigration(previousPkgPath, previousTypeName string, newType error)
|
||||
|
||||
2
vendor/github.com/cockroachdb/errors/contexttags/contexttags.go
generated
vendored
2
vendor/github.com/cockroachdb/errors/contexttags/contexttags.go
generated
vendored
@@ -25,7 +25,7 @@ import (
|
||||
// WithContextTags captures the k/v pairs stored in the context via the
|
||||
// `logtags` package and annotates them on the error.
|
||||
//
|
||||
// Only the stromg representation of values remains available. This is
|
||||
// Only the strong representation of values remains available. This is
|
||||
// because the library cannot guarantee that the underlying value is
|
||||
// preserved across the network. To avoid creating a stateful interface
|
||||
// (where the user code needs to know whether an error has traveled
|
||||
|
||||
2
vendor/github.com/cockroachdb/errors/contexttags_api.go
generated
vendored
2
vendor/github.com/cockroachdb/errors/contexttags_api.go
generated
vendored
@@ -24,7 +24,7 @@ import (
|
||||
// WithContextTags captures the k/v pairs stored in the context via the
|
||||
// `logtags` package and annotates them on the error.
|
||||
//
|
||||
// Only the stromg representation of values remains available. This is
|
||||
// Only the strong representation of values remains available. This is
|
||||
// because the library cannot guarantee that the underlying value is
|
||||
// preserved across the network. To avoid creating a stateful interface
|
||||
// (where the user code needs to know whether an error has traveled
|
||||
|
||||
52
vendor/github.com/cockroachdb/errors/errbase/decode.go
generated
vendored
52
vendor/github.com/cockroachdb/errors/errbase/decode.go
generated
vendored
@@ -57,6 +57,15 @@ func decodeLeaf(ctx context.Context, enc *errorspb.EncodedErrorLeaf) error {
|
||||
return genErr
|
||||
}
|
||||
// Decoding failed, we'll drop through to opaqueLeaf{} below.
|
||||
} else if decoder, ok := multiCauseDecoders[typeKey]; ok {
|
||||
causes := make([]error, len(enc.MultierrorCauses))
|
||||
for i, e := range enc.MultierrorCauses {
|
||||
causes[i] = DecodeError(ctx, *e)
|
||||
}
|
||||
genErr := decoder(ctx, causes, enc.Message, enc.Details.ReportablePayload, payload)
|
||||
if genErr != nil {
|
||||
return genErr
|
||||
}
|
||||
} else {
|
||||
// Shortcut for non-registered proto-encodable error types:
|
||||
// if it already implements `error`, it's good to go.
|
||||
@@ -66,6 +75,19 @@ func decodeLeaf(ctx context.Context, enc *errorspb.EncodedErrorLeaf) error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(enc.MultierrorCauses) > 0 {
|
||||
causes := make([]error, len(enc.MultierrorCauses))
|
||||
for i, e := range enc.MultierrorCauses {
|
||||
causes[i] = DecodeError(ctx, *e)
|
||||
}
|
||||
leaf := &opaqueLeafCauses{
|
||||
causes: causes,
|
||||
}
|
||||
leaf.msg = enc.Message
|
||||
leaf.details = enc.Details
|
||||
return leaf
|
||||
}
|
||||
|
||||
// No decoder and no error type: we'll keep what we received and
|
||||
// make it ready to re-encode exactly (if the error leaves over the
|
||||
// network again).
|
||||
@@ -97,7 +119,7 @@ func decodeWrapper(ctx context.Context, enc *errorspb.EncodedWrapper) error {
|
||||
typeKey := TypeKey(enc.Details.ErrorTypeMark.FamilyName)
|
||||
if decoder, ok := decoders[typeKey]; ok {
|
||||
// Yes, use it.
|
||||
genErr := decoder(ctx, cause, enc.MessagePrefix, enc.Details.ReportablePayload, payload)
|
||||
genErr := decoder(ctx, cause, enc.Message, enc.Details.ReportablePayload, payload)
|
||||
if genErr != nil {
|
||||
// Decoding succeeded. Use this.
|
||||
return genErr
|
||||
@@ -107,9 +129,10 @@ func decodeWrapper(ctx context.Context, enc *errorspb.EncodedWrapper) error {
|
||||
|
||||
// Otherwise, preserve all details about the original object.
|
||||
return &opaqueWrapper{
|
||||
cause: cause,
|
||||
prefix: enc.MessagePrefix,
|
||||
details: enc.Details,
|
||||
cause: cause,
|
||||
prefix: enc.Message,
|
||||
details: enc.Details,
|
||||
messageType: MessageType(enc.MessageType),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,3 +183,24 @@ type WrapperDecoder = func(ctx context.Context, cause error, msgPrefix string, s
|
||||
|
||||
// registry for RegisterWrapperType.
|
||||
var decoders = map[TypeKey]WrapperDecoder{}
|
||||
|
||||
// MultiCauseDecoder is to be provided (via RegisterMultiCauseDecoder
|
||||
// above) by additional multi-cause wrapper types not yet known by the
|
||||
// library. A nil return indicates that decoding was not successful.
|
||||
type MultiCauseDecoder = func(ctx context.Context, causes []error, msgPrefix string, safeDetails []string, payload proto.Message) error
|
||||
|
||||
// registry for RegisterMultiCauseDecoder.
|
||||
var multiCauseDecoders = map[TypeKey]MultiCauseDecoder{}
|
||||
|
||||
// RegisterMultiCauseDecoder can be used to register new multi-cause
|
||||
// wrapper types to the library. Registered wrappers will be decoded
|
||||
// using their own Go type when an error is decoded. Multi-cause
|
||||
// wrappers that have not been registered will be decoded using the
|
||||
// opaqueWrapper type.
|
||||
func RegisterMultiCauseDecoder(theType TypeKey, decoder MultiCauseDecoder) {
|
||||
if decoder == nil {
|
||||
delete(multiCauseDecoders, theType)
|
||||
} else {
|
||||
multiCauseDecoders[theType] = decoder
|
||||
}
|
||||
}
|
||||
|
||||
166
vendor/github.com/cockroachdb/errors/errbase/encode.go
generated
vendored
166
vendor/github.com/cockroachdb/errors/errbase/encode.go
generated
vendored
@@ -33,18 +33,24 @@ func EncodeError(ctx context.Context, err error) EncodedError {
|
||||
if cause := UnwrapOnce(err); cause != nil {
|
||||
return encodeWrapper(ctx, err, cause)
|
||||
}
|
||||
// Not a causer.
|
||||
return encodeLeaf(ctx, err)
|
||||
return encodeLeaf(ctx, err, UnwrapMulti(err))
|
||||
}
|
||||
|
||||
// encodeLeaf encodes a leaf error.
|
||||
func encodeLeaf(ctx context.Context, err error) EncodedError {
|
||||
// encodeLeaf encodes a leaf error. This function accepts a `causes`
|
||||
// argument because we encode multi-cause errors using the Leaf
|
||||
// protobuf. This was done to enable backwards compatibility when
|
||||
// introducing this functionality since the Wrapper type already has a
|
||||
// required single `cause` field.
|
||||
func encodeLeaf(ctx context.Context, err error, causes []error) EncodedError {
|
||||
var msg string
|
||||
var details errorspb.EncodedErrorDetails
|
||||
|
||||
if e, ok := err.(*opaqueLeaf); ok {
|
||||
msg = e.msg
|
||||
details = e.details
|
||||
} else if e, ok := err.(*opaqueLeafCauses); ok {
|
||||
msg = e.msg
|
||||
details = e.details
|
||||
} else {
|
||||
details.OriginalTypeName, details.ErrorTypeMark.FamilyName, details.ErrorTypeMark.Extension = getTypeDetails(err, false /*onlyFamily*/)
|
||||
|
||||
@@ -74,11 +80,21 @@ func encodeLeaf(ctx context.Context, err error) EncodedError {
|
||||
details.FullDetails = encodeAsAny(ctx, err, payload)
|
||||
}
|
||||
|
||||
var cs []*EncodedError
|
||||
if len(causes) > 0 {
|
||||
cs = make([]*EncodedError, len(causes))
|
||||
for i, ee := range causes {
|
||||
ee := EncodeError(ctx, ee)
|
||||
cs[i] = &ee
|
||||
}
|
||||
}
|
||||
|
||||
return EncodedError{
|
||||
Error: &errorspb.EncodedError_Leaf{
|
||||
Leaf: &errorspb.EncodedErrorLeaf{
|
||||
Message: msg,
|
||||
Details: details,
|
||||
Message: msg,
|
||||
Details: details,
|
||||
MultierrorCauses: cs,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -115,10 +131,18 @@ func encodeAsAny(ctx context.Context, err error, payload proto.Message) *types.A
|
||||
func encodeWrapper(ctx context.Context, err, cause error) EncodedError {
|
||||
var msg string
|
||||
var details errorspb.EncodedErrorDetails
|
||||
messageType := Prefix
|
||||
|
||||
if e, ok := err.(*opaqueWrapper); ok {
|
||||
// We delegate all knowledge of the error string
|
||||
// to the original encoder and do not try to re-engineer
|
||||
// the prefix out of the error. This helps maintain
|
||||
// backward compatibility with earlier versions of the
|
||||
// encoder which don't have any understanding of
|
||||
// error string ownership by the wrapper.
|
||||
msg = e.prefix
|
||||
details = e.details
|
||||
messageType = e.messageType
|
||||
} else {
|
||||
details.OriginalTypeName, details.ErrorTypeMark.FamilyName, details.ErrorTypeMark.Extension = getTypeDetails(err, false /*onlyFamily*/)
|
||||
|
||||
@@ -127,12 +151,12 @@ func encodeWrapper(ctx context.Context, err, cause error) EncodedError {
|
||||
// If we have a manually registered encoder, use that.
|
||||
typeKey := TypeKey(details.ErrorTypeMark.FamilyName)
|
||||
if enc, ok := encoders[typeKey]; ok {
|
||||
msg, details.ReportablePayload, payload = enc(ctx, err)
|
||||
msg, details.ReportablePayload, payload, messageType = enc(ctx, err)
|
||||
} else {
|
||||
// No encoder.
|
||||
// In that case, we'll try to compute a message prefix
|
||||
// manually.
|
||||
msg = extractPrefix(err, cause)
|
||||
msg, messageType = extractPrefix(err, cause)
|
||||
|
||||
// If there are known safe details, use them.
|
||||
if s, ok := err.(SafeDetailer); ok {
|
||||
@@ -148,9 +172,10 @@ func encodeWrapper(ctx context.Context, err, cause error) EncodedError {
|
||||
return EncodedError{
|
||||
Error: &errorspb.EncodedError_Wrapper{
|
||||
Wrapper: &errorspb.EncodedWrapper{
|
||||
Cause: EncodeError(ctx, cause),
|
||||
MessagePrefix: msg,
|
||||
Details: details,
|
||||
Cause: EncodeError(ctx, cause),
|
||||
Message: msg,
|
||||
Details: details,
|
||||
MessageType: errorspb.MessageType(messageType),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -158,21 +183,36 @@ func encodeWrapper(ctx context.Context, err, cause error) EncodedError {
|
||||
|
||||
// extractPrefix extracts the prefix from a wrapper's error message.
|
||||
// For example,
|
||||
// err := errors.New("bar")
|
||||
// err = errors.Wrap(err, "foo")
|
||||
// extractPrefix(err)
|
||||
//
|
||||
// err := errors.New("bar")
|
||||
// err = errors.Wrap(err, "foo")
|
||||
// extractPrefix(err)
|
||||
//
|
||||
// returns "foo".
|
||||
func extractPrefix(err, cause error) string {
|
||||
//
|
||||
// If a presumed wrapper does not have a message prefix, it is assumed
|
||||
// to override the entire error message and `extractPrefix` returns
|
||||
// the entire message and the boolean `true` to signify that the causes
|
||||
// should not be appended to it.
|
||||
func extractPrefix(err, cause error) (string, MessageType) {
|
||||
causeSuffix := cause.Error()
|
||||
errMsg := err.Error()
|
||||
|
||||
if strings.HasSuffix(errMsg, causeSuffix) {
|
||||
prefix := errMsg[:len(errMsg)-len(causeSuffix)]
|
||||
// If error msg matches exactly then this is a wrapper
|
||||
// with no message of its own.
|
||||
if len(prefix) == 0 {
|
||||
return "", Prefix
|
||||
}
|
||||
if strings.HasSuffix(prefix, ": ") {
|
||||
return prefix[:len(prefix)-2]
|
||||
return prefix[:len(prefix)-2], Prefix
|
||||
}
|
||||
}
|
||||
return ""
|
||||
// If we don't have the cause as a suffix, then we have
|
||||
// some other string as our error msg, preserve that and
|
||||
// mark as override
|
||||
return errMsg, FullMessage
|
||||
}
|
||||
|
||||
func getTypeDetails(
|
||||
@@ -183,6 +223,8 @@ func getTypeDetails(
|
||||
switch t := err.(type) {
|
||||
case *opaqueLeaf:
|
||||
return t.details.OriginalTypeName, t.details.ErrorTypeMark.FamilyName, t.details.ErrorTypeMark.Extension
|
||||
case *opaqueLeafCauses:
|
||||
return t.details.OriginalTypeName, t.details.ErrorTypeMark.FamilyName, t.details.ErrorTypeMark.Extension
|
||||
case *opaqueWrapper:
|
||||
return t.details.OriginalTypeName, t.details.ErrorTypeMark.FamilyName, t.details.ErrorTypeMark.Extension
|
||||
}
|
||||
@@ -286,6 +328,28 @@ type LeafEncoder = func(ctx context.Context, err error) (msg string, safeDetails
|
||||
// registry for RegisterLeafEncoder.
|
||||
var leafEncoders = map[TypeKey]LeafEncoder{}
|
||||
|
||||
// RegisterMultiCauseEncoder can be used to register new multi-cause
|
||||
// error types to the library. Registered types will be encoded using
|
||||
// their own Go type when an error is encoded. Multi-cause wrappers
|
||||
// that have not been registered will be encoded using the
|
||||
// opaqueWrapper type.
|
||||
func RegisterMultiCauseEncoder(theType TypeKey, encoder MultiCauseEncoder) {
|
||||
// This implementation is a simple wrapper around `LeafEncoder`
|
||||
// because we implemented multi-cause error wrapper encoding into a
|
||||
// `Leaf` instead of a `Wrapper` for smoother backwards
|
||||
// compatibility support. Exposing this detail to consumers of the
|
||||
// API is confusing and hence avoided. The causes of the error are
|
||||
// encoded separately regardless of this encoder's implementation.
|
||||
RegisterLeafEncoder(theType, encoder)
|
||||
}
|
||||
|
||||
// MultiCauseEncoder is to be provided (via RegisterMultiCauseEncoder
|
||||
// above) by additional multi-cause wrapper types not yet known to this
|
||||
// library. The encoder will automatically extract and encode the
|
||||
// causes of this error by calling `Unwrap()` and expecting a slice of
|
||||
// errors.
|
||||
type MultiCauseEncoder = func(ctx context.Context, err error) (msg string, safeDetails []string, payload proto.Message)
|
||||
|
||||
// RegisterWrapperEncoder can be used to register new wrapper types to
|
||||
// the library. Registered wrappers will be encoded using their own
|
||||
// Go type when an error is encoded. Wrappers that have not been
|
||||
@@ -295,6 +359,35 @@ var leafEncoders = map[TypeKey]LeafEncoder{}
|
||||
// or a different type, ensure that RegisterTypeMigration() was called
|
||||
// prior to RegisterWrapperEncoder().
|
||||
func RegisterWrapperEncoder(theType TypeKey, encoder WrapperEncoder) {
|
||||
RegisterWrapperEncoderWithMessageType(
|
||||
theType,
|
||||
func(ctx context.Context, err error) (
|
||||
msgPrefix string,
|
||||
safeDetails []string,
|
||||
payload proto.Message,
|
||||
messageType MessageType,
|
||||
) {
|
||||
prefix, details, payload := encoder(ctx, err)
|
||||
return prefix, details, payload, messageType
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterWrapperEncoderWithMessageType can be used to register
|
||||
// new wrapper types to the library. Registered wrappers will be
|
||||
// encoded using their own Go type when an error is encoded. Wrappers
|
||||
// that have not been registered will be encoded using the
|
||||
// opaqueWrapper type.
|
||||
//
|
||||
// This function differs from RegisterWrapperEncoder by allowing the
|
||||
// caller to explicitly decide whether the wrapper owns the entire
|
||||
// error message or not. Otherwise, the relationship is inferred.
|
||||
//
|
||||
// Note: if the error type has been migrated from a previous location
|
||||
// or a different type, ensure that RegisterTypeMigration() was called
|
||||
// prior to RegisterWrapperEncoder().
|
||||
func RegisterWrapperEncoderWithMessageType(
|
||||
theType TypeKey, encoder WrapperEncoderWithMessageType,
|
||||
) {
|
||||
if encoder == nil {
|
||||
delete(encoders, theType)
|
||||
} else {
|
||||
@@ -304,7 +397,42 @@ func RegisterWrapperEncoder(theType TypeKey, encoder WrapperEncoder) {
|
||||
|
||||
// WrapperEncoder is to be provided (via RegisterWrapperEncoder above)
|
||||
// by additional wrapper types not yet known to this library.
|
||||
type WrapperEncoder = func(ctx context.Context, err error) (msgPrefix string, safeDetails []string, payload proto.Message)
|
||||
type WrapperEncoder func(ctx context.Context, err error) (
|
||||
msgPrefix string,
|
||||
safeDetails []string,
|
||||
payload proto.Message,
|
||||
)
|
||||
|
||||
// MessageType is used to encode information about an error message
|
||||
// within a wrapper error type. This information is used to affect
|
||||
// display logic.
|
||||
type MessageType errorspb.MessageType
|
||||
|
||||
// Values below should match the ones in errorspb.MessageType for
|
||||
// direct conversion.
|
||||
const (
|
||||
// Prefix denotes an error message that should be prepended to the
|
||||
// message of its cause.
|
||||
Prefix MessageType = MessageType(errorspb.MessageType_PREFIX)
|
||||
// FullMessage denotes an error message that contains the text of its
|
||||
// causes and can be displayed standalone.
|
||||
FullMessage = MessageType(errorspb.MessageType_FULL_MESSAGE)
|
||||
)
|
||||
|
||||
// WrapperEncoderWithMessageType is to be provided (via
|
||||
// RegisterWrapperEncoderWithMessageType above) by additional wrapper
|
||||
// types not yet known to this library. This encoder returns an
|
||||
// additional enum which indicates whether the wrapper owns the error
|
||||
// message completely instead of simply being a prefix with the error
|
||||
// message of its causes appended to it. This information is encoded
|
||||
// along with the prefix in order to provide context during error
|
||||
// display.
|
||||
type WrapperEncoderWithMessageType func(ctx context.Context, err error) (
|
||||
msgPrefix string,
|
||||
safeDetails []string,
|
||||
payload proto.Message,
|
||||
messageType MessageType,
|
||||
)
|
||||
|
||||
// registry for RegisterWrapperType.
|
||||
var encoders = map[TypeKey]WrapperEncoder{}
|
||||
var encoders = map[TypeKey]WrapperEncoderWithMessageType{}
|
||||
|
||||
153
vendor/github.com/cockroachdb/errors/errbase/format_error.go
generated
vendored
153
vendor/github.com/cockroachdb/errors/errbase/format_error.go
generated
vendored
@@ -102,7 +102,13 @@ func formatErrorInternal(err error, s fmt.State, verb rune, redactableOutput boo
|
||||
// to enable stack trace de-duplication. This requires a
|
||||
// post-order traversal. Since we have a linked list, the best we
|
||||
// can do is a recursion.
|
||||
p.formatRecursive(err, true /* isOutermost */, true /* withDetail */)
|
||||
p.formatRecursive(
|
||||
err,
|
||||
true, /* isOutermost */
|
||||
true, /* withDetail */
|
||||
false, /* withDepth */
|
||||
0, /* depth */
|
||||
)
|
||||
|
||||
// We now have all the data, we can render the result.
|
||||
p.formatEntries(err)
|
||||
@@ -146,7 +152,13 @@ func formatErrorInternal(err error, s fmt.State, verb rune, redactableOutput boo
|
||||
// by calling FormatError(), in which case we'd get an infinite
|
||||
// recursion. So we have no choice but to peel the data
|
||||
// and then assemble the pieces ourselves.
|
||||
p.formatRecursive(err, true /* isOutermost */, false /* withDetail */)
|
||||
p.formatRecursive(
|
||||
err,
|
||||
true, /* isOutermost */
|
||||
false, /* withDetail */
|
||||
false, /* withDepth */
|
||||
0, /* depth */
|
||||
)
|
||||
p.formatSingleLineOutput()
|
||||
p.finishDisplay(verb)
|
||||
|
||||
@@ -195,7 +207,19 @@ func (s *state) formatEntries(err error) {
|
||||
// Wraps: (N) <details>
|
||||
//
|
||||
for i, j := len(s.entries)-2, 2; i >= 0; i, j = i-1, j+1 {
|
||||
fmt.Fprintf(&s.finalBuf, "\nWraps: (%d)", j)
|
||||
s.finalBuf.WriteByte('\n')
|
||||
// Extra indentation starts at depth==2 because the direct
|
||||
// children of the root error area already printed on separate
|
||||
// newlines.
|
||||
for m := 0; m < s.entries[i].depth-1; m += 1 {
|
||||
if m == s.entries[i].depth-2 {
|
||||
s.finalBuf.WriteString("└─ ")
|
||||
} else {
|
||||
s.finalBuf.WriteByte(' ')
|
||||
s.finalBuf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&s.finalBuf, "Wraps: (%d)", j)
|
||||
entry := s.entries[i]
|
||||
s.printEntry(entry)
|
||||
}
|
||||
@@ -278,8 +302,9 @@ func (s *state) printEntry(entry formatEntry) {
|
||||
//
|
||||
// This function is used both when FormatError() is called indirectly
|
||||
// from .Error(), e.g. in:
|
||||
// (e *myType) Error() { return fmt.Sprintf("%v", e) }
|
||||
// (e *myType) Format(s fmt.State, verb rune) { errors.FormatError(s, verb, e) }
|
||||
//
|
||||
// (e *myType) Error() { return fmt.Sprintf("%v", e) }
|
||||
// (e *myType) Format(s fmt.State, verb rune) { errors.FormatError(s, verb, e) }
|
||||
//
|
||||
// and also to print the first line in the output of a %+v format.
|
||||
//
|
||||
@@ -329,13 +354,35 @@ func (s *state) formatSingleLineOutput() {
|
||||
// s.finalBuf is untouched. The conversion of s.entries
|
||||
// to s.finalBuf is done by formatSingleLineOutput() and/or
|
||||
// formatEntries().
|
||||
func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
|
||||
//
|
||||
// `withDepth` and `depth` are used to tag subtrees of multi-cause
|
||||
// errors for added indentation during printing. Once a multi-cause
|
||||
// error is encountered, all subsequent calls with set `withDepth` to
|
||||
// true, and increment `depth` during recursion. This information is
|
||||
// persisted into the generated entries and used later to display the
|
||||
// error with increased indentation based in the depth.
|
||||
func (s *state) formatRecursive(err error, isOutermost, withDetail, withDepth bool, depth int) int {
|
||||
cause := UnwrapOnce(err)
|
||||
numChildren := 0
|
||||
if cause != nil {
|
||||
// Recurse first.
|
||||
s.formatRecursive(cause, false /*isOutermost*/, withDetail)
|
||||
// Recurse first, which populates entries list starting from innermost
|
||||
// entry. If we've previously seen a multi-cause wrapper, `withDepth`
|
||||
// will be true, and we'll record the depth below ensuring that extra
|
||||
// indentation is applied to this inner cause during printing.
|
||||
// Otherwise, we maintain "straight" vertical formatting by keeping the
|
||||
// parent callers `withDepth` value of `false` by default.
|
||||
numChildren += s.formatRecursive(cause, false, withDetail, withDepth, depth+1)
|
||||
}
|
||||
|
||||
causes := UnwrapMulti(err)
|
||||
for _, c := range causes {
|
||||
// Override `withDepth` to true for all child entries ensuring they have
|
||||
// indentation applied during formatting to distinguish them from
|
||||
// parents.
|
||||
numChildren += s.formatRecursive(c, false, withDetail, true, depth+1)
|
||||
}
|
||||
// inserted := len(s.entries) - 1 - startChildren
|
||||
|
||||
// Reinitialize the state for this stage of wrapping.
|
||||
s.wantDetail = withDetail
|
||||
s.needSpace = false
|
||||
@@ -354,11 +401,11 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
|
||||
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
|
||||
}
|
||||
// The error wants to elide the short messages from inner causes.
|
||||
// Read backwards through list of entries up to the number of new
|
||||
// entries created "under" this one amount and mark `elideShort`
|
||||
// true.
|
||||
s.elideShortChildren(numChildren)
|
||||
}
|
||||
|
||||
case Formatter:
|
||||
@@ -366,9 +413,7 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
|
||||
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
|
||||
}
|
||||
s.elideShortChildren(numChildren)
|
||||
}
|
||||
|
||||
case fmt.Formatter:
|
||||
@@ -389,7 +434,11 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
|
||||
s.lastStack = st.StackTrace()
|
||||
}
|
||||
} else {
|
||||
s.formatSimple(err, cause)
|
||||
if elideCauseMsg := s.formatSimple(err, cause); elideCauseMsg {
|
||||
// The error wants to elide the short messages from inner
|
||||
// causes. Do it.
|
||||
s.elideShortChildren(numChildren)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -411,9 +460,7 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
|
||||
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
|
||||
}
|
||||
s.elideShortChildren(numChildren)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -422,12 +469,21 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
|
||||
// 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.
|
||||
s.formatSimple(err, cause)
|
||||
elideChildren := s.formatSimple(err, cause)
|
||||
// always elideChildren when dealing with multi-cause errors.
|
||||
if len(causes) > 0 {
|
||||
elideChildren = true
|
||||
}
|
||||
if elideChildren {
|
||||
// The error wants to elide the short messages from inner
|
||||
// causes. Do it.
|
||||
s.elideShortChildren(numChildren)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the result.
|
||||
entry := s.collectEntry(err, bufIsRedactable)
|
||||
entry := s.collectEntry(err, bufIsRedactable, withDepth, depth)
|
||||
|
||||
// If there's an embedded stack trace, also collect it.
|
||||
// This will get either a stack from pkg/errors, or ours.
|
||||
@@ -441,9 +497,22 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
|
||||
// Remember the entry for later rendering.
|
||||
s.entries = append(s.entries, entry)
|
||||
s.buf = bytes.Buffer{}
|
||||
|
||||
return numChildren + 1
|
||||
}
|
||||
|
||||
func (s *state) collectEntry(err error, bufIsRedactable bool) formatEntry {
|
||||
// elideShortChildren takes a number of entries to set `elideShort` to
|
||||
// false. The reason a number of entries is needed is that we may be
|
||||
// eliding a subtree of causes in the case of a multi-cause error. In
|
||||
// the multi-cause case, we need to know how many of the prior errors
|
||||
// in the list of entries is a child of this subtree.
|
||||
func (s *state) elideShortChildren(newEntries int) {
|
||||
for i := 0; i < newEntries; i++ {
|
||||
s.entries[len(s.entries)-1-i].elideShort = true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) collectEntry(err error, bufIsRedactable bool, withDepth bool, depth int) formatEntry {
|
||||
entry := formatEntry{err: err}
|
||||
if s.wantDetail {
|
||||
// The buffer has been populated as a result of formatting with
|
||||
@@ -480,6 +549,10 @@ func (s *state) collectEntry(err error, bufIsRedactable bool) formatEntry {
|
||||
}
|
||||
}
|
||||
|
||||
if withDepth {
|
||||
entry.depth = depth
|
||||
}
|
||||
|
||||
return entry
|
||||
}
|
||||
|
||||
@@ -500,16 +573,23 @@ func RegisterSpecialCasePrinter(fn safeErrorPrinterFn) {
|
||||
// formatSimple performs a best effort at extracting the details at a
|
||||
// given level of wrapping when the error object does not implement
|
||||
// the Formatter interface.
|
||||
func (s *state) formatSimple(err, cause error) {
|
||||
// Returns true if we want to elide errors from causal chain.
|
||||
func (s *state) formatSimple(err, cause error) bool {
|
||||
var pref string
|
||||
elideCauses := false
|
||||
if cause != nil {
|
||||
pref = extractPrefix(err, cause)
|
||||
var messageType MessageType
|
||||
pref, messageType = extractPrefix(err, cause)
|
||||
if messageType == FullMessage {
|
||||
elideCauses = true
|
||||
}
|
||||
} else {
|
||||
pref = err.Error()
|
||||
}
|
||||
if len(pref) > 0 {
|
||||
s.Write([]byte(pref))
|
||||
}
|
||||
return elideCauses
|
||||
}
|
||||
|
||||
// finishDisplay renders s.finalBuf into s.State.
|
||||
@@ -568,7 +648,7 @@ type state struct {
|
||||
|
||||
// entries collect the result of formatRecursive(). They are
|
||||
// consumed by formatSingleLineOutput() and formatEntries() to
|
||||
// procude the contents of finalBuf.
|
||||
// produce the contents of finalBuf.
|
||||
entries []formatEntry
|
||||
|
||||
// buf collects the details of the current error object at a given
|
||||
@@ -690,6 +770,11 @@ type formatEntry struct {
|
||||
// truncated to avoid duplication of entries. This is used to
|
||||
// display a truncation indicator during verbose rendering.
|
||||
elidedStackTrace bool
|
||||
|
||||
// depth, if positive, represents a nesting depth of this error as
|
||||
// a causer of others. This is used with verbose printing to
|
||||
// illustrate the nesting depth for multi-cause error wrappers.
|
||||
depth int
|
||||
}
|
||||
|
||||
// String is used for debugging only.
|
||||
@@ -711,6 +796,12 @@ func (s *state) Write(b []byte) (n int, err error) {
|
||||
|
||||
for i, c := range b {
|
||||
if c == '\n' {
|
||||
//if s.needNewline > 0 {
|
||||
// for i := 0; i < s.needNewline-1; i++ {
|
||||
// s.buf.Write(detailSep[:len(sep)-1])
|
||||
// }
|
||||
// s.needNewline = 0
|
||||
//}
|
||||
// Flush all the bytes seen so far.
|
||||
s.buf.Write(b[k:i])
|
||||
// Don't print the newline itself; instead, prepare the state so
|
||||
@@ -740,6 +831,11 @@ func (s *state) Write(b []byte) (n int, err error) {
|
||||
s.notEmpty = true
|
||||
}
|
||||
}
|
||||
//if s.needNewline > 0 {
|
||||
// for i := 0; i < s.needNewline-1; i++ {
|
||||
// s.buf.Write(detailSep[:len(sep)-1])
|
||||
// }
|
||||
//}
|
||||
s.buf.Write(b[k:])
|
||||
return len(b), nil
|
||||
}
|
||||
@@ -766,6 +862,9 @@ func (p *state) switchOver() {
|
||||
p.buf = bytes.Buffer{}
|
||||
p.notEmpty = false
|
||||
p.hasDetail = true
|
||||
|
||||
// One of the newlines is accounted for in the switch over.
|
||||
// p.needNewline -= 1
|
||||
}
|
||||
|
||||
func (s *printer) Detail() bool {
|
||||
@@ -829,7 +928,7 @@ func (s *safePrinter) enhanceArgs(args []interface{}) {
|
||||
args[i] = redact.Safe(thisStack)
|
||||
lastSeen = st
|
||||
}
|
||||
// In contrast with (*printer).enhanceArgs(), we dont use a
|
||||
// In contrast with (*printer).enhanceArgs(), we don't use a
|
||||
// special case for `error` here, because the redact package
|
||||
// already helps us recursing into a safe print for
|
||||
// error objects.
|
||||
|
||||
36
vendor/github.com/cockroachdb/errors/errbase/format_simple.go
generated
vendored
36
vendor/github.com/cockroachdb/errors/errbase/format_simple.go
generated
vendored
@@ -1,36 +0,0 @@
|
||||
// Copyright 2019 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 errbase
|
||||
|
||||
import "io"
|
||||
|
||||
// formatSimpleError is a helper used by FormatError() for the top
|
||||
// level error/wrapper argument, if it does not implement the
|
||||
// errors.Formatter interface.
|
||||
func formatSimpleError(err error, p *state, sep string) error {
|
||||
if cause := UnwrapOnce(err); cause != nil {
|
||||
pref := extractPrefix(err, cause)
|
||||
p.buf.WriteString(pref)
|
||||
if pref != "" {
|
||||
p.buf.WriteByte(':')
|
||||
p.buf.WriteString(sep)
|
||||
}
|
||||
err = cause
|
||||
} else {
|
||||
io.WriteString(&p.buf, err.Error())
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
40
vendor/github.com/cockroachdb/errors/errbase/opaque.go
generated
vendored
40
vendor/github.com/cockroachdb/errors/errbase/opaque.go
generated
vendored
@@ -30,19 +30,39 @@ type opaqueLeaf struct {
|
||||
details errorspb.EncodedErrorDetails
|
||||
}
|
||||
|
||||
// opaqueLeafCauses is used when receiving an unknown multi-cause
|
||||
// wrapper type. Its important property is that if it is communicated
|
||||
// back to some network system that _does_ know about the type, the
|
||||
// original object can be restored. We encode multi-cause errors as
|
||||
// leaf nodes over the network, in order to support backwards
|
||||
// compatibility with existing single-cause wrapper messages.
|
||||
//
|
||||
// This struct *must* be initialized with a non-nil causes value in
|
||||
// order to comply with go stdlib expectations for `Unwrap()`.
|
||||
type opaqueLeafCauses struct {
|
||||
opaqueLeaf
|
||||
causes []error
|
||||
}
|
||||
|
||||
var _ error = (*opaqueLeaf)(nil)
|
||||
var _ SafeDetailer = (*opaqueLeaf)(nil)
|
||||
var _ fmt.Formatter = (*opaqueLeaf)(nil)
|
||||
var _ SafeFormatter = (*opaqueLeaf)(nil)
|
||||
|
||||
var _ error = (*opaqueLeafCauses)(nil)
|
||||
var _ SafeDetailer = (*opaqueLeafCauses)(nil)
|
||||
var _ fmt.Formatter = (*opaqueLeafCauses)(nil)
|
||||
var _ SafeFormatter = (*opaqueLeafCauses)(nil)
|
||||
|
||||
// opaqueWrapper is used when receiving an unknown wrapper type.
|
||||
// Its important property is that if it is communicated
|
||||
// back to some network system that _does_ know about
|
||||
// the type, the original object can be restored.
|
||||
type opaqueWrapper struct {
|
||||
cause error
|
||||
prefix string
|
||||
details errorspb.EncodedErrorDetails
|
||||
cause error
|
||||
prefix string
|
||||
details errorspb.EncodedErrorDetails
|
||||
messageType MessageType
|
||||
}
|
||||
|
||||
var _ error = (*opaqueWrapper)(nil)
|
||||
@@ -53,6 +73,9 @@ var _ SafeFormatter = (*opaqueWrapper)(nil)
|
||||
func (e *opaqueLeaf) Error() string { return e.msg }
|
||||
|
||||
func (e *opaqueWrapper) Error() string {
|
||||
if e.messageType == FullMessage {
|
||||
return e.prefix
|
||||
}
|
||||
if e.prefix == "" {
|
||||
return e.cause.Error()
|
||||
}
|
||||
@@ -66,8 +89,12 @@ func (e *opaqueWrapper) Unwrap() error { return e.cause }
|
||||
func (e *opaqueLeaf) SafeDetails() []string { return e.details.ReportablePayload }
|
||||
func (e *opaqueWrapper) SafeDetails() []string { return e.details.ReportablePayload }
|
||||
|
||||
func (e *opaqueLeaf) Format(s fmt.State, verb rune) { FormatError(e, s, verb) }
|
||||
func (e *opaqueWrapper) Format(s fmt.State, verb rune) { FormatError(e, s, verb) }
|
||||
func (e *opaqueLeaf) Format(s fmt.State, verb rune) { FormatError(e, s, verb) }
|
||||
func (e *opaqueLeafCauses) Format(s fmt.State, verb rune) { FormatError(e, s, verb) }
|
||||
func (e *opaqueWrapper) Format(s fmt.State, verb rune) { FormatError(e, s, verb) }
|
||||
|
||||
// opaqueLeafCauses is a multi-cause wrapper
|
||||
func (e *opaqueLeafCauses) Unwrap() []error { return e.causes }
|
||||
|
||||
func (e *opaqueLeaf) SafeFormatError(p Printer) (next error) {
|
||||
p.Print(e.msg)
|
||||
@@ -103,5 +130,8 @@ func (e *opaqueWrapper) SafeFormatError(p Printer) (next error) {
|
||||
p.Printf("\npayload type: %s", redact.Safe(e.details.FullDetails.TypeUrl))
|
||||
}
|
||||
}
|
||||
if e.messageType == FullMessage {
|
||||
return nil
|
||||
}
|
||||
return e.cause
|
||||
}
|
||||
|
||||
20
vendor/github.com/cockroachdb/errors/errbase/unwrap.go
generated
vendored
20
vendor/github.com/cockroachdb/errors/errbase/unwrap.go
generated
vendored
@@ -27,6 +27,16 @@ package errbase
|
||||
// It supports both errors implementing causer (`Cause()` method, from
|
||||
// github.com/pkg/errors) and `Wrapper` (`Unwrap()` method, from the
|
||||
// Go 2 error proposal).
|
||||
//
|
||||
// UnwrapOnce treats multi-errors (those implementing the
|
||||
// `Unwrap() []error` interface as leaf-nodes since they cannot
|
||||
// reasonably be iterated through to a single cause. These errors
|
||||
// are typically constructed as a result of `fmt.Errorf` which results
|
||||
// in a `wrapErrors` instance that contains an interpolated error
|
||||
// string along with a list of causes.
|
||||
//
|
||||
// The go stdlib does not define output on `Unwrap()` for a multi-cause
|
||||
// error, so we default to nil here.
|
||||
func UnwrapOnce(err error) (cause error) {
|
||||
switch e := err.(type) {
|
||||
case interface{ Cause() error }:
|
||||
@@ -39,6 +49,7 @@ func UnwrapOnce(err error) (cause error) {
|
||||
|
||||
// UnwrapAll accesses the root cause object of the error.
|
||||
// If the error has no cause (leaf error), it is returned directly.
|
||||
// UnwrapAll treats multi-errors as leaf nodes.
|
||||
func UnwrapAll(err error) error {
|
||||
for {
|
||||
if cause := UnwrapOnce(err); cause != nil {
|
||||
@@ -49,3 +60,12 @@ func UnwrapAll(err error) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// UnwrapMulti access the slice of causes that an error contains, if it is a
|
||||
// multi-error.
|
||||
func UnwrapMulti(err error) []error {
|
||||
if me, ok := err.(interface{ Unwrap() []error }); ok {
|
||||
return me.Unwrap()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
52
vendor/github.com/cockroachdb/errors/errbase_api.go
generated
vendored
52
vendor/github.com/cockroachdb/errors/errbase_api.go
generated
vendored
@@ -86,6 +86,20 @@ func GetTypeKey(err error) TypeKey { return errbase.GetTypeKey(err) }
|
||||
// A nil return indicates that decoding was not successful.
|
||||
type LeafDecoder = errbase.LeafDecoder
|
||||
|
||||
// MultiCauseDecoder is to be provided (via RegisterMultiCauseDecoder
|
||||
// above) by additional multi-cause wrapper types not yet known by the
|
||||
// library. A nil return indicates that decoding was not successful.
|
||||
type MultiCauseDecoder = errbase.MultiCauseDecoder
|
||||
|
||||
// RegisterMultiCauseDecoder can be used to register new multi-cause
|
||||
// wrapper types to the library. Registered wrappers will be decoded
|
||||
// using their own Go type when an error is decoded. Multi-cause
|
||||
// wrappers that have not been registered will be decoded using the
|
||||
// opaqueWrapper type.
|
||||
func RegisterMultiCauseDecoder(theType TypeKey, decoder MultiCauseDecoder) {
|
||||
errbase.RegisterMultiCauseDecoder(theType, decoder)
|
||||
}
|
||||
|
||||
// RegisterWrapperDecoder can be used to register new wrapper types to
|
||||
// the library. Registered wrappers will be decoded using their own
|
||||
// Go type when an error is decoded. Wrappers that have not been
|
||||
@@ -135,6 +149,41 @@ func RegisterWrapperEncoder(typeName TypeKey, encoder WrapperEncoder) {
|
||||
// by additional wrapper types not yet known to this library.
|
||||
type WrapperEncoder = errbase.WrapperEncoder
|
||||
|
||||
// RegisterWrapperEncoderWithMessageType can be used to register new wrapper
|
||||
// types to the library. These wrappers can optionally override the child error
|
||||
// messages with their own error string instead of relying on iterative
|
||||
// concatenation. Registered wrappers will be encoded using their own Go type
|
||||
// when an error is encoded. Wrappers that have not been registered will be
|
||||
// encoded using the opaqueWrapper type.
|
||||
//
|
||||
// Note: if the error type has been migrated from a previous location
|
||||
// or a different type, ensure that RegisterTypeMigration() was called
|
||||
// prior to RegisterWrapperEncoder().
|
||||
func RegisterWrapperEncoderWithMessageType(typeName TypeKey, encoder WrapperEncoderWithMessageType) {
|
||||
errbase.RegisterWrapperEncoderWithMessageType(typeName, encoder)
|
||||
}
|
||||
|
||||
// WrapperEncoderWithMessageType is to be provided (via
|
||||
// RegisterWrapperEncoderWithMessageType) by additional wrapper
|
||||
// types not yet known to this library.
|
||||
type WrapperEncoderWithMessageType = errbase.WrapperEncoderWithMessageType
|
||||
|
||||
// RegisterMultiCauseEncoder can be used to register new multi-cause
|
||||
// error types to the library. Registered types will be encoded using
|
||||
// their own Go type when an error is encoded. Multi-cause wrappers
|
||||
// that have not been registered will be encoded using the
|
||||
// opaqueWrapper type.
|
||||
func RegisterMultiCauseEncoder(typeName TypeKey, encoder MultiCauseEncoder) {
|
||||
errbase.RegisterMultiCauseEncoder(typeName, encoder)
|
||||
}
|
||||
|
||||
// MultiCauseEncoder is to be provided (via RegisterMultiCauseEncoder
|
||||
// above) by additional multi-cause wrapper types not yet known to this
|
||||
// library. The encoder will automatically extract and encode the
|
||||
// causes of this error by calling `Unwrap()` and expecting a slice of
|
||||
// errors.
|
||||
type MultiCauseEncoder = errbase.MultiCauseEncoder
|
||||
|
||||
// SetWarningFn enables configuration of the warning function.
|
||||
func SetWarningFn(fn func(context.Context, string, ...interface{})) { errbase.SetWarningFn(fn) }
|
||||
|
||||
@@ -185,7 +234,8 @@ func Formattable(err error) fmt.Formatter { return errbase.Formattable(err) }
|
||||
// The value of previousTypeName must be the result of calling
|
||||
// reflect.TypeOf(err).String() on the original error object.
|
||||
// This is usually composed as follows:
|
||||
// [*]<shortpackage>.<errortype>
|
||||
//
|
||||
// [*]<shortpackage>.<errortype>
|
||||
//
|
||||
// For example, Go's standard error type has name "*errors.errorString".
|
||||
// The asterisk indicates that `errorString` implements the `error`
|
||||
|
||||
234
vendor/github.com/cockroachdb/errors/errorspb/errors.pb.go
generated
vendored
234
vendor/github.com/cockroachdb/errors/errorspb/errors.pb.go
generated
vendored
@@ -24,10 +24,42 @@ var _ = math.Inf
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// MessageType encodes information regarding the interpretation of an
|
||||
// error's message string.
|
||||
type MessageType int32
|
||||
|
||||
const (
|
||||
// Keeping zero value as default for backwards compatibility.
|
||||
// PREFIX is a message that should be prepended to its cause.
|
||||
MessageType_PREFIX MessageType = 0
|
||||
// FULL_MESSAGE is a complete error message that can be displayed
|
||||
// without its cause.
|
||||
MessageType_FULL_MESSAGE MessageType = 1
|
||||
)
|
||||
|
||||
var MessageType_name = map[int32]string{
|
||||
0: "PREFIX",
|
||||
1: "FULL_MESSAGE",
|
||||
}
|
||||
|
||||
var MessageType_value = map[string]int32{
|
||||
"PREFIX": 0,
|
||||
"FULL_MESSAGE": 1,
|
||||
}
|
||||
|
||||
func (x MessageType) String() string {
|
||||
return proto.EnumName(MessageType_name, int32(x))
|
||||
}
|
||||
|
||||
func (MessageType) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_ddc818d0729874b8, []int{0}
|
||||
}
|
||||
|
||||
// EncodedError is the wire-encodable representation
|
||||
// of an error (or error cause chain).
|
||||
type EncodedError struct {
|
||||
// Types that are valid to be assigned to Error:
|
||||
//
|
||||
// *EncodedError_Leaf
|
||||
// *EncodedError_Wrapper
|
||||
Error isEncodedError_Error `protobuf_oneof:"error"`
|
||||
@@ -108,7 +140,7 @@ func (*EncodedError) XXX_OneofWrappers() []interface{} {
|
||||
}
|
||||
|
||||
// EncodedErrorLeaf is the wire-encodable representation
|
||||
// of an error leaf.
|
||||
// of an error leaf or a multi-cause wrapper from go 1.20+
|
||||
type EncodedErrorLeaf struct {
|
||||
// The main error message (mandatory), that can be printed to human
|
||||
// users and may contain PII. This contains the value of the leaf
|
||||
@@ -116,6 +148,11 @@ type EncodedErrorLeaf struct {
|
||||
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
|
||||
// The error details.
|
||||
Details EncodedErrorDetails `protobuf:"bytes,2,opt,name=details,proto3" json:"details"`
|
||||
// multierror_causes is a list of errors that contain the causal tree
|
||||
// of this leaf. If this field is not empty, then this leaf encodes
|
||||
// an error from go 1.20 or later that encodes multiple causes in its
|
||||
// chain.
|
||||
MultierrorCauses []*EncodedError `protobuf:"bytes,3,rep,name=multierror_causes,json=multierrorCauses,proto3" json:"multierror_causes,omitempty"`
|
||||
}
|
||||
|
||||
func (m *EncodedErrorLeaf) Reset() { *m = EncodedErrorLeaf{} }
|
||||
@@ -210,17 +247,23 @@ var xxx_messageInfo_EncodedErrorDetails proto.InternalMessageInfo
|
||||
type EncodedWrapper struct {
|
||||
// The cause error. Mandatory.
|
||||
Cause EncodedError `protobuf:"bytes,1,opt,name=cause,proto3" json:"cause"`
|
||||
// The wrapper message prefix (which may be empty). This
|
||||
// isbprinted before the cause's own message when
|
||||
// constructing a full message. This may contain PII.
|
||||
// The wrapper message. This could either be a full error message
|
||||
// that can be printed independently, or a (potentially empty) prefix
|
||||
// which is printed before the cause's own message to construct the
|
||||
// full message. This may contain PII.
|
||||
//
|
||||
// This is extracted automatically:
|
||||
//
|
||||
// - for wrappers that have a registered encoder,
|
||||
// - otherwise, when the wrapper's Error() has its cause's Error() as suffix.
|
||||
MessagePrefix string `protobuf:"bytes,2,opt,name=message_prefix,json=messagePrefix,proto3" json:"message_prefix,omitempty"`
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
// The error details.
|
||||
Details EncodedErrorDetails `protobuf:"bytes,3,opt,name=details,proto3" json:"details"`
|
||||
// message_type encodes the interpretation of `message`. Prior
|
||||
// versions will not set this field and it will be left as `PREFIX`.
|
||||
// This retains backwards compatibility since the new behavior is
|
||||
// only enabled when this enum is set to `FULL_MESSAGE`.
|
||||
MessageType MessageType `protobuf:"varint,4,opt,name=message_type,json=messageType,proto3,enum=cockroach.errorspb.MessageType" json:"message_type,omitempty"`
|
||||
}
|
||||
|
||||
func (m *EncodedWrapper) Reset() { *m = EncodedWrapper{} }
|
||||
@@ -382,6 +425,7 @@ func (m *ErrnoPayload) XXX_DiscardUnknown() {
|
||||
var xxx_messageInfo_ErrnoPayload proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("cockroach.errorspb.MessageType", MessageType_name, MessageType_value)
|
||||
proto.RegisterType((*EncodedError)(nil), "cockroach.errorspb.EncodedError")
|
||||
proto.RegisterType((*EncodedErrorLeaf)(nil), "cockroach.errorspb.EncodedErrorLeaf")
|
||||
proto.RegisterType((*EncodedErrorDetails)(nil), "cockroach.errorspb.EncodedErrorDetails")
|
||||
@@ -394,46 +438,51 @@ func init() {
|
||||
func init() { proto.RegisterFile("errorspb/errors.proto", fileDescriptor_ddc818d0729874b8) }
|
||||
|
||||
var fileDescriptor_ddc818d0729874b8 = []byte{
|
||||
// 624 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0x41, 0x6f, 0xd3, 0x4c,
|
||||
0x10, 0xb5, 0xbf, 0xa4, 0x4d, 0x32, 0x49, 0xfa, 0x95, 0xa5, 0x48, 0x6e, 0x45, 0xdd, 0x34, 0x80,
|
||||
0xa8, 0x2a, 0x70, 0x24, 0x38, 0x20, 0x21, 0x84, 0x44, 0x45, 0x45, 0x0f, 0x50, 0x2a, 0x53, 0x09,
|
||||
0x89, 0x8b, 0xb5, 0x49, 0xc6, 0xee, 0xaa, 0xb6, 0xd7, 0xda, 0x75, 0x44, 0xfd, 0x2f, 0x90, 0xf8,
|
||||
0x3f, 0x9c, 0x7b, 0xec, 0xb1, 0x27, 0x04, 0xa9, 0xf8, 0x17, 0x1c, 0x90, 0x77, 0xbd, 0x6d, 0x0a,
|
||||
0x15, 0x3d, 0x70, 0x1b, 0xbf, 0x79, 0xe3, 0xf7, 0x3c, 0x6f, 0x0c, 0xb7, 0x50, 0x08, 0x2e, 0x64,
|
||||
0x36, 0x1c, 0xe8, 0xc2, 0xcb, 0x04, 0xcf, 0x39, 0x21, 0x23, 0x3e, 0x3a, 0x14, 0x9c, 0x8e, 0x0e,
|
||||
0x3c, 0x43, 0x58, 0x59, 0x8e, 0x38, 0x8f, 0x62, 0x1c, 0x28, 0xc6, 0x70, 0x12, 0x0e, 0x68, 0x5a,
|
||||
0x68, 0xfa, 0xca, 0x52, 0xc4, 0x23, 0xae, 0xca, 0x41, 0x59, 0x69, 0xb4, 0xff, 0xd9, 0x86, 0xce,
|
||||
0x76, 0x3a, 0xe2, 0x63, 0x1c, 0x6f, 0x97, 0x2f, 0x21, 0x4f, 0xa1, 0x1e, 0x23, 0x0d, 0x1d, 0xbb,
|
||||
0x67, 0x6f, 0xb4, 0x1f, 0xdd, 0xf5, 0xfe, 0x14, 0xf1, 0x66, 0xf9, 0xaf, 0x91, 0x86, 0x3b, 0x96,
|
||||
0xaf, 0x66, 0xc8, 0x73, 0x68, 0x7c, 0x14, 0x34, 0xcb, 0x50, 0x38, 0xff, 0xa9, 0xf1, 0xfe, 0x5f,
|
||||
0xc6, 0xdf, 0x6b, 0xe6, 0x8e, 0xe5, 0x9b, 0xa1, 0xad, 0x06, 0xcc, 0x29, 0x56, 0x7f, 0x02, 0x8b,
|
||||
0xbf, 0x8b, 0x10, 0x07, 0x1a, 0x09, 0x4a, 0x49, 0x23, 0x54, 0xde, 0x5a, 0xbe, 0x79, 0x24, 0xaf,
|
||||
0xa0, 0x31, 0xc6, 0x9c, 0xb2, 0x58, 0x56, 0xb2, 0xf7, 0xaf, 0x73, 0xfd, 0x52, 0xd3, 0xb7, 0xea,
|
||||
0xc7, 0x5f, 0xd7, 0x2c, 0xdf, 0x4c, 0xf7, 0x7f, 0xda, 0x70, 0xf3, 0x0a, 0x1a, 0x79, 0x00, 0x84,
|
||||
0x0b, 0x16, 0xb1, 0x94, 0xc6, 0x41, 0x5e, 0x64, 0x18, 0xa4, 0x34, 0x31, 0x2e, 0x16, 0x4d, 0x67,
|
||||
0xbf, 0xc8, 0x70, 0x97, 0x26, 0x48, 0xde, 0xc2, 0xff, 0x4a, 0x54, 0x53, 0x13, 0x2a, 0x0e, 0x2b,
|
||||
0x5b, 0xeb, 0x57, 0xda, 0x2a, 0x8b, 0x72, 0xf6, 0x0d, 0x15, 0x87, 0x95, 0xa1, 0x2e, 0xce, 0x82,
|
||||
0xe4, 0x21, 0x10, 0x81, 0x19, 0x17, 0x39, 0x1d, 0xc6, 0x18, 0x64, 0xb4, 0x88, 0x39, 0x1d, 0x3b,
|
||||
0xb5, 0x5e, 0x6d, 0xa3, 0xe5, 0xdf, 0xb8, 0xe8, 0xec, 0xe9, 0x06, 0x79, 0x02, 0x9d, 0x70, 0x12,
|
||||
0xc7, 0x81, 0xd9, 0x49, 0x5d, 0x89, 0x2f, 0x79, 0xfa, 0x34, 0x3c, 0x73, 0x1a, 0xde, 0x8b, 0xb4,
|
||||
0xf0, 0xdb, 0x25, 0xb3, 0xfa, 0xcc, 0xfe, 0x17, 0x1b, 0x16, 0x2e, 0x87, 0x43, 0x9e, 0xc1, 0xdc,
|
||||
0x88, 0x4e, 0x24, 0x56, 0xe7, 0xd0, 0xbb, 0x6e, 0xb1, 0xd5, 0x07, 0xe8, 0x21, 0x72, 0x0f, 0x16,
|
||||
0xaa, 0x8c, 0x82, 0x4c, 0x60, 0xc8, 0x8e, 0xd4, 0x22, 0x5a, 0x7e, 0xb7, 0x42, 0xf7, 0x14, 0x38,
|
||||
0x9b, 0x5f, 0xed, 0x9f, 0xf2, 0xdb, 0x85, 0xee, 0xa5, 0x75, 0x92, 0x35, 0x68, 0x87, 0x34, 0x61,
|
||||
0x71, 0x31, 0x9b, 0x18, 0x68, 0x48, 0x65, 0x75, 0x1b, 0x5a, 0x78, 0x94, 0x63, 0x2a, 0x19, 0x4f,
|
||||
0x2b, 0x73, 0x17, 0x40, 0x7f, 0x13, 0x16, 0xde, 0xe5, 0x82, 0xa5, 0x91, 0x34, 0xbb, 0x75, 0x2e,
|
||||
0xac, 0xda, 0x6a, 0xff, 0xe7, 0xda, 0x3f, 0xca, 0x1f, 0x49, 0x88, 0x94, 0x1b, 0xea, 0x2a, 0x40,
|
||||
0x79, 0x1a, 0x01, 0x96, 0xa0, 0x92, 0xae, 0xf9, 0xad, 0x12, 0x51, 0x2c, 0x42, 0xa0, 0x4e, 0xc5,
|
||||
0xe8, 0xa0, 0x12, 0x55, 0x35, 0xb9, 0x03, 0x5d, 0x26, 0x83, 0x0c, 0x45, 0xc2, 0xa4, 0x72, 0x54,
|
||||
0xae, 0xa3, 0xe9, 0x77, 0x98, 0xdc, 0x3b, 0xc7, 0xc8, 0x32, 0x34, 0x99, 0x0c, 0xf0, 0x88, 0xc9,
|
||||
0x5c, 0x45, 0xdb, 0xf4, 0x1b, 0x4c, 0x6e, 0x97, 0x8f, 0xa4, 0x07, 0x1d, 0x26, 0x83, 0x94, 0xe7,
|
||||
0x55, 0x7b, 0x4e, 0xb5, 0x81, 0xc9, 0x5d, 0x9e, 0x6b, 0xc6, 0x2a, 0x00, 0x93, 0x41, 0xce, 0x12,
|
||||
0xe4, 0x93, 0xdc, 0x99, 0x57, 0xfd, 0x16, 0x93, 0xfb, 0x1a, 0x20, 0xeb, 0xea, 0x05, 0x39, 0x26,
|
||||
0x19, 0x17, 0x54, 0x14, 0x4e, 0x43, 0x11, 0xda, 0x4c, 0xee, 0x1b, 0x68, 0x6b, 0xf3, 0xf8, 0xbb,
|
||||
0x6b, 0x1d, 0x4f, 0x5d, 0xfb, 0x64, 0xea, 0xda, 0xa7, 0x53, 0xd7, 0xfe, 0x36, 0x75, 0xed, 0x4f,
|
||||
0x67, 0xae, 0x75, 0x72, 0xe6, 0x5a, 0xa7, 0x67, 0xae, 0xf5, 0xa1, 0x69, 0x22, 0x1b, 0xce, 0xab,
|
||||
0x5b, 0x7b, 0xfc, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x63, 0x5c, 0xa2, 0x72, 0xc1, 0x04, 0x00, 0x00,
|
||||
// 693 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0x41, 0x4f, 0x13, 0x41,
|
||||
0x14, 0xde, 0xa5, 0x85, 0xb6, 0xaf, 0x05, 0xcb, 0x88, 0xc9, 0x42, 0x64, 0x29, 0xab, 0x89, 0x04,
|
||||
0x75, 0x9b, 0xe0, 0xc1, 0xc4, 0x18, 0x13, 0xaa, 0x05, 0x4c, 0x28, 0x92, 0x05, 0xa3, 0xf1, 0xb2,
|
||||
0x99, 0xb6, 0xd3, 0x65, 0xc2, 0xee, 0xce, 0x66, 0x66, 0x1b, 0xd9, 0x7f, 0x61, 0xe2, 0x5f, 0xf2,
|
||||
0xc0, 0x91, 0x23, 0x27, 0xa3, 0x25, 0xfe, 0x04, 0x6f, 0x1e, 0xcc, 0xce, 0xee, 0xd8, 0xa2, 0x28,
|
||||
0x07, 0x6f, 0x6f, 0xde, 0xfb, 0xbe, 0xf7, 0xde, 0xf7, 0xde, 0x1b, 0xb8, 0x45, 0x38, 0x67, 0x5c,
|
||||
0x44, 0xdd, 0x66, 0x66, 0xd8, 0x11, 0x67, 0x31, 0x43, 0xa8, 0xc7, 0x7a, 0xc7, 0x9c, 0xe1, 0xde,
|
||||
0x91, 0xad, 0x00, 0x4b, 0x8b, 0x1e, 0x63, 0x9e, 0x4f, 0x9a, 0x12, 0xd1, 0x1d, 0x0e, 0x9a, 0x38,
|
||||
0x4c, 0x32, 0xf8, 0xd2, 0x82, 0xc7, 0x3c, 0x26, 0xcd, 0x66, 0x6a, 0x65, 0x5e, 0xeb, 0xa3, 0x0e,
|
||||
0xb5, 0x76, 0xd8, 0x63, 0x7d, 0xd2, 0x6f, 0xa7, 0x49, 0xd0, 0x13, 0x28, 0xfa, 0x04, 0x0f, 0x0c,
|
||||
0xbd, 0xa1, 0xaf, 0x55, 0x37, 0xee, 0xda, 0x7f, 0x16, 0xb1, 0x27, 0xf1, 0xbb, 0x04, 0x0f, 0x76,
|
||||
0x34, 0x47, 0x72, 0xd0, 0x33, 0x28, 0xbd, 0xe7, 0x38, 0x8a, 0x08, 0x37, 0xa6, 0x24, 0xdd, 0xfa,
|
||||
0x07, 0xfd, 0x4d, 0x86, 0xdc, 0xd1, 0x1c, 0x45, 0x6a, 0x95, 0x60, 0x5a, 0xa2, 0xac, 0x4f, 0x3a,
|
||||
0xd4, 0x7f, 0xaf, 0x82, 0x0c, 0x28, 0x05, 0x44, 0x08, 0xec, 0x11, 0xd9, 0x5c, 0xc5, 0x51, 0x4f,
|
||||
0xb4, 0x0d, 0xa5, 0x3e, 0x89, 0x31, 0xf5, 0x45, 0x5e, 0xf7, 0xde, 0x75, 0x6d, 0xbf, 0xc8, 0xe0,
|
||||
0xad, 0xe2, 0xe9, 0xe7, 0x15, 0xcd, 0x51, 0x6c, 0xd4, 0x81, 0xf9, 0x60, 0xe8, 0xc7, 0x54, 0x72,
|
||||
0xdc, 0x1e, 0x1e, 0x0a, 0x22, 0x8c, 0x42, 0xa3, 0xb0, 0x56, 0xdd, 0x68, 0x5c, 0x97, 0xd2, 0xa9,
|
||||
0x8f, 0xa9, 0xcf, 0x25, 0xd3, 0xfa, 0xa1, 0xc3, 0xcd, 0x2b, 0xaa, 0xa2, 0x07, 0x80, 0x18, 0xa7,
|
||||
0x1e, 0x0d, 0xb1, 0xef, 0xc6, 0x49, 0x44, 0xdc, 0x10, 0x07, 0x4a, 0x54, 0x5d, 0x45, 0x0e, 0x93,
|
||||
0x88, 0xec, 0xe1, 0x80, 0xa0, 0x57, 0x70, 0x23, 0xeb, 0x47, 0x42, 0x03, 0xcc, 0x8f, 0x73, 0x95,
|
||||
0xab, 0x57, 0xb6, 0x94, 0x1a, 0x29, 0xb7, 0x83, 0xf9, 0x71, 0xae, 0x6f, 0x96, 0x4c, 0x3a, 0xd1,
|
||||
0x43, 0x40, 0x9c, 0x44, 0x8c, 0xc7, 0xb8, 0xeb, 0x13, 0x37, 0xc2, 0x89, 0xcf, 0x70, 0x5f, 0xca,
|
||||
0xac, 0x38, 0xf3, 0xe3, 0xc8, 0x7e, 0x16, 0x40, 0x8f, 0xa1, 0x36, 0x18, 0xfa, 0xbe, 0xab, 0x46,
|
||||
0x5c, 0x94, 0xc5, 0x17, 0xec, 0xec, 0xd4, 0x6c, 0x75, 0x6a, 0xf6, 0x66, 0x98, 0x38, 0xd5, 0x14,
|
||||
0x99, 0xcb, 0xb4, 0xbe, 0xeb, 0x30, 0x77, 0x79, 0xd9, 0xe8, 0x29, 0x4c, 0xcb, 0xa9, 0xe6, 0xe7,
|
||||
0x75, 0xed, 0x50, 0x73, 0x01, 0x19, 0x69, 0xf2, 0x02, 0xa6, 0xfe, 0x7a, 0x01, 0x85, 0xff, 0xba,
|
||||
0x80, 0x16, 0xd4, 0xf2, 0x9c, 0x72, 0xdc, 0x52, 0xec, 0xdc, 0xc6, 0xca, 0x55, 0xd9, 0x3a, 0x19,
|
||||
0x2e, 0x1d, 0xab, 0x53, 0x0d, 0xc6, 0x0f, 0x6b, 0x0f, 0x66, 0x2f, 0x6d, 0x01, 0xad, 0x40, 0x75,
|
||||
0x80, 0x03, 0xea, 0x27, 0x93, 0x8b, 0x86, 0xcc, 0x25, 0x57, 0x7c, 0x1b, 0x2a, 0xe4, 0x24, 0x26,
|
||||
0xa1, 0xa0, 0x2c, 0xcc, 0xa5, 0x8d, 0x1d, 0xd6, 0x3a, 0xcc, 0x1d, 0xc4, 0x9c, 0x86, 0x9e, 0x50,
|
||||
0x2b, 0x31, 0xc6, 0x72, 0x75, 0xb9, 0x36, 0xf5, 0xb4, 0xbe, 0xa5, 0xff, 0x99, 0xf3, 0x90, 0x29,
|
||||
0xe8, 0x32, 0x40, 0x7a, 0x51, 0x2e, 0x49, 0x9d, 0xb2, 0x74, 0xc1, 0xa9, 0xa4, 0x1e, 0x89, 0x42,
|
||||
0x08, 0x8a, 0x98, 0xf7, 0x8e, 0xf2, 0xa2, 0xd2, 0x46, 0x77, 0x60, 0x96, 0x0a, 0x37, 0x22, 0x3c,
|
||||
0xa0, 0x42, 0x76, 0x94, 0x8e, 0xb4, 0xec, 0xd4, 0xa8, 0xd8, 0xff, 0xe5, 0x43, 0x8b, 0x50, 0xa6,
|
||||
0xc2, 0x25, 0x27, 0x54, 0xc4, 0x72, 0x48, 0x65, 0xa7, 0x44, 0x45, 0x3b, 0x7d, 0xa2, 0x06, 0xd4,
|
||||
0xa8, 0x70, 0x43, 0x16, 0xe7, 0xe1, 0x69, 0x19, 0x06, 0x2a, 0xf6, 0x58, 0x9c, 0x21, 0x96, 0x01,
|
||||
0xa8, 0x70, 0x63, 0x1a, 0x10, 0x36, 0x8c, 0x8d, 0x19, 0x19, 0xaf, 0x50, 0x71, 0x98, 0x39, 0xd0,
|
||||
0xaa, 0x4c, 0x10, 0x93, 0x20, 0x62, 0x1c, 0xf3, 0xc4, 0x28, 0x49, 0x40, 0x95, 0x8a, 0x43, 0xe5,
|
||||
0x5a, 0xbf, 0x0f, 0xd5, 0x89, 0xf9, 0x23, 0x80, 0x99, 0x7d, 0xa7, 0xbd, 0xf5, 0xf2, 0x6d, 0x5d,
|
||||
0x43, 0x75, 0xa8, 0x6d, 0xbd, 0xde, 0xdd, 0x75, 0x3b, 0xed, 0x83, 0x83, 0xcd, 0xed, 0x76, 0x5d,
|
||||
0x6f, 0xad, 0x9f, 0x7e, 0x35, 0xb5, 0xd3, 0x91, 0xa9, 0x9f, 0x8d, 0x4c, 0xfd, 0x7c, 0x64, 0xea,
|
||||
0x5f, 0x46, 0xa6, 0xfe, 0xe1, 0xc2, 0xd4, 0xce, 0x2e, 0x4c, 0xed, 0xfc, 0xc2, 0xd4, 0xde, 0x95,
|
||||
0xd5, 0x56, 0xbb, 0x33, 0xf2, 0x9e, 0x1f, 0xfd, 0x0c, 0x00, 0x00, 0xff, 0xff, 0xd7, 0x2c, 0x21,
|
||||
0xe3, 0x75, 0x05, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *EncodedError) Marshal() (dAtA []byte, err error) {
|
||||
@@ -530,6 +579,20 @@ func (m *EncodedErrorLeaf) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.MultierrorCauses) > 0 {
|
||||
for iNdEx := len(m.MultierrorCauses) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.MultierrorCauses[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintErrors(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x1a
|
||||
}
|
||||
}
|
||||
{
|
||||
size, err := m.Details.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
@@ -631,6 +694,11 @@ func (m *EncodedWrapper) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.MessageType != 0 {
|
||||
i = encodeVarintErrors(dAtA, i, uint64(m.MessageType))
|
||||
i--
|
||||
dAtA[i] = 0x20
|
||||
}
|
||||
{
|
||||
size, err := m.Details.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
@@ -641,10 +709,10 @@ func (m *EncodedWrapper) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x1a
|
||||
if len(m.MessagePrefix) > 0 {
|
||||
i -= len(m.MessagePrefix)
|
||||
copy(dAtA[i:], m.MessagePrefix)
|
||||
i = encodeVarintErrors(dAtA, i, uint64(len(m.MessagePrefix)))
|
||||
if len(m.Message) > 0 {
|
||||
i -= len(m.Message)
|
||||
copy(dAtA[i:], m.Message)
|
||||
i = encodeVarintErrors(dAtA, i, uint64(len(m.Message)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
@@ -874,6 +942,12 @@ func (m *EncodedErrorLeaf) Size() (n int) {
|
||||
}
|
||||
l = m.Details.Size()
|
||||
n += 1 + l + sovErrors(uint64(l))
|
||||
if len(m.MultierrorCauses) > 0 {
|
||||
for _, e := range m.MultierrorCauses {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovErrors(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -910,12 +984,15 @@ func (m *EncodedWrapper) Size() (n int) {
|
||||
_ = l
|
||||
l = m.Cause.Size()
|
||||
n += 1 + l + sovErrors(uint64(l))
|
||||
l = len(m.MessagePrefix)
|
||||
l = len(m.Message)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovErrors(uint64(l))
|
||||
}
|
||||
l = m.Details.Size()
|
||||
n += 1 + l + sovErrors(uint64(l))
|
||||
if m.MessageType != 0 {
|
||||
n += 1 + sovErrors(uint64(m.MessageType))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -1202,6 +1279,40 @@ func (m *EncodedErrorLeaf) Unmarshal(dAtA []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MultierrorCauses", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowErrors
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthErrors
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthErrors
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.MultierrorCauses = append(m.MultierrorCauses, &EncodedError{})
|
||||
if err := m.MultierrorCauses[len(m.MultierrorCauses)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipErrors(dAtA[iNdEx:])
|
||||
@@ -1470,7 +1581,7 @@ func (m *EncodedWrapper) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MessagePrefix", wireType)
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
@@ -1498,7 +1609,7 @@ func (m *EncodedWrapper) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.MessagePrefix = string(dAtA[iNdEx:postIndex])
|
||||
m.Message = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
@@ -1533,6 +1644,25 @@ func (m *EncodedWrapper) Unmarshal(dAtA []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 4:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MessageType", wireType)
|
||||
}
|
||||
m.MessageType = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowErrors
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.MessageType |= MessageType(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipErrors(dAtA[iNdEx:])
|
||||
|
||||
34
vendor/github.com/cockroachdb/errors/errorspb/errors.proto
generated
vendored
34
vendor/github.com/cockroachdb/errors/errorspb/errors.proto
generated
vendored
@@ -17,7 +17,7 @@ message EncodedError {
|
||||
}
|
||||
|
||||
// EncodedErrorLeaf is the wire-encodable representation
|
||||
// of an error leaf.
|
||||
// of an error leaf or a multi-cause wrapper from go 1.20+
|
||||
message EncodedErrorLeaf {
|
||||
// The main error message (mandatory), that can be printed to human
|
||||
// users and may contain PII. This contains the value of the leaf
|
||||
@@ -26,6 +26,12 @@ message EncodedErrorLeaf {
|
||||
|
||||
// The error details.
|
||||
EncodedErrorDetails details = 2 [(gogoproto.nullable) = false];
|
||||
|
||||
// multierror_causes is a list of errors that contain the causal tree
|
||||
// of this leaf. If this field is not empty, then this leaf encodes
|
||||
// an error from go 1.20 or later that encodes multiple causes in its
|
||||
// chain.
|
||||
repeated EncodedError multierror_causes = 3;
|
||||
}
|
||||
|
||||
message EncodedErrorDetails {
|
||||
@@ -60,24 +66,42 @@ message EncodedErrorDetails {
|
||||
google.protobuf.Any full_details = 4;
|
||||
}
|
||||
|
||||
// MessageType encodes information regarding the interpretation of an
|
||||
// error's message string.
|
||||
enum MessageType {
|
||||
// Keeping zero value as default for backwards compatibility.
|
||||
// PREFIX is a message that should be prepended to its cause.
|
||||
PREFIX = 0;
|
||||
// FULL_MESSAGE is a complete error message that can be displayed
|
||||
// without its cause.
|
||||
FULL_MESSAGE = 1;
|
||||
}
|
||||
|
||||
// EncodedWrapper is the wire-encodable representation
|
||||
// of an error wrapper.
|
||||
message EncodedWrapper {
|
||||
// The cause error. Mandatory.
|
||||
EncodedError cause = 1 [(gogoproto.nullable) = false];
|
||||
|
||||
// The wrapper message prefix (which may be empty). This
|
||||
// isbprinted before the cause's own message when
|
||||
// constructing a full message. This may contain PII.
|
||||
// The wrapper message. This could either be a full error message
|
||||
// that can be printed independently, or a (potentially empty) prefix
|
||||
// which is printed before the cause's own message to construct the
|
||||
// full message. This may contain PII.
|
||||
//
|
||||
// This is extracted automatically:
|
||||
//
|
||||
// - for wrappers that have a registered encoder,
|
||||
// - otherwise, when the wrapper's Error() has its cause's Error() as suffix.
|
||||
string message_prefix = 2;
|
||||
string message = 2;
|
||||
|
||||
// The error details.
|
||||
EncodedErrorDetails details = 3 [(gogoproto.nullable) = false];
|
||||
|
||||
// message_type encodes the interpretation of `message`. Prior
|
||||
// versions will not set this field and it will be left as `PREFIX`.
|
||||
// This retains backwards compatibility since the new behavior is
|
||||
// only enabled when this enum is set to `FULL_MESSAGE`.
|
||||
MessageType message_type = 4;
|
||||
}
|
||||
|
||||
// ErrorTypeMark identifies an error type for the purpose of determining
|
||||
|
||||
9
vendor/github.com/cockroachdb/errors/errutil/as.go
generated
vendored
9
vendor/github.com/cockroachdb/errors/errutil/as.go
generated
vendored
@@ -58,7 +58,16 @@ func As(err error, target interface{}) bool {
|
||||
if x, ok := c.(interface{ As(interface{}) bool }); ok && x.As(target) {
|
||||
return true
|
||||
}
|
||||
|
||||
// If at any point in the single cause chain including the top,
|
||||
// we encounter a multi-cause chain, recursively explore it.
|
||||
for _, cause := range errbase.UnwrapMulti(c) {
|
||||
if As(cause, target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
2
vendor/github.com/cockroachdb/errors/errutil/doc.go
generated
vendored
2
vendor/github.com/cockroachdb/errors/errutil/doc.go
generated
vendored
@@ -14,6 +14,6 @@
|
||||
|
||||
// Package errutil combines primitives from the library, the Go errors
|
||||
// package and github.com/pkg/errors. It aims to serve as drop-in
|
||||
// replacement to gitub.com/pkg/errors and replace the legacy
|
||||
// replacement to github.com/pkg/errors and replace the legacy
|
||||
// implementation of `pgerrors`.
|
||||
package errutil
|
||||
|
||||
8
vendor/github.com/cockroachdb/errors/errutil/utilities.go
generated
vendored
8
vendor/github.com/cockroachdb/errors/errutil/utilities.go
generated
vendored
@@ -15,6 +15,7 @@
|
||||
package errutil
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors/join"
|
||||
"github.com/cockroachdb/errors/secondary"
|
||||
"github.com/cockroachdb/errors/withstack"
|
||||
"github.com/cockroachdb/redact"
|
||||
@@ -158,3 +159,10 @@ func WrapWithDepthf(depth int, err error, format string, args ...interface{}) er
|
||||
err = withstack.WithStackDepth(err, depth+1)
|
||||
return err
|
||||
}
|
||||
|
||||
// JoinWithDepth constructs a Join error with the provided list of
|
||||
// errors as arguments, and wraps it in a `WithStackDepth` to capture a
|
||||
// stacktrace alongside.
|
||||
func JoinWithDepth(depth int, errs ...error) error {
|
||||
return withstack.WithStackDepth(join.Join(errs...), depth+1)
|
||||
}
|
||||
|
||||
16
vendor/github.com/cockroachdb/errors/errutil_api.go
generated
vendored
16
vendor/github.com/cockroachdb/errors/errutil_api.go
generated
vendored
@@ -192,3 +192,19 @@ func HandleAsAssertionFailureDepth(depth int, origErr error) error {
|
||||
// - it also supports recursing through causes with Cause().
|
||||
// - if it detects an API use error, its panic object is a valid error.
|
||||
func As(err error, target interface{}) bool { return errutil.As(err, target) }
|
||||
|
||||
// Join returns an error that wraps the given errors.
|
||||
// Any nil error values are discarded.
|
||||
// Join returns nil if errs contains no non-nil values.
|
||||
// The error formats as the concatenation of the strings obtained
|
||||
// by calling the Error method of each element of errs, with a newline
|
||||
// between each string. A stack trace is also retained.
|
||||
func Join(errs ...error) error {
|
||||
return errutil.JoinWithDepth(1, errs...)
|
||||
}
|
||||
|
||||
// JoinWithDepth is like Join but the depth at which the call stack is
|
||||
// captured can be specified.
|
||||
func JoinWithDepth(depth int, errs ...error) error {
|
||||
return errutil.JoinWithDepth(depth+1, errs...)
|
||||
}
|
||||
|
||||
5
vendor/github.com/cockroachdb/errors/hintdetail/hintdetail.go
generated
vendored
5
vendor/github.com/cockroachdb/errors/hintdetail/hintdetail.go
generated
vendored
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
// WithHint decorates an error with a textual hint.
|
||||
// The hint may contain PII and thus will not reportable.
|
||||
// The suggested use case for hint is to relay information to end users.
|
||||
//
|
||||
// Hint is shown:
|
||||
// - when formatting with `%+v`.
|
||||
@@ -97,10 +98,12 @@ type ErrorHinter interface {
|
||||
|
||||
// WithDetail decorates an error with a textual detail.
|
||||
// The detail may contain PII and thus will not reportable.
|
||||
// The suggested use case for detail is to augment errors with information
|
||||
// useful for debugging.
|
||||
//
|
||||
// Detail is shown:
|
||||
// - when formatting with `%+v`.
|
||||
// - with `GetAllHints()` / `FlattenHints()` below.
|
||||
// - with `GetAllDetails()` / `FlattenDetails()` below.
|
||||
//
|
||||
// Note: the detail does not appear in the main error message returned
|
||||
// with Error(). Use GetAllDetails() or FlattenDetails() to retrieve
|
||||
|
||||
5
vendor/github.com/cockroachdb/errors/hintdetail_api.go
generated
vendored
5
vendor/github.com/cockroachdb/errors/hintdetail_api.go
generated
vendored
@@ -27,6 +27,7 @@ type ErrorDetailer = hintdetail.ErrorDetailer
|
||||
|
||||
// WithHint decorates an error with a textual hint.
|
||||
// The hint may contain PII and thus will not reportable.
|
||||
// The suggested use case for hint is to relay information to end users.
|
||||
//
|
||||
// Hint is shown:
|
||||
// - when formatting with `%+v`.
|
||||
@@ -44,10 +45,12 @@ func WithHintf(err error, format string, args ...interface{}) error {
|
||||
|
||||
// WithDetail decorates an error with a textual detail.
|
||||
// The detail may contain PII and thus will not reportable.
|
||||
// The suggested use case for detail is to augment errors with information
|
||||
// useful for debugging.
|
||||
//
|
||||
// Detail is shown:
|
||||
// - when formatting with `%+v`.
|
||||
// - with `GetAllHints()` / `FlattenHints()` below.
|
||||
// - with `GetAllDetails()` / `FlattenDetails()` below.
|
||||
//
|
||||
// Note: the detail does not appear in the main error message returned
|
||||
// with Error(). Use GetAllDetails() or FlattenDetails() to retrieve
|
||||
|
||||
105
vendor/github.com/cockroachdb/errors/join/join.go
generated
vendored
Normal file
105
vendor/github.com/cockroachdb/errors/join/join.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright 2019 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 join
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors/errbase"
|
||||
"github.com/cockroachdb/redact"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
// Join returns an error that wraps the given errors.
|
||||
// Any nil error values are discarded.
|
||||
// Join returns nil if errs contains no non-nil values.
|
||||
// The error formats as the concatenation of the strings obtained
|
||||
// by calling the Error method of each element of errs, with a newline
|
||||
// between each string.
|
||||
func Join(errs ...error) error {
|
||||
n := 0
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
n++
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
e := &joinError{
|
||||
errs: make([]error, 0, n),
|
||||
}
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
type joinError struct {
|
||||
errs []error
|
||||
}
|
||||
|
||||
var _ error = (*joinError)(nil)
|
||||
var _ fmt.Formatter = (*joinError)(nil)
|
||||
var _ errbase.SafeFormatter = (*joinError)(nil)
|
||||
|
||||
func (e *joinError) Error() string {
|
||||
return redact.Sprint(e).StripMarkers()
|
||||
}
|
||||
|
||||
func (e *joinError) Unwrap() []error {
|
||||
return e.errs
|
||||
}
|
||||
|
||||
func (e *joinError) SafeFormatError(p errbase.Printer) error {
|
||||
for i, err := range e.errs {
|
||||
if i > 0 {
|
||||
p.Print("\n")
|
||||
}
|
||||
p.Print(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *joinError) Format(s fmt.State, verb rune) {
|
||||
errbase.FormatError(e, s, verb)
|
||||
}
|
||||
|
||||
func init() {
|
||||
errbase.RegisterMultiCauseEncoder(
|
||||
errbase.GetTypeKey(&joinError{}),
|
||||
func(
|
||||
ctx context.Context,
|
||||
err error,
|
||||
) (msg string, safeDetails []string, payload proto.Message) {
|
||||
return "", nil, nil
|
||||
},
|
||||
)
|
||||
errbase.RegisterMultiCauseDecoder(
|
||||
errbase.GetTypeKey(&joinError{}),
|
||||
func(
|
||||
ctx context.Context,
|
||||
causes []error,
|
||||
msgPrefix string,
|
||||
safeDetails []string,
|
||||
payload proto.Message,
|
||||
) error {
|
||||
return Join(causes...)
|
||||
},
|
||||
)
|
||||
}
|
||||
18
vendor/github.com/cockroachdb/errors/markers/markers.go
generated
vendored
18
vendor/github.com/cockroachdb/errors/markers/markers.go
generated
vendored
@@ -59,6 +59,13 @@ func Is(err, reference error) bool {
|
||||
if tryDelegateToIsMethod(c, reference) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Recursively try multi-error causes, if applicable.
|
||||
for _, me := range errbase.UnwrapMulti(c) {
|
||||
if Is(me, reference) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@@ -68,8 +75,8 @@ func Is(err, reference error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Not directly equal. Try harder, using error marks. We don't this
|
||||
// during the loop above as it may be more expensive.
|
||||
// Not directly equal. Try harder, using error marks. We don't do
|
||||
// this during the loop above as it may be more expensive.
|
||||
//
|
||||
// Note: there is a more effective recursive algorithm that ensures
|
||||
// that any pair of string only gets compared once. Should the
|
||||
@@ -172,6 +179,13 @@ func IsAny(err error, references ...error) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively try multi-error causes, if applicable.
|
||||
for _, me := range errbase.UnwrapMulti(c) {
|
||||
if IsAny(me, references...) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try harder with marks.
|
||||
|
||||
40
vendor/github.com/cockroachdb/errors/report/report.go
generated
vendored
40
vendor/github.com/cockroachdb/errors/report/report.go
generated
vendored
@@ -34,36 +34,39 @@ import (
|
||||
//
|
||||
// A Sentry report is displayed visually in the Sentry UI as follows:
|
||||
//
|
||||
////////////////
|
||||
// //////////////
|
||||
// Title: (1) some prefix in bold (2) one line for a stack trace
|
||||
// (3) a single-line subtitle
|
||||
//
|
||||
// (4) the tags, as a tag soup (concatenated in a single paragrah,
|
||||
// (4) the tags, as a tag soup (concatenated in a single paragraph,
|
||||
// unsorted)
|
||||
//
|
||||
// (5) a "message"
|
||||
//
|
||||
// (6) zero or more "exceptions", each composed of:
|
||||
// (7) a bold title
|
||||
// (8) some freeform text
|
||||
// (9) a stack trace
|
||||
//
|
||||
// (7) a bold title
|
||||
// (8) some freeform text
|
||||
// (9) a stack trace
|
||||
//
|
||||
// (10) metadata fields: environment, arch, etc
|
||||
//
|
||||
// (11) "Additional data" fields
|
||||
//
|
||||
// (12) SDK version
|
||||
/////////////////
|
||||
// ///////////////
|
||||
//
|
||||
// These visual items map to the Sentry Event object as follows:
|
||||
//
|
||||
// (1) the Type field of the 1st Exception object, if any
|
||||
// otherwise the Message field
|
||||
//
|
||||
// otherwise the Message field
|
||||
//
|
||||
// (2) the topmost entry from the Stacktrace field of the 1st Exception object, if any
|
||||
// (3) the Value field of the 1st Exception object, if any, unwrapped as a single line
|
||||
// (4) the Tags field
|
||||
// (5) the Message field
|
||||
// (7) the Type field (same as (1) for 1st execption)
|
||||
// (7) the Type field (same as (1) for 1st exception)
|
||||
// (8) the Value field (same as (3) for 1st exception)
|
||||
// (9) the Stacktrace field (input to (2) on 1st exception)
|
||||
// (10) the other fields on the Event object
|
||||
@@ -78,7 +81,9 @@ import (
|
||||
// (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 "exception" objects
|
||||
//
|
||||
// additional "exception" objects
|
||||
//
|
||||
// (9): generated from innermost stack trace
|
||||
// (6): every exception object after the 1st reports additional stack trace contexts
|
||||
// (11): the detailed error types and their error mark.
|
||||
@@ -92,7 +97,6 @@ import (
|
||||
// is included in the Sentry report. This does not affect error types
|
||||
// provided by the library, but could impact error types defined by
|
||||
// 3rd parties. This limitation may be lifted in a later version.
|
||||
//
|
||||
func BuildSentryReport(err error) (event *sentry.Event, extraDetails map[string]interface{}) {
|
||||
if err == nil {
|
||||
// No error: do nothing.
|
||||
@@ -102,13 +106,13 @@ func BuildSentryReport(err error) (event *sentry.Event, extraDetails map[string]
|
||||
// First step is to collect the details.
|
||||
var stacks []*withstack.ReportableStackTrace
|
||||
var details []errbase.SafeDetailPayload
|
||||
for c := err; c != nil; c = errbase.UnwrapOnce(c) {
|
||||
visitAllMulti(err, func(c error) {
|
||||
st := withstack.GetReportableStackTrace(c)
|
||||
stacks = append(stacks, st)
|
||||
|
||||
sd := errbase.GetSafeDetails(c)
|
||||
details = append(details, sd)
|
||||
}
|
||||
})
|
||||
module := string(domains.GetDomain(err))
|
||||
|
||||
// firstDetailLine is the first detail string encountered.
|
||||
@@ -292,7 +296,7 @@ func BuildSentryReport(err error) (event *sentry.Event, extraDetails map[string]
|
||||
event.Message = longMsgBuf.String()
|
||||
event.Exception = exceptions
|
||||
|
||||
// If there is no exception payload, synthetize one.
|
||||
// If there is no exception payload, synthesize one.
|
||||
if len(event.Exception) == 0 {
|
||||
// We know we don't have a stack trace to extract line/function
|
||||
// info from (if we had, we'd have an Exception payload at that
|
||||
@@ -397,3 +401,13 @@ func reverseExceptionOrder(ex []sentry.Exception) {
|
||||
ex[i], ex[len(ex)-i-1] = ex[len(ex)-i-1], ex[i]
|
||||
}
|
||||
}
|
||||
|
||||
func visitAllMulti(err error, f func(error)) {
|
||||
f(err)
|
||||
if e := errbase.UnwrapOnce(err); e != nil {
|
||||
visitAllMulti(e, f)
|
||||
}
|
||||
for _, e := range errbase.UnwrapMulti(err) {
|
||||
visitAllMulti(e, f)
|
||||
}
|
||||
}
|
||||
|
||||
4
vendor/github.com/cockroachdb/errors/report_api.go
generated
vendored
4
vendor/github.com/cockroachdb/errors/report_api.go
generated
vendored
@@ -32,7 +32,7 @@ import (
|
||||
// Title: (1) some prefix in bold (2) one line for a stack trace
|
||||
// (3) a single-line subtitle
|
||||
//
|
||||
// (4) the tags, as a tag soup (concatenated in a single paragrah,
|
||||
// (4) the tags, as a tag soup (concatenated in a single paragraph,
|
||||
// unsorted)
|
||||
//
|
||||
// (5) a "message"
|
||||
@@ -57,7 +57,7 @@ import (
|
||||
// (3) the Value field of the 1st Exception object, if any, unwrapped as a single line
|
||||
// (4) the Tags field
|
||||
// (5) the Message field
|
||||
// (7) the Type field (same as (1) for 1st execption)
|
||||
// (7) the Type field (same as (1) for 1st exception)
|
||||
// (8) the Value field (same as (3) for 1st exception)
|
||||
// (9) the Stacktrace field (input to (2) on 1st exception)
|
||||
// (10) the other fields on the Event object
|
||||
|
||||
25
vendor/github.com/cockroachdb/fifo/.gitignore
generated
vendored
Normal file
25
vendor/github.com/cockroachdb/fifo/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
.idea/
|
||||
|
||||
201
vendor/github.com/cockroachdb/fifo/LICENSE
generated
vendored
Normal file
201
vendor/github.com/cockroachdb/fifo/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
21
vendor/github.com/cockroachdb/fifo/invariants_off.go
generated
vendored
Normal file
21
vendor/github.com/cockroachdb/fifo/invariants_off.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
//go:build !fifo_invariants
|
||||
|
||||
package fifo
|
||||
|
||||
// invariants is false if we were not built with the "fifo_invariants" build
|
||||
// tag.
|
||||
const invariants = false
|
||||
20
vendor/github.com/cockroachdb/fifo/invariants_on.go
generated
vendored
Normal file
20
vendor/github.com/cockroachdb/fifo/invariants_on.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
//go:build fifo_invariants
|
||||
|
||||
package fifo
|
||||
|
||||
// invariants is true if we were built with the "fifo_invariants" build tag.
|
||||
const invariants = true
|
||||
162
vendor/github.com/cockroachdb/fifo/queue.go
generated
vendored
Normal file
162
vendor/github.com/cockroachdb/fifo/queue.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2024 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 fifo
|
||||
|
||||
import "sync"
|
||||
|
||||
// Queue implements an allocation efficient FIFO queue. It is not safe for
|
||||
// concurrent access.
|
||||
//
|
||||
// Note that the queue provides pointer access to the internal storage (via
|
||||
// PeekFront and PushBack) so it must be used with care. These pointers must not
|
||||
// be used once the respective element is popped out of the queue.
|
||||
//
|
||||
// -- Implementation --
|
||||
//
|
||||
// The queue is implemented as a linked list of nodes, where each node is a
|
||||
// small ring buffer. The nodes are allocated using a sync.Pool (a single pool
|
||||
// is created for any given type and is used for all queues of that type).
|
||||
type Queue[T any] struct {
|
||||
len int
|
||||
head, tail *queueNode[T]
|
||||
|
||||
pool *QueueBackingPool[T]
|
||||
}
|
||||
|
||||
// MakeQueue constructs a new Queue.
|
||||
//
|
||||
// The pool should be a singleton object initialized with MakeQueueBackingPool.
|
||||
// A single pool can and should be used by all queues of that type.
|
||||
func MakeQueue[T any](pool *QueueBackingPool[T]) Queue[T] {
|
||||
return Queue[T]{
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
// Len returns the current length of the queue.
|
||||
func (q *Queue[T]) Len() int {
|
||||
return q.len
|
||||
}
|
||||
|
||||
// PushBack adds t to the end of the queue.
|
||||
//
|
||||
// The returned pointer can be used to modify the element while it is in the
|
||||
// queue; it is valid until the element is removed from the queue.
|
||||
func (q *Queue[T]) PushBack(t T) *T {
|
||||
if q.head == nil {
|
||||
q.head = q.pool.get()
|
||||
q.tail = q.head
|
||||
} else if q.tail.IsFull() {
|
||||
newTail := q.pool.get()
|
||||
q.tail.next = newTail
|
||||
q.tail = newTail
|
||||
}
|
||||
q.len++
|
||||
return q.tail.PushBack(t)
|
||||
}
|
||||
|
||||
// PeekFront returns the current head of the queue, or nil if the queue is
|
||||
// empty.
|
||||
//
|
||||
// The result is only valid until the next call to PopFront.
|
||||
func (q *Queue[T]) PeekFront() *T {
|
||||
if q.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return q.head.PeekFront()
|
||||
}
|
||||
|
||||
// PopFront removes the current head of the queue.
|
||||
//
|
||||
// It is illegal to call PopFront on an empty queue.
|
||||
func (q *Queue[T]) PopFront() {
|
||||
q.head.PopFront()
|
||||
// If this is the only node, we don't want to release it; otherwise we would
|
||||
// allocate/free a node every time we transition between the queue being empty
|
||||
// and non-empty.
|
||||
if q.head.len == 0 && q.head.next != nil {
|
||||
oldHead := q.head
|
||||
q.head = oldHead.next
|
||||
q.pool.put(oldHead)
|
||||
}
|
||||
q.len--
|
||||
}
|
||||
|
||||
// QueueBackingPool is a sync.Pool that used to allocate internal nodes
|
||||
// for Queue[T].
|
||||
type QueueBackingPool[T any] struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// MakeQueueBackingPool makes a queue backing pool. It is intented to be used to
|
||||
// initialize a singleton (global) variable. A single pool can and should be
|
||||
// used by all queues of that type.
|
||||
func MakeQueueBackingPool[T any]() QueueBackingPool[T] {
|
||||
return QueueBackingPool[T]{
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} { return &queueNode[T]{} },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (qp *QueueBackingPool[T]) get() *queueNode[T] {
|
||||
return qp.pool.Get().(*queueNode[T])
|
||||
}
|
||||
|
||||
func (qp *QueueBackingPool[T]) put(n *queueNode[T]) {
|
||||
*n = queueNode[T]{}
|
||||
qp.pool.Put(n)
|
||||
}
|
||||
|
||||
// We batch the allocation of this many queue objects. The value was chosen
|
||||
// without experimentation - it provides a reasonable amount of amortization
|
||||
// without a very large increase in memory overhead if T is large.
|
||||
const queueNodeSize = 8
|
||||
|
||||
type queueNode[T any] struct {
|
||||
buf [queueNodeSize]T
|
||||
head, len int32
|
||||
next *queueNode[T]
|
||||
}
|
||||
|
||||
func (qn *queueNode[T]) IsFull() bool {
|
||||
return qn.len == queueNodeSize
|
||||
}
|
||||
|
||||
func (qn *queueNode[T]) PushBack(t T) *T {
|
||||
if invariants && qn.len >= queueNodeSize {
|
||||
panic("cannot push back into a full node")
|
||||
}
|
||||
i := (qn.head + qn.len) % queueNodeSize
|
||||
qn.buf[i] = t
|
||||
qn.len++
|
||||
return &qn.buf[i]
|
||||
}
|
||||
|
||||
func (qn *queueNode[T]) PeekFront() *T {
|
||||
return &qn.buf[qn.head]
|
||||
}
|
||||
|
||||
func (qn *queueNode[T]) PopFront() T {
|
||||
if invariants && qn.len == 0 {
|
||||
panic("cannot pop from empty queue")
|
||||
}
|
||||
t := qn.buf[qn.head]
|
||||
var zero T
|
||||
qn.buf[qn.head] = zero
|
||||
qn.head = (qn.head + 1) % queueNodeSize
|
||||
qn.len--
|
||||
return t
|
||||
}
|
||||
239
vendor/github.com/cockroachdb/fifo/semaphore.go
generated
vendored
Normal file
239
vendor/github.com/cockroachdb/fifo/semaphore.go
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright 2024 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 fifo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Semaphore implements a weighted, dynamically reconfigurable semaphore which
|
||||
// respects context cancellation.
|
||||
//
|
||||
// The semaphore implements a FIFO policy, where Acquire requests are satisfied
|
||||
// in order. This policy provides fairness and prevents starvation but is
|
||||
// susceptible to head-of-line blocking, where a large request that can't be
|
||||
// satisfied blocks many other small requests that could be.
|
||||
type Semaphore struct {
|
||||
mu struct {
|
||||
sync.Mutex
|
||||
|
||||
capacity int64
|
||||
// outstanding can exceed capacity if the capacity is dynamically decreased.
|
||||
outstanding int64
|
||||
|
||||
waiters Queue[semaWaiter]
|
||||
|
||||
// numCanceled is the number of waiters in the waiters queue which have been
|
||||
// canceled. It is used to determine the current number of active waiters in
|
||||
// the queue which is waiters.Len() minus this value.
|
||||
numCanceled int
|
||||
|
||||
// numHadToWait accumulates the total number of Acquire requests which had
|
||||
// to wait because the semaphore was exhausted.
|
||||
numHadToWait int64
|
||||
}
|
||||
}
|
||||
|
||||
// NewSemaphore creates a new semaphore with the given capacity.
|
||||
func NewSemaphore(capacity int64) *Semaphore {
|
||||
if capacity <= 0 {
|
||||
panic("invalid capacity")
|
||||
}
|
||||
s := &Semaphore{}
|
||||
s.mu.capacity = capacity
|
||||
s.mu.waiters = MakeQueue[semaWaiter](&semaQueuePool)
|
||||
return s
|
||||
}
|
||||
|
||||
var semaQueuePool = MakeQueueBackingPool[semaWaiter]()
|
||||
|
||||
var ErrRequestExceedsCapacity = errors.New("request exceeds semaphore capacity")
|
||||
|
||||
// TryAcquire attempts to acquire n units from the semaphore without waiting. On
|
||||
// success, returns true and the caller must later Release the units.
|
||||
func (s *Semaphore) TryAcquire(n int64) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.numWaitersLocked() == 0 && s.mu.outstanding+n <= s.mu.capacity {
|
||||
s.mu.outstanding += n
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Acquire n units from the semaphore, waiting if necessary.
|
||||
//
|
||||
// If the context is canceled while we are waiting, returns the context error.
|
||||
// If n exceeds the current capacity, returns ErrRequestExceedsCapacity.
|
||||
// On success, the caller must later Release the units.
|
||||
func (s *Semaphore) Acquire(ctx context.Context, n int64) error {
|
||||
s.mu.Lock()
|
||||
|
||||
// Fast path.
|
||||
if s.numWaitersLocked() == 0 && s.mu.outstanding+n <= s.mu.capacity {
|
||||
s.mu.outstanding += n
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
if n > s.mu.capacity {
|
||||
s.mu.Unlock()
|
||||
return ErrRequestExceedsCapacity
|
||||
}
|
||||
|
||||
c := chanSyncPool.Get().(chan error)
|
||||
defer chanSyncPool.Put(c)
|
||||
w := s.mu.waiters.PushBack(semaWaiter{n: n, c: c})
|
||||
s.mu.numHadToWait++
|
||||
s.mu.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
// We need to check if we raced with a channel notify (which happens under
|
||||
// the lock).
|
||||
select {
|
||||
case err := <-c:
|
||||
// We actually fulfilled or failed the request.
|
||||
return err
|
||||
default:
|
||||
}
|
||||
// Mark the request as canceled.
|
||||
w.c = nil
|
||||
s.mu.numCanceled++
|
||||
// If we are the head of the queue, we may be able to fulfill other waiters.
|
||||
s.processWaitersLocked()
|
||||
return ctx.Err()
|
||||
|
||||
case err := <-c:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Release n units back. These must be units that were acquired by a previous
|
||||
// Acquire call. It is legal to split up or coalesce units when releasing.
|
||||
func (s *Semaphore) Release(n int64) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.mu.outstanding -= n
|
||||
if s.mu.outstanding < 0 {
|
||||
panic("releasing more than was acquired")
|
||||
}
|
||||
s.processWaitersLocked()
|
||||
}
|
||||
|
||||
// UpdateCapacity changes the capacity of the semaphore. If the new capacity is
|
||||
// smaller, the already outstanding acquisitions might exceed the new capacity
|
||||
// until they are released.
|
||||
//
|
||||
// If there are Acquire calls that are waiting which are requesting more than
|
||||
// the new capacity, they will error out with ErrRequestExceedsCapacity.
|
||||
func (s *Semaphore) UpdateCapacity(capacity int64) {
|
||||
if capacity <= 0 {
|
||||
panic("invalid capacity")
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.mu.capacity = capacity
|
||||
s.processWaitersLocked()
|
||||
}
|
||||
|
||||
// Stats returns the current state of the semaphore.
|
||||
func (s *Semaphore) Stats() SemaphoreStats {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return SemaphoreStats{
|
||||
Capacity: s.mu.capacity,
|
||||
Outstanding: s.mu.outstanding,
|
||||
NumHadToWait: s.mu.numHadToWait,
|
||||
}
|
||||
}
|
||||
|
||||
// SemaphoreStats contains information about the current state of the semaphore.
|
||||
type SemaphoreStats struct {
|
||||
// Capacity is the current capacity of the semaphore.
|
||||
Capacity int64
|
||||
// Outstanding is the number of units that have been acquired. Note that this
|
||||
// can exceed Capacity if the capacity was recently decreased.
|
||||
Outstanding int64
|
||||
// NumHadToWait is the total number of Acquire calls (since the semaphore was
|
||||
// created) that had to wait because the semaphore was exhausted. Useful for
|
||||
// cumulative metrics.
|
||||
NumHadToWait int64
|
||||
|
||||
// TODO(radu): consider keeping track of the total amount of time the
|
||||
// semaphore was exhausted (i.e. there were waiters queued).
|
||||
}
|
||||
|
||||
func (ss SemaphoreStats) String() string {
|
||||
return fmt.Sprintf("capacity: %d, outstanding: %d, num-had-to-wait: %d",
|
||||
ss.Capacity, ss.Outstanding, ss.NumHadToWait)
|
||||
}
|
||||
|
||||
type semaWaiter struct {
|
||||
// n is the amount that the waiter is trying to acquire.
|
||||
n int64
|
||||
// c is the channel on which Acquire is blocked. If the request is canceled,
|
||||
// it is set to nil.
|
||||
c chan error
|
||||
}
|
||||
|
||||
// numWaitersLocked returns how many requests (that have not been canceled) are
|
||||
// waiting in the queue.
|
||||
func (s *Semaphore) numWaitersLocked() int {
|
||||
return s.mu.waiters.Len() - s.mu.numCanceled
|
||||
}
|
||||
|
||||
// processWaitersLocked processes and notifies as many waiters from the head of
|
||||
// the queue as possible.
|
||||
func (s *Semaphore) processWaitersLocked() {
|
||||
for ; s.mu.waiters.Len() > 0; s.mu.waiters.PopFront() {
|
||||
switch w := s.mu.waiters.PeekFront(); {
|
||||
case w.c == nil:
|
||||
// Request was canceled, we can just clean it up.
|
||||
s.mu.numCanceled--
|
||||
if invariants && s.mu.numCanceled < 0 {
|
||||
panic("negative numCanceled")
|
||||
}
|
||||
|
||||
case s.mu.outstanding+w.n <= s.mu.capacity:
|
||||
// Request can be fulfilled.
|
||||
s.mu.outstanding += w.n
|
||||
w.c <- nil
|
||||
|
||||
case w.n > s.mu.capacity:
|
||||
// Request must be failed. This can happen if the capacity was decreased
|
||||
// while the element was queued.
|
||||
w.c <- ErrRequestExceedsCapacity
|
||||
|
||||
default:
|
||||
// Head of the queue needs to wait some more.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// chanSyncPool is used to pool allocations of the channels used to notify
|
||||
// goroutines waiting in Acquire.
|
||||
var chanSyncPool = sync.Pool{
|
||||
New: func() interface{} { return make(chan error, 1) },
|
||||
}
|
||||
3
vendor/github.com/cockroachdb/pebble/.gitignore
generated
vendored
3
vendor/github.com/cockroachdb/pebble/.gitignore
generated
vendored
@@ -1,6 +1,9 @@
|
||||
# Github action artifacts.
|
||||
artifacts
|
||||
# Profiling artifacts.
|
||||
cpu.*.prof
|
||||
heap.prof
|
||||
mutex.prof
|
||||
coverprofile.out
|
||||
# Testing artifacts
|
||||
meta.*.test
|
||||
|
||||
27
vendor/github.com/cockroachdb/pebble/Makefile
generated
vendored
27
vendor/github.com/cockroachdb/pebble/Makefile
generated
vendored
@@ -4,7 +4,8 @@ GOFLAGS :=
|
||||
STRESSFLAGS :=
|
||||
TAGS := invariants
|
||||
TESTS := .
|
||||
LATEST_RELEASE := $(shell git fetch origin && git branch -r --list '*/crl-release-*' | grep -o 'crl-release-.*$$' | sort | tail -1)
|
||||
PREV_RELEASE := crl-release-23.1
|
||||
COVER_PROFILE := coverprofile.out
|
||||
|
||||
.PHONY: all
|
||||
all:
|
||||
@@ -15,7 +16,10 @@ all:
|
||||
@echo " make stressrace"
|
||||
@echo " make stressmeta"
|
||||
@echo " make crossversion-meta"
|
||||
@echo " make testcoverage"
|
||||
@echo " make mod-update"
|
||||
@echo " make generate"
|
||||
@echo " make generate-test-data"
|
||||
@echo " make clean"
|
||||
|
||||
override testflags :=
|
||||
@@ -23,6 +27,10 @@ override testflags :=
|
||||
test:
|
||||
${GO} test -tags '$(TAGS)' ${testflags} -run ${TESTS} ${PKG}
|
||||
|
||||
.PHONY: testcoverage
|
||||
testcoverage:
|
||||
${GO} test -tags '$(TAGS)' ${testflags} -run ${TESTS} ${PKG} -coverprofile ${COVER_PROFILE}
|
||||
|
||||
.PHONY: testrace
|
||||
testrace: testflags += -race -timeout 20m
|
||||
testrace: test
|
||||
@@ -55,11 +63,11 @@ stressmeta: stress
|
||||
|
||||
.PHONY: crossversion-meta
|
||||
crossversion-meta:
|
||||
git checkout ${LATEST_RELEASE}; \
|
||||
${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/${LATEST_RELEASE}.test'; \
|
||||
git checkout ${PREV_RELEASE}; \
|
||||
${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/${PREV_RELEASE}.test'; \
|
||||
git checkout -; \
|
||||
${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/head.test'; \
|
||||
${GO} test -tags '$(TAGS)' ${testflags} -v -run 'TestMetaCrossVersion' ./internal/metamorphic/crossversion --version '${LATEST_RELEASE},${LATEST_RELEASE},${LATEST_RELEASE}.test' --version 'HEAD,HEAD,./head.test'
|
||||
${GO} test -tags '$(TAGS)' ${testflags} -v -run 'TestMetaCrossVersion' ./internal/metamorphic/crossversion --version '${PREV_RELEASE},${PREV_RELEASE},${PREV_RELEASE}.test' --version 'HEAD,HEAD,./head.test'
|
||||
|
||||
.PHONY: stress-crossversion
|
||||
stress-crossversion:
|
||||
@@ -69,6 +77,17 @@ stress-crossversion:
|
||||
generate:
|
||||
${GO} generate ${PKG}
|
||||
|
||||
generate:
|
||||
|
||||
# Note that the output of generate-test-data is not deterministic. This should
|
||||
# only be run manually as needed.
|
||||
.PHONY: generate-test-data
|
||||
generate-test-data:
|
||||
${GO} run -tags make_incorrect_manifests ./tool/make_incorrect_manifests.go
|
||||
${GO} run -tags make_test_find_db ./tool/make_test_find_db.go
|
||||
${GO} run -tags make_test_sstables ./tool/make_test_sstables.go
|
||||
${GO} run -tags make_test_remotecat ./tool/make_test_remotecat.go
|
||||
|
||||
mod-update:
|
||||
${GO} get -u
|
||||
${GO} mod tidy
|
||||
|
||||
84
vendor/github.com/cockroachdb/pebble/README.md
generated
vendored
84
vendor/github.com/cockroachdb/pebble/README.md
generated
vendored
@@ -1,4 +1,4 @@
|
||||
# Pebble [](https://github.com/cockroachdb/pebble/actions/workflows/ci.yaml) [](https://godoc.org/github.com/cockroachdb/pebble)
|
||||
# Pebble [](https://github.com/cockroachdb/pebble/actions/workflows/ci.yaml) [](https://godoc.org/github.com/cockroachdb/pebble) <sup><sub><sub>[Coverage](https://storage.googleapis.com/crl-codecover-public/pebble/index.html)</sub></sub></sup>
|
||||
|
||||
#### [Nightly benchmarks](https://cockroachdb.github.io/pebble/)
|
||||
|
||||
@@ -8,9 +8,8 @@ RocksDB file formats and a few extensions such as range deletion
|
||||
tombstones, table-level bloom filters, and updates to the MANIFEST
|
||||
format.
|
||||
|
||||
Pebble intentionally does not aspire to include every feature in
|
||||
RocksDB and is specifically targetting the use case and feature set
|
||||
needed by CockroachDB:
|
||||
Pebble intentionally does not aspire to include every feature in RocksDB and
|
||||
specifically targets the use case and feature set needed by CockroachDB:
|
||||
|
||||
* Block-based tables
|
||||
* Checkpoints
|
||||
@@ -68,6 +67,17 @@ Pebble offers several improvements over RocksDB:
|
||||
* Faster commit pipeline that achieves better concurrency.
|
||||
* Seamless merged iteration of indexed batches. The mutations in the
|
||||
batch conceptually occupy another memtable level.
|
||||
* L0 sublevels and flush splitting for concurrent compactions out of L0 and
|
||||
reduced read-amplification during heavy write load.
|
||||
* Faster LSM edits in LSMs with large numbers of sstables through use of a
|
||||
copy-on-write B-tree to hold file metadata.
|
||||
* Delete-only compactions that drop whole sstables that fall within the bounds
|
||||
of a range deletion.
|
||||
* Block-property collectors and filters that enable iterators to skip tables,
|
||||
index blocks and data blocks that are irrelevant, according to user-defined
|
||||
properties over key-value pairs.
|
||||
* Range keys API, allowing KV pairs defined over a range of keyspace with
|
||||
user-defined semantics and interleaved during iteration.
|
||||
* Smaller, more approachable code base.
|
||||
|
||||
See the [Pebble vs RocksDB: Implementation
|
||||
@@ -100,10 +110,68 @@ known incompatibilities.
|
||||
format.
|
||||
* Plain table format. Pebble does not support the plain table sstable
|
||||
format.
|
||||
* SSTable format version 3 and 4. Pebble does not currently support
|
||||
version 3 and version 4 format sstables. The sstable format version
|
||||
is controlled by the `BlockBasedTableOptions::format_version`
|
||||
option. See [#97](https://github.com/cockroachdb/pebble/issues/97).
|
||||
* SSTable format version 3 and 4. Pebble does not support version 3
|
||||
and version 4 format sstables. The sstable format version is
|
||||
controlled by the `BlockBasedTableOptions::format_version` option.
|
||||
See [#97](https://github.com/cockroachdb/pebble/issues/97).
|
||||
|
||||
## Format major versions
|
||||
|
||||
Over time Pebble has introduced new physical file formats. Backwards
|
||||
incompatible changes are made through the introduction of 'format major
|
||||
versions'. By default, when Pebble opens a database, it defaults to
|
||||
`FormatMostCompatible`. This version is bi-directionally compatible with RocksDB
|
||||
6.2.1 (with the caveats described above).
|
||||
|
||||
To opt into new formats, a user may set `FormatMajorVersion` on the
|
||||
[`Options`](https://pkg.go.dev/github.com/cockroachdb/pebble#Options)
|
||||
supplied to
|
||||
[`Open`](https://pkg.go.dev/github.com/cockroachdb/pebble#Open), or
|
||||
upgrade the format major version at runtime using
|
||||
[`DB.RatchetFormatMajorVersion`](https://pkg.go.dev/github.com/cockroachdb/pebble#DB.RatchetFormatMajorVersion).
|
||||
Format major version upgrades are permanent; There is no option to
|
||||
return to an earlier format.
|
||||
|
||||
The table below outlines the history of format major versions:
|
||||
|
||||
| Name | Value | Migration |
|
||||
|------------------------------------|-------|------------|
|
||||
| FormatMostCompatible | 1 | No |
|
||||
| FormatVersioned | 3 | No |
|
||||
| FormatSetWithDelete | 4 | No |
|
||||
| FormatBlockPropertyCollector | 5 | No |
|
||||
| FormatSplitUserKeysMarked | 6 | Background |
|
||||
| FormatSplitUserKeysMarkedCompacted | 7 | Blocking |
|
||||
| FormatRangeKeys | 8 | No |
|
||||
| FormatMinTableFormatPebblev1 | 9 | No |
|
||||
| FormatPrePebblev1Marked | 10 | Background |
|
||||
| FormatSSTableValueBlocks | 12 | No |
|
||||
| FormatFlushableIngest | 13 | No |
|
||||
| FormatPrePebblev1MarkedCompacted | 14 | Blocking |
|
||||
| FormatDeleteSizedAndObsolete | 15 | No |
|
||||
| FormatVirtualSSTables | 16 | No |
|
||||
|
||||
Upgrading to a format major version with 'Background' in the migration
|
||||
column may trigger background activity to rewrite physical file
|
||||
formats, typically through compactions. Upgrading to a format major
|
||||
version with 'Blocking' in the migration column will block until a
|
||||
migration is complete. The database may continue to serve reads and
|
||||
writes if upgrading a live database through
|
||||
`RatchetFormatMajorVersion`, but the method call will not return until
|
||||
the migration is complete.
|
||||
|
||||
For reference, the table below lists the range of supported Pebble format major
|
||||
versions for CockroachDB releases.
|
||||
|
||||
| CockroachDB release | Earliest supported | Latest supported |
|
||||
|---------------------|------------------------------------|---------------------------|
|
||||
| 20.1 through 21.1 | FormatMostCompatible | FormatMostCompatible |
|
||||
| 21.2 | FormatMostCompatible | FormatSetWithDelete |
|
||||
| 21.2 | FormatMostCompatible | FormatSetWithDelete |
|
||||
| 22.1 | FormatMostCompatible | FormatSplitUserKeysMarked |
|
||||
| 22.2 | FormatMostCompatible | FormatPrePebblev1Marked |
|
||||
| 23.1 | FormatSplitUserKeysMarkedCompacted | FormatFlushableIngest |
|
||||
| 23.2 | FormatSplitUserKeysMarkedCompacted | FormatVirtualSSTables |
|
||||
|
||||
## Pedigree
|
||||
|
||||
|
||||
315
vendor/github.com/cockroachdb/pebble/batch.go
generated
vendored
315
vendor/github.com/cockroachdb/pebble/batch.go
generated
vendored
@@ -41,10 +41,10 @@ const (
|
||||
var ErrNotIndexed = errors.New("pebble: batch not indexed")
|
||||
|
||||
// ErrInvalidBatch indicates that a batch is invalid or otherwise corrupted.
|
||||
var ErrInvalidBatch = errors.New("pebble: invalid batch")
|
||||
var ErrInvalidBatch = base.MarkCorruptionError(errors.New("pebble: invalid batch"))
|
||||
|
||||
// ErrBatchTooLarge indicates that a batch is invalid or otherwise corrupted.
|
||||
var ErrBatchTooLarge = errors.Newf("pebble: batch too large: >= %s", humanize.Uint64(maxBatchSize))
|
||||
var ErrBatchTooLarge = base.MarkCorruptionError(errors.Newf("pebble: batch too large: >= %s", humanize.Bytes.Uint64(maxBatchSize)))
|
||||
|
||||
// DeferredBatchOp represents a batch operation (eg. set, merge, delete) that is
|
||||
// being inserted into the batch. Indexing is not performed on the specified key
|
||||
@@ -183,6 +183,13 @@ func (d DeferredBatchOp) Finish() error {
|
||||
// WAL, and thus stable. New record kinds may be added, but the existing ones
|
||||
// will not be modified.
|
||||
type Batch struct {
|
||||
batchInternal
|
||||
applied atomic.Bool
|
||||
}
|
||||
|
||||
// batchInternal contains the set of fields within Batch that are non-atomic and
|
||||
// capable of being reset using a *b = batchInternal{} struct copy.
|
||||
type batchInternal struct {
|
||||
// Data is the wire format of a batch's log entry:
|
||||
// - 8 bytes for a sequence number of the first batch element,
|
||||
// or zeroes if the batch has not yet been applied,
|
||||
@@ -260,10 +267,12 @@ type Batch struct {
|
||||
// memtable.
|
||||
flushable *flushableBatch
|
||||
|
||||
// ingestedSSTBatch indicates that the batch contains one or more key kinds
|
||||
// of InternalKeyKindIngestSST. If the batch contains key kinds of IngestSST
|
||||
// then it will only contain key kinds of IngestSST.
|
||||
ingestedSSTBatch bool
|
||||
// minimumFormatMajorVersion indicates the format major version required in
|
||||
// order to commit this batch. If an operation requires a particular format
|
||||
// major version, it ratchets the batch's minimumFormatMajorVersion. When
|
||||
// the batch is committed, this is validated against the database's current
|
||||
// format major version.
|
||||
minimumFormatMajorVersion FormatMajorVersion
|
||||
|
||||
// Synchronous Apply uses the commit WaitGroup for both publishing the
|
||||
// seqnum and waiting for the WAL fsync (if needed). Asynchronous
|
||||
@@ -285,7 +294,22 @@ type Batch struct {
|
||||
commitStats BatchCommitStats
|
||||
|
||||
commitErr error
|
||||
applied atomic.Bool
|
||||
|
||||
// Position bools together to reduce the sizeof the struct.
|
||||
|
||||
// ingestedSSTBatch indicates that the batch contains one or more key kinds
|
||||
// of InternalKeyKindIngestSST. If the batch contains key kinds of IngestSST
|
||||
// then it will only contain key kinds of IngestSST.
|
||||
ingestedSSTBatch bool
|
||||
|
||||
// committing is set to true when a batch begins to commit. It's used to
|
||||
// ensure the batch is not mutated concurrently. It is not an atomic
|
||||
// deliberately, so as to avoid the overhead on batch mutations. This is
|
||||
// okay, because under correct usage this field will never be accessed
|
||||
// concurrently. It's only under incorrect usage the memory accesses of this
|
||||
// variable may violate memory safety. Since we don't use atomics here,
|
||||
// false negatives are possible.
|
||||
committing bool
|
||||
}
|
||||
|
||||
// BatchCommitStats exposes stats related to committing a batch.
|
||||
@@ -312,7 +336,11 @@ type BatchCommitStats struct {
|
||||
// commitPipeline.Commit.
|
||||
SemaphoreWaitDuration time.Duration
|
||||
// WALQueueWaitDuration is the wait time for allocating memory blocks in the
|
||||
// LogWriter (due to the LogWriter not writing fast enough).
|
||||
// LogWriter (due to the LogWriter not writing fast enough). At the moment
|
||||
// this is duration is always zero because a single WAL will allow
|
||||
// allocating memory blocks up to the entire memtable size. In the future,
|
||||
// we may pipeline WALs and bound the WAL queued blocks separately, so this
|
||||
// field is preserved for that possibility.
|
||||
WALQueueWaitDuration time.Duration
|
||||
// MemTableWriteStallDuration is the wait caused by a write stall due to too
|
||||
// many memtables (due to not flushing fast enough).
|
||||
@@ -356,6 +384,14 @@ func newBatch(db *DB) *Batch {
|
||||
return b
|
||||
}
|
||||
|
||||
func newBatchWithSize(db *DB, size int) *Batch {
|
||||
b := newBatch(db)
|
||||
if cap(b.data) < size {
|
||||
b.data = rawalloc.New(0, size)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func newIndexedBatch(db *DB, comparer *Comparer) *Batch {
|
||||
i := indexedBatchPool.Get().(*indexedBatch)
|
||||
i.batch.cmp = comparer.Compare
|
||||
@@ -367,6 +403,14 @@ func newIndexedBatch(db *DB, comparer *Comparer) *Batch {
|
||||
return &i.batch
|
||||
}
|
||||
|
||||
func newIndexedBatchWithSize(db *DB, comparer *Comparer, size int) *Batch {
|
||||
b := newIndexedBatch(db, comparer)
|
||||
if cap(b.data) < size {
|
||||
b.data = rawalloc.New(0, size)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// nextSeqNum returns the batch "sequence number" that will be given to the next
|
||||
// key written to the batch. During iteration keys within an indexed batch are
|
||||
// given a sequence number consisting of their offset within the batch combined
|
||||
@@ -405,17 +449,21 @@ func (b *Batch) release() {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Batch) refreshMemTableSize() {
|
||||
func (b *Batch) refreshMemTableSize() error {
|
||||
b.memTableSize = 0
|
||||
if len(b.data) < batchHeaderLen {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
b.countRangeDels = 0
|
||||
b.countRangeKeys = 0
|
||||
b.minimumFormatMajorVersion = 0
|
||||
for r := b.Reader(); ; {
|
||||
kind, key, value, ok := r.Next()
|
||||
kind, key, value, ok, err := r.Next()
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
switch kind {
|
||||
@@ -423,12 +471,23 @@ func (b *Batch) refreshMemTableSize() {
|
||||
b.countRangeDels++
|
||||
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
b.countRangeKeys++
|
||||
case InternalKeyKindDeleteSized:
|
||||
if b.minimumFormatMajorVersion < FormatDeleteSizedAndObsolete {
|
||||
b.minimumFormatMajorVersion = FormatDeleteSizedAndObsolete
|
||||
}
|
||||
case InternalKeyKindIngestSST:
|
||||
if b.minimumFormatMajorVersion < FormatFlushableIngest {
|
||||
b.minimumFormatMajorVersion = FormatFlushableIngest
|
||||
}
|
||||
// This key kind doesn't contribute to the memtable size.
|
||||
continue
|
||||
}
|
||||
b.memTableSize += memTableEntrySize(len(key), len(value))
|
||||
}
|
||||
if b.countRangeKeys > 0 && b.minimumFormatMajorVersion < FormatRangeKeys {
|
||||
b.minimumFormatMajorVersion = FormatRangeKeys
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply the operations contained in the batch to the receiver batch.
|
||||
@@ -442,7 +501,7 @@ func (b *Batch) Apply(batch *Batch, _ *WriteOptions) error {
|
||||
return nil
|
||||
}
|
||||
if len(batch.data) < batchHeaderLen {
|
||||
return base.CorruptionErrorf("pebble: invalid batch")
|
||||
return ErrInvalidBatch
|
||||
}
|
||||
|
||||
offset := len(b.data)
|
||||
@@ -459,8 +518,11 @@ func (b *Batch) Apply(batch *Batch, _ *WriteOptions) error {
|
||||
// order to update the index.
|
||||
for iter := BatchReader(b.data[offset:]); len(iter) > 0; {
|
||||
offset := uintptr(unsafe.Pointer(&iter[0])) - uintptr(unsafe.Pointer(&b.data[0]))
|
||||
kind, key, value, ok := iter.Next()
|
||||
kind, key, value, ok, err := iter.Next()
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
switch kind {
|
||||
@@ -516,6 +578,9 @@ func (b *Batch) Get(key []byte) ([]byte, io.Closer, error) {
|
||||
}
|
||||
|
||||
func (b *Batch) prepareDeferredKeyValueRecord(keyLen, valueLen int, kind InternalKeyKind) {
|
||||
if b.committing {
|
||||
panic("pebble: batch already committing")
|
||||
}
|
||||
if len(b.data) == 0 {
|
||||
b.init(keyLen + valueLen + 2*binary.MaxVarintLen64 + batchHeaderLen)
|
||||
}
|
||||
@@ -565,6 +630,9 @@ func (b *Batch) prepareDeferredKeyValueRecord(keyLen, valueLen int, kind Interna
|
||||
}
|
||||
|
||||
func (b *Batch) prepareDeferredKeyRecord(keyLen int, kind InternalKeyKind) {
|
||||
if b.committing {
|
||||
panic("pebble: batch already committing")
|
||||
}
|
||||
if len(b.data) == 0 {
|
||||
b.init(keyLen + binary.MaxVarintLen64 + batchHeaderLen)
|
||||
}
|
||||
@@ -598,6 +666,41 @@ func (b *Batch) prepareDeferredKeyRecord(keyLen int, kind InternalKeyKind) {
|
||||
b.data = b.data[:pos+keyLen]
|
||||
}
|
||||
|
||||
// AddInternalKey allows the caller to add an internal key of point key kinds to
|
||||
// a batch. Passing in an internal key of kind RangeKey* or RangeDelete will
|
||||
// result in a panic. Note that the seqnum in the internal key is effectively
|
||||
// ignored, even though the Kind is preserved. This is because the batch format
|
||||
// does not allow for a per-key seqnum to be specified, only a batch-wide one.
|
||||
//
|
||||
// Note that non-indexed keys (IngestKeyKind{LogData,IngestSST}) are not
|
||||
// supported with this method as they require specialized logic.
|
||||
func (b *Batch) AddInternalKey(key *base.InternalKey, value []byte, _ *WriteOptions) error {
|
||||
keyLen := len(key.UserKey)
|
||||
hasValue := false
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindRangeDelete, InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
panic("unexpected range delete or range key kind in AddInternalKey")
|
||||
case InternalKeyKindSingleDelete, InternalKeyKindDelete:
|
||||
b.prepareDeferredKeyRecord(len(key.UserKey), key.Kind())
|
||||
default:
|
||||
b.prepareDeferredKeyValueRecord(keyLen, len(value), key.Kind())
|
||||
hasValue = true
|
||||
}
|
||||
b.deferredOp.index = b.index
|
||||
copy(b.deferredOp.Key, key.UserKey)
|
||||
if hasValue {
|
||||
copy(b.deferredOp.Value, value)
|
||||
}
|
||||
// TODO(peter): Manually inline DeferredBatchOp.Finish(). Mid-stack inlining
|
||||
// in go1.13 will remove the need for this.
|
||||
if b.index != nil {
|
||||
if err := b.index.Add(b.deferredOp.offset); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set adds an action to the batch that sets the key to map to the value.
|
||||
//
|
||||
// It is safe to modify the contents of the arguments after Set returns.
|
||||
@@ -680,6 +783,72 @@ func (b *Batch) DeleteDeferred(keyLen int) *DeferredBatchOp {
|
||||
return &b.deferredOp
|
||||
}
|
||||
|
||||
// DeleteSized behaves identically to Delete, but takes an additional
|
||||
// argument indicating the size of the value being deleted. DeleteSized
|
||||
// should be preferred when the caller has the expectation that there exists
|
||||
// a single internal KV pair for the key (eg, the key has not been
|
||||
// overwritten recently), and the caller knows the size of its value.
|
||||
//
|
||||
// DeleteSized will record the value size within the tombstone and use it to
|
||||
// inform compaction-picking heuristics which strive to reduce space
|
||||
// amplification in the LSM. This "calling your shot" mechanic allows the
|
||||
// storage engine to more accurately estimate and reduce space amplification.
|
||||
//
|
||||
// It is safe to modify the contents of the arguments after DeleteSized
|
||||
// returns.
|
||||
func (b *Batch) DeleteSized(key []byte, deletedValueSize uint32, _ *WriteOptions) error {
|
||||
deferredOp := b.DeleteSizedDeferred(len(key), deletedValueSize)
|
||||
copy(b.deferredOp.Key, key)
|
||||
// TODO(peter): Manually inline DeferredBatchOp.Finish(). Check if in a
|
||||
// later Go release this is unnecessary.
|
||||
if b.index != nil {
|
||||
if err := b.index.Add(deferredOp.offset); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSizedDeferred is similar to DeleteSized in that it adds a sized delete
|
||||
// operation to the batch, except it only takes in key length instead of a
|
||||
// complete key slice, letting the caller encode into the DeferredBatchOp.Key
|
||||
// slice and then call Finish() on the returned object.
|
||||
func (b *Batch) DeleteSizedDeferred(keyLen int, deletedValueSize uint32) *DeferredBatchOp {
|
||||
if b.minimumFormatMajorVersion < FormatDeleteSizedAndObsolete {
|
||||
b.minimumFormatMajorVersion = FormatDeleteSizedAndObsolete
|
||||
}
|
||||
|
||||
// Encode the sum of the key length and the value in the value.
|
||||
v := uint64(deletedValueSize) + uint64(keyLen)
|
||||
|
||||
// Encode `v` as a varint.
|
||||
var buf [binary.MaxVarintLen64]byte
|
||||
n := 0
|
||||
{
|
||||
x := v
|
||||
for x >= 0x80 {
|
||||
buf[n] = byte(x) | 0x80
|
||||
x >>= 7
|
||||
n++
|
||||
}
|
||||
buf[n] = byte(x)
|
||||
n++
|
||||
}
|
||||
|
||||
// NB: In batch entries and sstable entries, values are stored as
|
||||
// varstrings. Here, the value is itself a simple varint. This results in an
|
||||
// unnecessary double layer of encoding:
|
||||
// varint(n) varint(deletedValueSize)
|
||||
// The first varint will always be 1-byte, since a varint-encoded uint64
|
||||
// will never exceed 128 bytes. This unnecessary extra byte and wrapping is
|
||||
// preserved to avoid special casing across the database, and in particular
|
||||
// in sstable block decoding which is performance sensitive.
|
||||
b.prepareDeferredKeyValueRecord(keyLen, n, InternalKeyKindDeleteSized)
|
||||
b.deferredOp.index = b.index
|
||||
copy(b.deferredOp.Value, buf[:n])
|
||||
return &b.deferredOp
|
||||
}
|
||||
|
||||
// SingleDelete adds an action to the batch that single deletes the entry for key.
|
||||
// See Writer.SingleDelete for more details on the semantics of SingleDelete.
|
||||
//
|
||||
@@ -782,6 +951,9 @@ func (b *Batch) rangeKeySetDeferred(startLen, internalValueLen int) *DeferredBat
|
||||
|
||||
func (b *Batch) incrementRangeKeysCount() {
|
||||
b.countRangeKeys++
|
||||
if b.minimumFormatMajorVersion < FormatRangeKeys {
|
||||
b.minimumFormatMajorVersion = FormatRangeKeys
|
||||
}
|
||||
if b.index != nil {
|
||||
b.rangeKeys = nil
|
||||
b.rangeKeysSeqNum = 0
|
||||
@@ -895,6 +1067,7 @@ func (b *Batch) ingestSST(fileNum base.FileNum) {
|
||||
// is not reset because for the InternalKeyKindIngestSST the count is the
|
||||
// number of sstable paths which have been added to the batch.
|
||||
b.memTableSize = origMemTableSize
|
||||
b.minimumFormatMajorVersion = FormatFlushableIngest
|
||||
}
|
||||
|
||||
// Empty returns true if the batch is empty, and false otherwise.
|
||||
@@ -930,11 +1103,12 @@ func (b *Batch) SetRepr(data []byte) error {
|
||||
}
|
||||
b.data = data
|
||||
b.count = uint64(binary.LittleEndian.Uint32(b.countData()))
|
||||
var err error
|
||||
if b.db != nil {
|
||||
// Only track memTableSize for batches that will be committed to the DB.
|
||||
b.refreshMemTableSize()
|
||||
err = b.refreshMemTableSize()
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// NewIter returns an iterator that is unpositioned (Iterator.Valid() will
|
||||
@@ -944,8 +1118,8 @@ func (b *Batch) SetRepr(data []byte) error {
|
||||
// The returned Iterator observes all of the Batch's existing mutations, but no
|
||||
// later mutations. Its view can be refreshed via RefreshBatchSnapshot or
|
||||
// SetOptions().
|
||||
func (b *Batch) NewIter(o *IterOptions) *Iterator {
|
||||
return b.NewIterWithContext(context.Background(), o)
|
||||
func (b *Batch) NewIter(o *IterOptions) (*Iterator, error) {
|
||||
return b.NewIterWithContext(context.Background(), o), nil
|
||||
}
|
||||
|
||||
// NewIterWithContext is like NewIter, and additionally accepts a context for
|
||||
@@ -954,7 +1128,7 @@ func (b *Batch) NewIterWithContext(ctx context.Context, o *IterOptions) *Iterato
|
||||
if b.index == nil {
|
||||
return &Iterator{err: ErrNotIndexed}
|
||||
}
|
||||
return b.db.newIter(ctx, b, nil /* snapshot */, o)
|
||||
return b.db.newIter(ctx, b, snapshotIterOpts{}, o)
|
||||
}
|
||||
|
||||
// newInternalIter creates a new internalIterator that iterates over the
|
||||
@@ -1167,37 +1341,39 @@ func (b *Batch) Indexed() bool {
|
||||
return b.index != nil
|
||||
}
|
||||
|
||||
func (b *Batch) init(cap int) {
|
||||
// init ensures that the batch data slice is initialized to meet the
|
||||
// minimum required size and allocates space for the batch header.
|
||||
func (b *Batch) init(size int) {
|
||||
n := batchInitialSize
|
||||
for n < cap {
|
||||
for n < size {
|
||||
n *= 2
|
||||
}
|
||||
b.data = rawalloc.New(batchHeaderLen, n)
|
||||
b.setCount(0)
|
||||
b.setSeqNum(0)
|
||||
if cap(b.data) < n {
|
||||
b.data = rawalloc.New(batchHeaderLen, n)
|
||||
}
|
||||
b.data = b.data[:batchHeaderLen]
|
||||
// Zero the sequence number in the header.
|
||||
for i := 0; i < len(b.data); i++ {
|
||||
b.data[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the batch for reuse. The underlying byte slice (that is
|
||||
// returned by Repr()) is not modified. It is only necessary to call this
|
||||
// returned by Repr()) may not be modified. It is only necessary to call this
|
||||
// method if a batch is explicitly being reused. Close automatically takes are
|
||||
// of releasing resources when appropriate for batches that are internally
|
||||
// being reused.
|
||||
func (b *Batch) Reset() {
|
||||
b.count = 0
|
||||
b.countRangeDels = 0
|
||||
b.countRangeKeys = 0
|
||||
b.memTableSize = 0
|
||||
b.deferredOp = DeferredBatchOp{}
|
||||
b.tombstones = nil
|
||||
b.tombstonesSeqNum = 0
|
||||
b.rangeKeys = nil
|
||||
b.rangeKeysSeqNum = 0
|
||||
b.flushable = nil
|
||||
b.commit = sync.WaitGroup{}
|
||||
b.fsyncWait = sync.WaitGroup{}
|
||||
b.commitStats = BatchCommitStats{}
|
||||
b.commitErr = nil
|
||||
// Zero out the struct, retaining only the fields necessary for manual
|
||||
// reuse.
|
||||
b.batchInternal = batchInternal{
|
||||
data: b.data,
|
||||
cmp: b.cmp,
|
||||
formatKey: b.formatKey,
|
||||
abbreviatedKey: b.abbreviatedKey,
|
||||
index: b.index,
|
||||
db: b.db,
|
||||
}
|
||||
b.applied.Store(false)
|
||||
if b.data != nil {
|
||||
if cap(b.data) > batchMaxRetainedSize {
|
||||
@@ -1209,13 +1385,14 @@ func (b *Batch) Reset() {
|
||||
} else {
|
||||
// Otherwise, reset the buffer for re-use.
|
||||
b.data = b.data[:batchHeaderLen]
|
||||
b.setSeqNum(0)
|
||||
// Zero the sequence number in the header.
|
||||
for i := 0; i < len(b.data); i++ {
|
||||
b.data[i] = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
if b.index != nil {
|
||||
b.index.Init(&b.data, b.cmp, b.abbreviatedKey)
|
||||
b.rangeDelIndex = nil
|
||||
b.rangeKeyIndex = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1287,6 +1464,11 @@ func (b *Batch) Reader() BatchReader {
|
||||
}
|
||||
|
||||
func batchDecodeStr(data []byte) (odata []byte, s []byte, ok bool) {
|
||||
// TODO(jackson): This will index out of bounds if there's no varint or an
|
||||
// invalid varint (eg, a single 0xff byte). Correcting will add a bit of
|
||||
// overhead. We could avoid that overhead whenever len(data) >=
|
||||
// binary.MaxVarint32?
|
||||
|
||||
var v uint32
|
||||
var n int
|
||||
ptr := unsafe.Pointer(&data[0])
|
||||
@@ -1349,29 +1531,32 @@ func ReadBatch(repr []byte) (r BatchReader, count uint32) {
|
||||
return repr[batchHeaderLen:], count
|
||||
}
|
||||
|
||||
// Next returns the next entry in this batch. The final return value is false
|
||||
// if the batch is corrupt. The end of batch is reached when len(r)==0.
|
||||
func (r *BatchReader) Next() (kind InternalKeyKind, ukey []byte, value []byte, ok bool) {
|
||||
// Next returns the next entry in this batch, if there is one. If the reader has
|
||||
// reached the end of the batch, Next returns ok=false and a nil error. If the
|
||||
// batch is corrupt and the next entry is illegible, Next returns ok=false and a
|
||||
// non-nil error.
|
||||
func (r *BatchReader) Next() (kind InternalKeyKind, ukey []byte, value []byte, ok bool, err error) {
|
||||
if len(*r) == 0 {
|
||||
return 0, nil, nil, false
|
||||
return 0, nil, nil, false, nil
|
||||
}
|
||||
kind = InternalKeyKind((*r)[0])
|
||||
if kind > InternalKeyKindMax {
|
||||
return 0, nil, nil, false
|
||||
return 0, nil, nil, false, errors.Wrapf(ErrInvalidBatch, "invalid key kind 0x%x", (*r)[0])
|
||||
}
|
||||
*r, ukey, ok = batchDecodeStr((*r)[1:])
|
||||
if !ok {
|
||||
return 0, nil, nil, false
|
||||
return 0, nil, nil, false, errors.Wrapf(ErrInvalidBatch, "decoding user key")
|
||||
}
|
||||
switch kind {
|
||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
||||
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete,
|
||||
InternalKeyKindDeleteSized:
|
||||
*r, value, ok = batchDecodeStr(*r)
|
||||
if !ok {
|
||||
return 0, nil, nil, false
|
||||
return 0, nil, nil, false, errors.Wrapf(ErrInvalidBatch, "decoding %s value", kind)
|
||||
}
|
||||
}
|
||||
return kind, ukey, value, true
|
||||
return kind, ukey, value, true, nil
|
||||
}
|
||||
|
||||
// Note: batchIter mirrors the implementation of flushableBatchIter. Keep the
|
||||
@@ -1500,7 +1685,8 @@ func (i *batchIter) value() []byte {
|
||||
|
||||
switch InternalKeyKind(data[offset]) {
|
||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
||||
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete,
|
||||
InternalKeyKindDeleteSized:
|
||||
_, value, ok := batchDecodeStr(data[keyEnd:])
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -1572,7 +1758,7 @@ var _ flushable = (*flushableBatch)(nil)
|
||||
// interface. This allows the batch to act like a memtable and be placed in the
|
||||
// queue of flushable memtables. Note that the flushable batch takes ownership
|
||||
// of the batch data.
|
||||
func newFlushableBatch(batch *Batch, comparer *Comparer) *flushableBatch {
|
||||
func newFlushableBatch(batch *Batch, comparer *Comparer) (*flushableBatch, error) {
|
||||
b := &flushableBatch{
|
||||
data: batch.data,
|
||||
cmp: comparer.Compare,
|
||||
@@ -1593,8 +1779,11 @@ func newFlushableBatch(batch *Batch, comparer *Comparer) *flushableBatch {
|
||||
var index uint32
|
||||
for iter := BatchReader(b.data[batchHeaderLen:]); len(iter) > 0; index++ {
|
||||
offset := uintptr(unsafe.Pointer(&iter[0])) - uintptr(unsafe.Pointer(&b.data[0]))
|
||||
kind, key, _, ok := iter.Next()
|
||||
kind, key, _, ok, err := iter.Next()
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
entry := flushableBatchEntry{
|
||||
@@ -1666,7 +1855,7 @@ func newFlushableBatch(batch *Batch, comparer *Comparer) *flushableBatch {
|
||||
}
|
||||
fragmentRangeKeys(frag, it, len(rangeKeyOffsets))
|
||||
}
|
||||
return b
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (b *flushableBatch) setSeqNum(seqNum uint64) {
|
||||
@@ -1715,6 +1904,7 @@ func (b *flushableBatch) Swap(i, j int) {
|
||||
b.offsets[i], b.offsets[j] = b.offsets[j], b.offsets[i]
|
||||
}
|
||||
|
||||
// newIter is part of the flushable interface.
|
||||
func (b *flushableBatch) newIter(o *IterOptions) internalIterator {
|
||||
return &flushableBatchIter{
|
||||
batch: b,
|
||||
@@ -1727,6 +1917,7 @@ func (b *flushableBatch) newIter(o *IterOptions) internalIterator {
|
||||
}
|
||||
}
|
||||
|
||||
// newFlushIter is part of the flushable interface.
|
||||
func (b *flushableBatch) newFlushIter(o *IterOptions, bytesFlushed *uint64) internalIterator {
|
||||
return &flushFlushableBatchIter{
|
||||
flushableBatchIter: flushableBatchIter{
|
||||
@@ -1740,6 +1931,7 @@ func (b *flushableBatch) newFlushIter(o *IterOptions, bytesFlushed *uint64) inte
|
||||
}
|
||||
}
|
||||
|
||||
// newRangeDelIter is part of the flushable interface.
|
||||
func (b *flushableBatch) newRangeDelIter(o *IterOptions) keyspan.FragmentIterator {
|
||||
if len(b.tombstones) == 0 {
|
||||
return nil
|
||||
@@ -1747,6 +1939,7 @@ func (b *flushableBatch) newRangeDelIter(o *IterOptions) keyspan.FragmentIterato
|
||||
return keyspan.NewIter(b.cmp, b.tombstones)
|
||||
}
|
||||
|
||||
// newRangeKeyIter is part of the flushable interface.
|
||||
func (b *flushableBatch) newRangeKeyIter(o *IterOptions) keyspan.FragmentIterator {
|
||||
if len(b.rangeKeys) == 0 {
|
||||
return nil
|
||||
@@ -1754,17 +1947,23 @@ func (b *flushableBatch) newRangeKeyIter(o *IterOptions) keyspan.FragmentIterato
|
||||
return keyspan.NewIter(b.cmp, b.rangeKeys)
|
||||
}
|
||||
|
||||
// containsRangeKeys is part of the flushable interface.
|
||||
func (b *flushableBatch) containsRangeKeys() bool { return len(b.rangeKeys) > 0 }
|
||||
|
||||
// inuseBytes is part of the flushable interface.
|
||||
func (b *flushableBatch) inuseBytes() uint64 {
|
||||
return uint64(len(b.data) - batchHeaderLen)
|
||||
}
|
||||
|
||||
// totalBytes is part of the flushable interface.
|
||||
func (b *flushableBatch) totalBytes() uint64 {
|
||||
return uint64(cap(b.data))
|
||||
}
|
||||
|
||||
// readyForFlush is part of the flushable interface.
|
||||
func (b *flushableBatch) readyForFlush() bool {
|
||||
// A flushable batch is always ready for flush; it must be flushed together
|
||||
// with the previous memtable.
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1944,7 +2143,8 @@ func (i *flushableBatchIter) value() base.LazyValue {
|
||||
var ok bool
|
||||
switch kind {
|
||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
||||
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete,
|
||||
InternalKeyKindDeleteSized:
|
||||
keyEnd := i.offsets[i.index].keyEnd
|
||||
_, value, ok = batchDecodeStr(i.data[keyEnd:])
|
||||
if !ok {
|
||||
@@ -2081,7 +2281,10 @@ func batchSort(
|
||||
rangeKeyIter := b.newRangeKeyIter(nil, math.MaxUint64)
|
||||
return pointIter, rangeDelIter, rangeKeyIter
|
||||
}
|
||||
f := newFlushableBatch(b, b.db.opts.Comparer)
|
||||
f, err := newFlushableBatch(b, b.db.opts.Comparer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return f.newIter(nil), f.newRangeDelIter(nil), f.newRangeKeyIter(nil)
|
||||
}
|
||||
|
||||
|
||||
13
vendor/github.com/cockroachdb/pebble/checkpoint.go
generated
vendored
13
vendor/github.com/cockroachdb/pebble/checkpoint.go
generated
vendored
@@ -135,9 +135,6 @@ func mkdirAllAndSyncParents(fs vfs.FS, destDir string) (vfs.File, error) {
|
||||
// space overhead for a checkpoint if hard links are disabled. Also beware that
|
||||
// even if hard links are used, the space overhead for the checkpoint will
|
||||
// increase over time as the DB performs compactions.
|
||||
//
|
||||
// TODO(bananabrick): Test checkpointing of virtual sstables once virtual
|
||||
// sstables is running e2e.
|
||||
func (d *DB) Checkpoint(
|
||||
destDir string, opts ...CheckpointOption,
|
||||
) (
|
||||
@@ -187,12 +184,12 @@ func (d *DB) Checkpoint(
|
||||
// file number.
|
||||
memQueue := d.mu.mem.queue
|
||||
current := d.mu.versions.currentVersion()
|
||||
formatVers := d.mu.formatVers.vers
|
||||
formatVers := d.FormatMajorVersion()
|
||||
manifestFileNum := d.mu.versions.manifestFileNum
|
||||
manifestSize := d.mu.versions.manifest.Size()
|
||||
optionsFileNum := d.optionsFileNum
|
||||
virtualBackingFiles := make(map[base.DiskFileNum]struct{})
|
||||
for diskFileNum := range d.mu.versions.fileBackingMap {
|
||||
for diskFileNum := range d.mu.versions.backingState.fileBackingMap {
|
||||
virtualBackingFiles[diskFileNum] = struct{}{}
|
||||
}
|
||||
// Release the manifest and DB.mu so we don't block other operations on
|
||||
@@ -215,11 +212,7 @@ func (d *DB) Checkpoint(
|
||||
}
|
||||
if ckErr != nil {
|
||||
// Attempt to cleanup on error.
|
||||
paths, _ := fs.List(destDir)
|
||||
for _, path := range paths {
|
||||
_ = fs.Remove(path)
|
||||
}
|
||||
_ = fs.Remove(destDir)
|
||||
_ = fs.RemoveAll(destDir)
|
||||
}
|
||||
}()
|
||||
dir, ckErr = mkdirAllAndSyncParents(fs, destDir)
|
||||
|
||||
281
vendor/github.com/cockroachdb/pebble/cleaner.go
generated
vendored
281
vendor/github.com/cockroachdb/pebble/cleaner.go
generated
vendored
@@ -4,7 +4,18 @@
|
||||
|
||||
package pebble
|
||||
|
||||
import "github.com/cockroachdb/pebble/internal/base"
|
||||
import (
|
||||
"context"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/errors/oserror"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/tokenbucket"
|
||||
)
|
||||
|
||||
// Cleaner exports the base.Cleaner type.
|
||||
type Cleaner = base.Cleaner
|
||||
@@ -14,3 +25,271 @@ type DeleteCleaner = base.DeleteCleaner
|
||||
|
||||
// ArchiveCleaner exports the base.ArchiveCleaner type.
|
||||
type ArchiveCleaner = base.ArchiveCleaner
|
||||
|
||||
type cleanupManager struct {
|
||||
opts *Options
|
||||
objProvider objstorage.Provider
|
||||
onTableDeleteFn func(fileSize uint64)
|
||||
deletePacer *deletionPacer
|
||||
|
||||
// jobsCh is used as the cleanup job queue.
|
||||
jobsCh chan *cleanupJob
|
||||
// waitGroup is used to wait for the background goroutine to exit.
|
||||
waitGroup sync.WaitGroup
|
||||
|
||||
mu struct {
|
||||
sync.Mutex
|
||||
// totalJobs is the total number of enqueued jobs (completed or in progress).
|
||||
totalJobs int
|
||||
completedJobs int
|
||||
completedJobsCond sync.Cond
|
||||
jobsQueueWarningIssued bool
|
||||
}
|
||||
}
|
||||
|
||||
// We can queue this many jobs before we have to block EnqueueJob.
|
||||
const jobsQueueDepth = 1000
|
||||
|
||||
// obsoleteFile holds information about a file that needs to be deleted soon.
|
||||
type obsoleteFile struct {
|
||||
dir string
|
||||
fileNum base.DiskFileNum
|
||||
fileType fileType
|
||||
fileSize uint64
|
||||
}
|
||||
|
||||
type cleanupJob struct {
|
||||
jobID int
|
||||
obsoleteFiles []obsoleteFile
|
||||
}
|
||||
|
||||
// openCleanupManager creates a cleanupManager and starts its background goroutine.
|
||||
// The cleanupManager must be Close()d.
|
||||
func openCleanupManager(
|
||||
opts *Options,
|
||||
objProvider objstorage.Provider,
|
||||
onTableDeleteFn func(fileSize uint64),
|
||||
getDeletePacerInfo func() deletionPacerInfo,
|
||||
) *cleanupManager {
|
||||
cm := &cleanupManager{
|
||||
opts: opts,
|
||||
objProvider: objProvider,
|
||||
onTableDeleteFn: onTableDeleteFn,
|
||||
deletePacer: newDeletionPacer(time.Now(), int64(opts.TargetByteDeletionRate), getDeletePacerInfo),
|
||||
jobsCh: make(chan *cleanupJob, jobsQueueDepth),
|
||||
}
|
||||
cm.mu.completedJobsCond.L = &cm.mu.Mutex
|
||||
cm.waitGroup.Add(1)
|
||||
|
||||
go func() {
|
||||
pprof.Do(context.Background(), gcLabels, func(context.Context) {
|
||||
cm.mainLoop()
|
||||
})
|
||||
}()
|
||||
|
||||
return cm
|
||||
}
|
||||
|
||||
// Close stops the background goroutine, waiting until all queued jobs are completed.
|
||||
// Delete pacing is disabled for the remaining jobs.
|
||||
func (cm *cleanupManager) Close() {
|
||||
close(cm.jobsCh)
|
||||
cm.waitGroup.Wait()
|
||||
}
|
||||
|
||||
// EnqueueJob adds a cleanup job to the manager's queue.
|
||||
func (cm *cleanupManager) EnqueueJob(jobID int, obsoleteFiles []obsoleteFile) {
|
||||
job := &cleanupJob{
|
||||
jobID: jobID,
|
||||
obsoleteFiles: obsoleteFiles,
|
||||
}
|
||||
|
||||
// Report deleted bytes to the pacer, which can use this data to potentially
|
||||
// increase the deletion rate to keep up. We want to do this at enqueue time
|
||||
// rather than when we get to the job, otherwise the reported bytes will be
|
||||
// subject to the throttling rate which defeats the purpose.
|
||||
var pacingBytes uint64
|
||||
for _, of := range obsoleteFiles {
|
||||
if cm.needsPacing(of.fileType, of.fileNum) {
|
||||
pacingBytes += of.fileSize
|
||||
}
|
||||
}
|
||||
if pacingBytes > 0 {
|
||||
cm.deletePacer.ReportDeletion(time.Now(), pacingBytes)
|
||||
}
|
||||
|
||||
cm.mu.Lock()
|
||||
cm.mu.totalJobs++
|
||||
cm.maybeLogLocked()
|
||||
cm.mu.Unlock()
|
||||
|
||||
if invariants.Enabled && len(cm.jobsCh) >= cap(cm.jobsCh)-2 {
|
||||
panic("cleanup jobs queue full")
|
||||
}
|
||||
|
||||
cm.jobsCh <- job
|
||||
}
|
||||
|
||||
// Wait until the completion of all jobs that were already queued.
|
||||
//
|
||||
// Does not wait for jobs that are enqueued during the call.
|
||||
//
|
||||
// Note that DB.mu should not be held while calling this method; the background
|
||||
// goroutine needs to acquire DB.mu to update deleted table metrics.
|
||||
func (cm *cleanupManager) Wait() {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
n := cm.mu.totalJobs
|
||||
for cm.mu.completedJobs < n {
|
||||
cm.mu.completedJobsCond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// mainLoop runs the manager's background goroutine.
|
||||
func (cm *cleanupManager) mainLoop() {
|
||||
defer cm.waitGroup.Done()
|
||||
|
||||
var tb tokenbucket.TokenBucket
|
||||
// Use a token bucket with 1 token / second refill rate and 1 token burst.
|
||||
tb.Init(1.0, 1.0)
|
||||
for job := range cm.jobsCh {
|
||||
for _, of := range job.obsoleteFiles {
|
||||
if of.fileType != fileTypeTable {
|
||||
path := base.MakeFilepath(cm.opts.FS, of.dir, of.fileType, of.fileNum)
|
||||
cm.deleteObsoleteFile(of.fileType, job.jobID, path, of.fileNum, of.fileSize)
|
||||
} else {
|
||||
cm.maybePace(&tb, of.fileType, of.fileNum, of.fileSize)
|
||||
cm.onTableDeleteFn(of.fileSize)
|
||||
cm.deleteObsoleteObject(fileTypeTable, job.jobID, of.fileNum)
|
||||
}
|
||||
}
|
||||
cm.mu.Lock()
|
||||
cm.mu.completedJobs++
|
||||
cm.mu.completedJobsCond.Broadcast()
|
||||
cm.maybeLogLocked()
|
||||
cm.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *cleanupManager) needsPacing(fileType base.FileType, fileNum base.DiskFileNum) bool {
|
||||
if fileType != fileTypeTable {
|
||||
return false
|
||||
}
|
||||
meta, err := cm.objProvider.Lookup(fileType, fileNum)
|
||||
if err != nil {
|
||||
// The object was already removed from the provider; we won't actually
|
||||
// delete anything, so we don't need to pace.
|
||||
return false
|
||||
}
|
||||
// Don't throttle deletion of remote objects.
|
||||
return !meta.IsRemote()
|
||||
}
|
||||
|
||||
// maybePace sleeps before deleting an object if appropriate. It is always
|
||||
// called from the background goroutine.
|
||||
func (cm *cleanupManager) maybePace(
|
||||
tb *tokenbucket.TokenBucket, fileType base.FileType, fileNum base.DiskFileNum, fileSize uint64,
|
||||
) {
|
||||
if !cm.needsPacing(fileType, fileNum) {
|
||||
return
|
||||
}
|
||||
|
||||
tokens := cm.deletePacer.PacingDelay(time.Now(), fileSize)
|
||||
if tokens == 0.0 {
|
||||
// The token bucket might be in debt; it could make us wait even for 0
|
||||
// tokens. We don't want that if the pacer decided throttling should be
|
||||
// disabled.
|
||||
return
|
||||
}
|
||||
// Wait for tokens. We use a token bucket instead of sleeping outright because
|
||||
// the token bucket accumulates up to one second of unused tokens.
|
||||
for {
|
||||
ok, d := tb.TryToFulfill(tokenbucket.Tokens(tokens))
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
time.Sleep(d)
|
||||
}
|
||||
}
|
||||
|
||||
// deleteObsoleteFile deletes a (non-object) file that is no longer needed.
|
||||
func (cm *cleanupManager) deleteObsoleteFile(
|
||||
fileType fileType, jobID int, path string, fileNum base.DiskFileNum, fileSize uint64,
|
||||
) {
|
||||
// TODO(peter): need to handle this error, probably by re-adding the
|
||||
// file that couldn't be deleted to one of the obsolete slices map.
|
||||
err := cm.opts.Cleaner.Clean(cm.opts.FS, fileType, path)
|
||||
if oserror.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
|
||||
switch fileType {
|
||||
case fileTypeLog:
|
||||
cm.opts.EventListener.WALDeleted(WALDeleteInfo{
|
||||
JobID: jobID,
|
||||
Path: path,
|
||||
FileNum: fileNum.FileNum(),
|
||||
Err: err,
|
||||
})
|
||||
case fileTypeManifest:
|
||||
cm.opts.EventListener.ManifestDeleted(ManifestDeleteInfo{
|
||||
JobID: jobID,
|
||||
Path: path,
|
||||
FileNum: fileNum.FileNum(),
|
||||
Err: err,
|
||||
})
|
||||
case fileTypeTable:
|
||||
panic("invalid deletion of object file")
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *cleanupManager) deleteObsoleteObject(
|
||||
fileType fileType, jobID int, fileNum base.DiskFileNum,
|
||||
) {
|
||||
if fileType != fileTypeTable {
|
||||
panic("not an object")
|
||||
}
|
||||
|
||||
var path string
|
||||
meta, err := cm.objProvider.Lookup(fileType, fileNum)
|
||||
if err != nil {
|
||||
path = "<nil>"
|
||||
} else {
|
||||
path = cm.objProvider.Path(meta)
|
||||
err = cm.objProvider.Remove(fileType, fileNum)
|
||||
}
|
||||
if cm.objProvider.IsNotExistError(err) {
|
||||
return
|
||||
}
|
||||
|
||||
switch fileType {
|
||||
case fileTypeTable:
|
||||
cm.opts.EventListener.TableDeleted(TableDeleteInfo{
|
||||
JobID: jobID,
|
||||
Path: path,
|
||||
FileNum: fileNum.FileNum(),
|
||||
Err: err,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// maybeLogLocked issues a log if the job queue gets 75% full and issues a log
|
||||
// when the job queue gets back to less than 10% full.
|
||||
//
|
||||
// Must be called with cm.mu locked.
|
||||
func (cm *cleanupManager) maybeLogLocked() {
|
||||
const highThreshold = jobsQueueDepth * 3 / 4
|
||||
const lowThreshold = jobsQueueDepth / 10
|
||||
|
||||
jobsInQueue := cm.mu.totalJobs - cm.mu.completedJobs
|
||||
|
||||
if !cm.mu.jobsQueueWarningIssued && jobsInQueue > highThreshold {
|
||||
cm.mu.jobsQueueWarningIssued = true
|
||||
cm.opts.Logger.Infof("cleanup falling behind; job queue has over %d jobs", highThreshold)
|
||||
}
|
||||
|
||||
if cm.mu.jobsQueueWarningIssued && jobsInQueue < lowThreshold {
|
||||
cm.mu.jobsQueueWarningIssued = false
|
||||
cm.opts.Logger.Infof("cleanup back to normal; job queue has under %d jobs", lowThreshold)
|
||||
}
|
||||
}
|
||||
|
||||
29
vendor/github.com/cockroachdb/pebble/commit.go
generated
vendored
29
vendor/github.com/cockroachdb/pebble/commit.go
generated
vendored
@@ -9,7 +9,6 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/pebble/record"
|
||||
)
|
||||
@@ -39,7 +38,7 @@ type commitQueue struct {
|
||||
// power of 2. A slot is in use until *both* the tail index has moved beyond
|
||||
// it and the slot value has been set to nil. The slot value is set to nil
|
||||
// atomically by the consumer and read atomically by the producer.
|
||||
slots [record.SyncConcurrency]unsafe.Pointer
|
||||
slots [record.SyncConcurrency]atomic.Pointer[Batch]
|
||||
}
|
||||
|
||||
const dequeueBits = 32
|
||||
@@ -67,8 +66,8 @@ func (q *commitQueue) enqueue(b *Batch) {
|
||||
}
|
||||
slot := &q.slots[head&uint32(len(q.slots)-1)]
|
||||
|
||||
// Check if the head slot has been released by dequeue.
|
||||
for atomic.LoadPointer(slot) != nil {
|
||||
// Check if the head slot has been released by dequeueApplied.
|
||||
for slot.Load() != nil {
|
||||
// Another goroutine is still cleaning up the tail, so the queue is
|
||||
// actually still full. We spin because this should resolve itself
|
||||
// momentarily.
|
||||
@@ -76,14 +75,18 @@ func (q *commitQueue) enqueue(b *Batch) {
|
||||
}
|
||||
|
||||
// The head slot is free, so we own it.
|
||||
atomic.StorePointer(slot, unsafe.Pointer(b))
|
||||
slot.Store(b)
|
||||
|
||||
// Increment head. This passes ownership of slot to dequeue and acts as a
|
||||
// Increment head. This passes ownership of slot to dequeueApplied and acts as a
|
||||
// store barrier for writing the slot.
|
||||
q.headTail.Add(1 << dequeueBits)
|
||||
}
|
||||
|
||||
func (q *commitQueue) dequeue() *Batch {
|
||||
// dequeueApplied removes the earliest enqueued Batch, if it is applied.
|
||||
//
|
||||
// Returns nil if the commit queue is empty or the earliest Batch is not yet
|
||||
// applied.
|
||||
func (q *commitQueue) dequeueApplied() *Batch {
|
||||
for {
|
||||
ptrs := q.headTail.Load()
|
||||
head, tail := q.unpack(ptrs)
|
||||
@@ -93,7 +96,7 @@ func (q *commitQueue) dequeue() *Batch {
|
||||
}
|
||||
|
||||
slot := &q.slots[tail&uint32(len(q.slots)-1)]
|
||||
b := (*Batch)(atomic.LoadPointer(slot))
|
||||
b := slot.Load()
|
||||
if b == nil || !b.applied.Load() {
|
||||
// The batch is not ready to be dequeued, or another goroutine has
|
||||
// already dequeued it.
|
||||
@@ -109,7 +112,7 @@ func (q *commitQueue) dequeue() *Batch {
|
||||
// Tell enqueue that we're done with this slot. Zeroing the slot is also
|
||||
// important so we don't leave behind references that could keep this object
|
||||
// live longer than necessary.
|
||||
atomic.StorePointer(slot, nil)
|
||||
slot.Store(nil)
|
||||
// At this point enqueue owns the slot.
|
||||
return b
|
||||
}
|
||||
@@ -239,6 +242,7 @@ type commitPipeline struct {
|
||||
// logSyncQSem are used for this reservation.
|
||||
commitQueueSem chan struct{}
|
||||
logSyncQSem chan struct{}
|
||||
ingestSem chan struct{}
|
||||
// The mutex to use for synchronizing access to logSeqNum and serializing
|
||||
// calls to commitEnv.write().
|
||||
mu sync.Mutex
|
||||
@@ -263,6 +267,7 @@ func newCommitPipeline(env commitEnv) *commitPipeline {
|
||||
// and sync the WAL.
|
||||
commitQueueSem: make(chan struct{}, record.SyncConcurrency-1),
|
||||
logSyncQSem: make(chan struct{}, record.SyncConcurrency-1),
|
||||
ingestSem: make(chan struct{}, 1),
|
||||
}
|
||||
return p
|
||||
}
|
||||
@@ -471,13 +476,13 @@ func (p *commitPipeline) publish(b *Batch) {
|
||||
|
||||
// Loop dequeuing applied batches from the pending queue. If our batch was
|
||||
// the head of the pending queue we are guaranteed that either we'll publish
|
||||
// it or someone else will dequeue and publish it. If our batch is not the
|
||||
// head of the queue then either we'll dequeue applied batches and reach our
|
||||
// it or someone else will dequeueApplied and publish it. If our batch is not the
|
||||
// head of the queue then either we'll dequeueApplied applied batches and reach our
|
||||
// batch or there is an unapplied batch blocking us. When that unapplied
|
||||
// batch applies it will go through the same process and publish our batch
|
||||
// for us.
|
||||
for {
|
||||
t := p.pending.dequeue()
|
||||
t := p.pending.dequeueApplied()
|
||||
if t == nil {
|
||||
// Wait for another goroutine to publish us. We might also be waiting for
|
||||
// the WAL sync to finish.
|
||||
|
||||
1275
vendor/github.com/cockroachdb/pebble/compaction.go
generated
vendored
1275
vendor/github.com/cockroachdb/pebble/compaction.go
generated
vendored
File diff suppressed because it is too large
Load Diff
698
vendor/github.com/cockroachdb/pebble/compaction_iter.go
generated
vendored
698
vendor/github.com/cockroachdb/pebble/compaction_iter.go
generated
vendored
@@ -6,6 +6,7 @@ package pebble
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
@@ -14,9 +15,9 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/bytealloc"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||
"github.com/cockroachdb/pebble/internal/rangekey"
|
||||
"github.com/cockroachdb/redact"
|
||||
)
|
||||
|
||||
// compactionIter provides a forward-only iterator that encapsulates the logic
|
||||
@@ -205,7 +206,29 @@ type compactionIter struct {
|
||||
// compaction iterator was only returned because an open snapshot prevents
|
||||
// its elision. This field only applies to point keys, and not to range
|
||||
// deletions or range keys.
|
||||
//
|
||||
// For MERGE, it is possible that doing the merge is interrupted even when
|
||||
// the next point key is in the same stripe. This can happen if the loop in
|
||||
// mergeNext gets interrupted by sameStripeNonSkippable.
|
||||
// sameStripeNonSkippable occurs due to RANGEDELs that sort before
|
||||
// SET/MERGE/DEL with the same seqnum, so the RANGEDEL does not necessarily
|
||||
// delete the subsequent SET/MERGE/DEL keys.
|
||||
snapshotPinned bool
|
||||
// forceObsoleteDueToRangeDel is set to true in a subset of the cases that
|
||||
// snapshotPinned is true. This value is true when the point is obsolete due
|
||||
// to a RANGEDEL but could not be deleted due to a snapshot.
|
||||
//
|
||||
// NB: it may seem that the additional cases that snapshotPinned captures
|
||||
// are harmless in that they can also be used to mark a point as obsolete
|
||||
// (it is merely a duplication of some logic that happens in
|
||||
// Writer.AddWithForceObsolete), but that is not quite accurate as of this
|
||||
// writing -- snapshotPinned originated in stats collection and for a
|
||||
// sequence MERGE, SET, where the MERGE cannot merge with the (older) SET
|
||||
// due to a snapshot, the snapshotPinned value for the SET is true.
|
||||
//
|
||||
// TODO(sumeer,jackson): improve the logic of snapshotPinned and reconsider
|
||||
// whether we need forceObsoleteDueToRangeDel.
|
||||
forceObsoleteDueToRangeDel bool
|
||||
// The index of the snapshot for the current key within the snapshots slice.
|
||||
curSnapshotIdx int
|
||||
curSnapshotSeqNum uint64
|
||||
@@ -233,13 +256,19 @@ type compactionIter struct {
|
||||
// The fragmented range keys.
|
||||
rangeKeys []keyspan.Span
|
||||
// Byte allocator for the tombstone keys.
|
||||
alloc bytealloc.A
|
||||
allowZeroSeqNum bool
|
||||
elideTombstone func(key []byte) bool
|
||||
elideRangeTombstone func(start, end []byte) bool
|
||||
alloc bytealloc.A
|
||||
allowZeroSeqNum bool
|
||||
elideTombstone func(key []byte) bool
|
||||
elideRangeTombstone func(start, end []byte) bool
|
||||
ineffectualSingleDeleteCallback func(userKey []byte)
|
||||
singleDeleteInvariantViolationCallback func(userKey []byte)
|
||||
// The on-disk format major version. This informs the types of keys that
|
||||
// may be written to disk during a compaction.
|
||||
formatVersion FormatMajorVersion
|
||||
stats struct {
|
||||
// count of DELSIZED keys that were missized.
|
||||
countMissizedDels uint64
|
||||
}
|
||||
}
|
||||
|
||||
func newCompactionIter(
|
||||
@@ -254,20 +283,24 @@ func newCompactionIter(
|
||||
allowZeroSeqNum bool,
|
||||
elideTombstone func(key []byte) bool,
|
||||
elideRangeTombstone func(start, end []byte) bool,
|
||||
ineffectualSingleDeleteCallback func(userKey []byte),
|
||||
singleDeleteInvariantViolationCallback func(userKey []byte),
|
||||
formatVersion FormatMajorVersion,
|
||||
) *compactionIter {
|
||||
i := &compactionIter{
|
||||
equal: equal,
|
||||
merge: merge,
|
||||
iter: iter,
|
||||
snapshots: snapshots,
|
||||
frontiers: frontiers{cmp: cmp},
|
||||
rangeDelFrag: rangeDelFrag,
|
||||
rangeKeyFrag: rangeKeyFrag,
|
||||
allowZeroSeqNum: allowZeroSeqNum,
|
||||
elideTombstone: elideTombstone,
|
||||
elideRangeTombstone: elideRangeTombstone,
|
||||
formatVersion: formatVersion,
|
||||
equal: equal,
|
||||
merge: merge,
|
||||
iter: iter,
|
||||
snapshots: snapshots,
|
||||
frontiers: frontiers{cmp: cmp},
|
||||
rangeDelFrag: rangeDelFrag,
|
||||
rangeKeyFrag: rangeKeyFrag,
|
||||
allowZeroSeqNum: allowZeroSeqNum,
|
||||
elideTombstone: elideTombstone,
|
||||
elideRangeTombstone: elideRangeTombstone,
|
||||
ineffectualSingleDeleteCallback: ineffectualSingleDeleteCallback,
|
||||
singleDeleteInvariantViolationCallback: singleDeleteInvariantViolationCallback,
|
||||
formatVersion: formatVersion,
|
||||
}
|
||||
i.rangeDelFrag.Cmp = cmp
|
||||
i.rangeDelFrag.Format = formatKey
|
||||
@@ -306,21 +339,43 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Prior to this call to `Next()` we are in one of three situations with
|
||||
// Prior to this call to `Next()` we are in one of four situations with
|
||||
// respect to `iterKey` and related state:
|
||||
//
|
||||
// - `!skip && pos == iterPosNext`: `iterKey` is already at the next key.
|
||||
// - `!skip && pos == iterPosCur`: We are at the key that has been returned.
|
||||
// - `!skip && pos == iterPosCurForward`: We are at the key that has been returned.
|
||||
// To move forward we advance by one key, even if that lands us in the same
|
||||
// snapshot stripe.
|
||||
// - `skip && pos == iterPosCur`: We are at the key that has been returned.
|
||||
// - `skip && pos == iterPosCurForward`: We are at the key that has been returned.
|
||||
// To move forward we skip skippable entries in the stripe.
|
||||
// - `skip && pos == iterPosNext && i.iterStripeChange == sameStripeNonSkippable`:
|
||||
// This case may occur when skipping within a snapshot stripe and we
|
||||
// encounter either:
|
||||
// a) an invalid key kind; The previous call will have returned
|
||||
// whatever key it was processing and deferred handling of the
|
||||
// invalid key to this invocation of Next(). We're responsible for
|
||||
// ignoring skip=true and falling into the invalid key kind case
|
||||
// down below.
|
||||
// b) an interleaved range delete; This is a wart of the current code
|
||||
// structure. While skipping within a snapshot stripe, a range
|
||||
// delete interleaved at its start key and sequence number
|
||||
// interrupts the sequence of point keys. After we return the range
|
||||
// delete to the caller, we need to pick up skipping at where we
|
||||
// left off, so we preserve skip=true.
|
||||
// TODO(jackson): This last case is confusing and can be removed if we
|
||||
// interleave range deletions at the maximal sequence number using the
|
||||
// keyspan interleaving iterator. This is the treatment given to range
|
||||
// keys today.
|
||||
if i.pos == iterPosCurForward {
|
||||
if i.skip {
|
||||
i.skipInStripe()
|
||||
} else {
|
||||
i.nextInStripe()
|
||||
}
|
||||
} else if i.skip {
|
||||
if i.iterStripeChange != sameStripeNonSkippable {
|
||||
panic(errors.AssertionFailedf("compaction iterator has skip=true, but iterator is at iterPosNext"))
|
||||
}
|
||||
}
|
||||
|
||||
i.pos = iterPosCurForward
|
||||
@@ -374,6 +429,10 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
return &i.key, i.value
|
||||
}
|
||||
|
||||
// TODO(sumeer): we could avoid calling Covers if i.iterStripeChange ==
|
||||
// sameStripeSameKey since that check has already been done in
|
||||
// nextInStripeHelper. However, we also need to handle the case of
|
||||
// CoversInvisibly below.
|
||||
if cover := i.rangeDelFrag.Covers(*i.iterKey, i.curSnapshotSeqNum); cover == keyspan.CoversVisibly {
|
||||
// A pending range deletion deletes this key. Skip it.
|
||||
i.saveKey()
|
||||
@@ -382,17 +441,37 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
} else if cover == keyspan.CoversInvisibly {
|
||||
// i.iterKey would be deleted by a range deletion if there weren't
|
||||
// any open snapshots. Mark it as pinned.
|
||||
//
|
||||
// NB: there are multiple places in this file where we call
|
||||
// i.rangeDelFrag.Covers and this is the only one where we are writing
|
||||
// to i.snapshotPinned. Those other cases occur in mergeNext where the
|
||||
// caller is deciding whether the value should be merged or not, and the
|
||||
// key is in the same snapshot stripe. Hence, snapshotPinned is by
|
||||
// definition false in those cases.
|
||||
i.snapshotPinned = true
|
||||
i.forceObsoleteDueToRangeDel = true
|
||||
} else {
|
||||
i.forceObsoleteDueToRangeDel = false
|
||||
}
|
||||
|
||||
switch i.iterKey.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
if i.elideTombstone(i.iterKey.UserKey) {
|
||||
if i.curSnapshotIdx == 0 {
|
||||
// If we're at the last snapshot stripe and the tombstone
|
||||
// can be elided skip skippable keys in the same stripe.
|
||||
i.saveKey()
|
||||
i.skipInStripe()
|
||||
if i.key.Kind() == InternalKeyKindSingleDelete {
|
||||
i.skipDueToSingleDeleteElision()
|
||||
} else {
|
||||
i.skipInStripe()
|
||||
if !i.skip && i.iterStripeChange != newStripeNewKey {
|
||||
panic(errors.AssertionFailedf("pebble: skipInStripe in last stripe disabled skip without advancing to new key"))
|
||||
}
|
||||
}
|
||||
if i.iterStripeChange == newStripeSameKey {
|
||||
panic(errors.AssertionFailedf("pebble: skipInStripe in last stripe found a new stripe within the same key"))
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
// We're not at the last snapshot stripe, so the tombstone
|
||||
@@ -410,11 +489,23 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
i.skip = true
|
||||
return &i.key, i.value
|
||||
|
||||
case InternalKeyKindDeleteSized:
|
||||
// We may skip subsequent keys because of this tombstone. Scan
|
||||
// ahead to see just how much data this tombstone drops and if
|
||||
// the tombstone's value should be updated accordingly.
|
||||
return i.deleteSizedNext()
|
||||
|
||||
case InternalKeyKindSingleDelete:
|
||||
if i.singleDeleteNext() {
|
||||
return &i.key, i.value
|
||||
} else if i.err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
continue
|
||||
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected kind %s", redact.SafeString(i.iterKey.Kind().String())))
|
||||
}
|
||||
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||
@@ -424,6 +515,9 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
// preserving the original value, and potentially mutating the key
|
||||
// kind.
|
||||
i.setNext()
|
||||
if i.err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &i.key, i.value
|
||||
|
||||
case InternalKeyKindMerge:
|
||||
@@ -440,7 +534,15 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
if i.err == nil {
|
||||
// includesBase is true whenever we've transformed the MERGE record
|
||||
// into a SET.
|
||||
includesBase := i.key.Kind() == InternalKeyKindSet
|
||||
var includesBase bool
|
||||
switch i.key.Kind() {
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||
includesBase = true
|
||||
case InternalKeyKindMerge:
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected kind %s", redact.SafeString(i.key.Kind().String())))
|
||||
}
|
||||
i.value, needDelete, i.valueCloser, i.err = finishValueMerger(valueMerger, includesBase)
|
||||
}
|
||||
if i.err == nil {
|
||||
@@ -469,6 +571,8 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
||||
}
|
||||
if i.err != nil {
|
||||
i.valid = false
|
||||
// TODO(sumeer): why is MarkCorruptionError only being called for
|
||||
// MERGE?
|
||||
i.err = base.MarkCorruptionError(i.err)
|
||||
}
|
||||
return nil, nil
|
||||
@@ -508,10 +612,16 @@ func snapshotIndex(seq uint64, snapshots []uint64) (int, uint64) {
|
||||
return index, snapshots[index]
|
||||
}
|
||||
|
||||
// skipInStripe skips over skippable keys in the same stripe and user key.
|
||||
// skipInStripe skips over skippable keys in the same stripe and user key. It
|
||||
// may set i.err, in which case i.iterKey will be nil.
|
||||
func (i *compactionIter) skipInStripe() {
|
||||
i.skip = true
|
||||
// TODO(sumeer): we can avoid the overhead of calling i.rangeDelFrag.Covers,
|
||||
// in this case of nextInStripe, since we are skipping all of them anyway.
|
||||
for i.nextInStripe() == sameStripeSkippable {
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
}
|
||||
// Reset skip if we landed outside the original stripe. Otherwise, we landed
|
||||
// in the same stripe on a non-skippable key. In that case we should preserve
|
||||
@@ -549,11 +659,17 @@ const (
|
||||
// nextInStripe advances the iterator and returns one of the above const ints
|
||||
// indicating how its state changed.
|
||||
//
|
||||
// All sameStripeSkippable keys that are covered by a RANGEDEL will be skipped
|
||||
// and not returned.
|
||||
//
|
||||
// Calls to nextInStripe must be preceded by a call to saveKey to retain a
|
||||
// temporary reference to the original key, so that forward iteration can
|
||||
// proceed with a reference to the original key. Care should be taken to avoid
|
||||
// overwriting or mutating the saved key or value before they have been returned
|
||||
// to the caller of the exported function (i.e. the caller of Next, First, etc.)
|
||||
//
|
||||
// nextInStripe may set i.err, in which case the return value will be
|
||||
// newStripeNewKey, and i.iterKey will be nil.
|
||||
func (i *compactionIter) nextInStripe() stripeChangeType {
|
||||
i.iterStripeChange = i.nextInStripeHelper()
|
||||
return i.iterStripeChange
|
||||
@@ -562,58 +678,72 @@ func (i *compactionIter) nextInStripe() stripeChangeType {
|
||||
// nextInStripeHelper is an internal helper for nextInStripe; callers should use
|
||||
// nextInStripe and not call nextInStripeHelper.
|
||||
func (i *compactionIter) nextInStripeHelper() stripeChangeType {
|
||||
if !i.iterNext() {
|
||||
return newStripeNewKey
|
||||
}
|
||||
key := i.iterKey
|
||||
origSnapshotIdx := i.curSnapshotIdx
|
||||
for {
|
||||
if !i.iterNext() {
|
||||
return newStripeNewKey
|
||||
}
|
||||
key := i.iterKey
|
||||
|
||||
// NB: The below conditional is an optimization to avoid a user key
|
||||
// comparison in many cases. Internal keys with the same user key are
|
||||
// ordered in (strictly) descending order by trailer. If the new key has a
|
||||
// greater or equal trailer, or the previous key had a zero sequence number,
|
||||
// the new key must have a new user key.
|
||||
//
|
||||
// A couple things make these cases common:
|
||||
// - Sequence-number zeroing ensures ~all of the keys in L6 have a zero
|
||||
// sequence number.
|
||||
// - Ingested sstables' keys all adopt the same sequence number.
|
||||
if i.keyTrailer <= base.InternalKeyZeroSeqnumMaxTrailer || key.Trailer >= i.keyTrailer {
|
||||
if invariants.Enabled && i.equal(i.key.UserKey, key.UserKey) {
|
||||
if !i.equal(i.key.UserKey, key.UserKey) {
|
||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||
return newStripeNewKey
|
||||
}
|
||||
|
||||
// If i.key and key have the same user key, then
|
||||
// 1. i.key must not have had a zero sequence number (or it would've be the last
|
||||
// key with its user key).
|
||||
// 2. i.key must have a strictly larger sequence number
|
||||
// There's an exception in that either key may be a range delete. Range
|
||||
// deletes may share a sequence number with a point key if the keys were
|
||||
// ingested together. Range keys may also share the sequence number if they
|
||||
// were ingested, but range keys are interleaved into the compaction
|
||||
// iterator's input iterator at the maximal sequence number so their
|
||||
// original sequence number will not be observed here.
|
||||
if prevSeqNum := base.SeqNumFromTrailer(i.keyTrailer); (prevSeqNum == 0 || prevSeqNum <= key.SeqNum()) &&
|
||||
i.key.Kind() != InternalKeyKindRangeDelete && key.Kind() != InternalKeyKindRangeDelete {
|
||||
prevKey := i.key
|
||||
prevKey.Trailer = i.keyTrailer
|
||||
panic(fmt.Sprintf("pebble: invariant violation: %s and %s out of order", key, prevKey))
|
||||
panic(errors.AssertionFailedf("pebble: invariant violation: %s and %s out of order", prevKey, key))
|
||||
}
|
||||
|
||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||
return newStripeNewKey
|
||||
} else if !i.equal(i.key.UserKey, key.UserKey) {
|
||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||
return newStripeNewKey
|
||||
}
|
||||
origSnapshotIdx := i.curSnapshotIdx
|
||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindRangeDelete:
|
||||
// Range tombstones need to be exposed by the compactionIter to the upper level
|
||||
// `compaction` object, so return them regardless of whether they are in the same
|
||||
// snapshot stripe.
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeNonSkippable
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindRangeDelete:
|
||||
// Range tombstones need to be exposed by the compactionIter to the upper level
|
||||
// `compaction` object, so return them regardless of whether they are in the same
|
||||
// snapshot stripe.
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeNonSkippable
|
||||
}
|
||||
return newStripeSameKey
|
||||
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
// Range keys are interleaved at the max sequence number for a given user
|
||||
// key, so we should not see any more range keys in this stripe.
|
||||
panic("unreachable")
|
||||
case InternalKeyKindInvalid:
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeNonSkippable
|
||||
}
|
||||
return newStripeSameKey
|
||||
case InternalKeyKindDelete, InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindSingleDelete,
|
||||
InternalKeyKindSetWithDelete, InternalKeyKindDeleteSized:
|
||||
// Fall through
|
||||
default:
|
||||
i.iterKey = nil
|
||||
i.err = base.CorruptionErrorf("invalid internal key kind: %d", errors.Safe(i.iterKey.Kind()))
|
||||
i.valid = false
|
||||
return newStripeNewKey
|
||||
}
|
||||
return newStripeSameKey
|
||||
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
// Range keys are interleaved at the max sequence number for a given user
|
||||
// key, so we should not see any more range keys in this stripe.
|
||||
panic("unreachable")
|
||||
case InternalKeyKindInvalid:
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeNonSkippable
|
||||
// Same snapshot.
|
||||
if i.rangeDelFrag.Covers(*i.iterKey, i.curSnapshotSeqNum) == keyspan.CoversVisibly {
|
||||
continue
|
||||
}
|
||||
return sameStripeSkippable
|
||||
}
|
||||
return newStripeSameKey
|
||||
}
|
||||
if i.curSnapshotIdx == origSnapshotIdx {
|
||||
return sameStripeSkippable
|
||||
}
|
||||
return newStripeSameKey
|
||||
}
|
||||
|
||||
func (i *compactionIter) setNext() {
|
||||
@@ -639,6 +769,10 @@ func (i *compactionIter) setNext() {
|
||||
|
||||
// Else, we continue to loop through entries in the stripe looking for a
|
||||
// DEL. Note that we may stop *before* encountering a DEL, if one exists.
|
||||
//
|
||||
// NB: nextInStripe will skip sameStripeSkippable keys that are visibly
|
||||
// covered by a RANGEDEL. This can include DELs -- this is fine since such
|
||||
// DELs don't need to be combined with SET to make SETWITHDEL.
|
||||
for {
|
||||
switch i.nextInStripe() {
|
||||
case newStripeNewKey, newStripeSameKey:
|
||||
@@ -686,14 +820,19 @@ func (i *compactionIter) setNext() {
|
||||
i.skip = true
|
||||
return
|
||||
case sameStripeSkippable:
|
||||
// We're still in the same stripe. If this is a DEL/SINGLEDEL, we
|
||||
// stop looking and emit a SETWITHDEL. Subsequent keys are
|
||||
// eligible for skipping.
|
||||
if i.iterKey.Kind() == InternalKeyKindDelete ||
|
||||
i.iterKey.Kind() == InternalKeyKindSingleDelete {
|
||||
// We're still in the same stripe. If this is a
|
||||
// DEL/SINGLEDEL/DELSIZED, we stop looking and emit a SETWITHDEL.
|
||||
// Subsequent keys are eligible for skipping.
|
||||
switch i.iterKey.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
i.key.SetKind(InternalKeyKindSetWithDelete)
|
||||
i.skip = true
|
||||
return
|
||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindSetWithDelete:
|
||||
// Do nothing
|
||||
default:
|
||||
i.err = base.CorruptionErrorf("invalid internal key kind: %d", errors.Safe(i.iterKey.Kind()))
|
||||
i.valid = false
|
||||
}
|
||||
default:
|
||||
panic("pebble: unexpected stripeChangeType: " + strconv.Itoa(int(i.iterStripeChange)))
|
||||
@@ -713,13 +852,24 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
||||
i.pos = iterPosNext
|
||||
return i.iterStripeChange
|
||||
}
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
// NB: MERGE#10+RANGEDEL#9 stays a MERGE, since nextInStripe skips
|
||||
// sameStripeSkippable keys that are visibly covered by a RANGEDEL. There
|
||||
// may be MERGE#7 that is invisibly covered and will be preserved, but
|
||||
// there is no risk that MERGE#10 and MERGE#7 will get merged in the
|
||||
// future as the RANGEDEL still exists and will be used in user-facing
|
||||
// reads that see MERGE#10, and will also eventually cause MERGE#7 to be
|
||||
// deleted in a compaction.
|
||||
key := i.iterKey
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
// We've hit a deletion tombstone. Return everything up to this point and
|
||||
// then skip entries until the next snapshot stripe. We change the kind
|
||||
// of the result key to a Set so that it shadows keys in lower
|
||||
// levels. That is, MERGE+DEL -> SET.
|
||||
// levels. That is, MERGE+DEL -> SETWITHDEL.
|
||||
//
|
||||
// We do the same for SingleDelete since SingleDelete is only
|
||||
// permitted (with deterministic behavior) for keys that have been
|
||||
// set once since the last SingleDelete/Delete, so everything
|
||||
@@ -732,21 +882,11 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
||||
// single Set, and then merge in any following Sets, but that is
|
||||
// complicated wrt code and unnecessary given the narrow permitted
|
||||
// use of SingleDelete.
|
||||
i.key.SetKind(InternalKeyKindSet)
|
||||
i.key.SetKind(InternalKeyKindSetWithDelete)
|
||||
i.skip = true
|
||||
return sameStripeSkippable
|
||||
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||
if i.rangeDelFrag.Covers(*key, i.curSnapshotSeqNum) == keyspan.CoversVisibly {
|
||||
// We change the kind of the result key to a Set so that it shadows
|
||||
// keys in lower levels. That is, MERGE+RANGEDEL -> SET. This isn't
|
||||
// strictly necessary, but provides consistency with the behavior of
|
||||
// MERGE+DEL.
|
||||
i.key.SetKind(InternalKeyKindSet)
|
||||
i.skip = true
|
||||
return sameStripeSkippable
|
||||
}
|
||||
|
||||
// We've hit a Set or SetWithDel value. Merge with the existing
|
||||
// value and return. We change the kind of the resulting key to a
|
||||
// Set so that it shadows keys in lower levels. That is:
|
||||
@@ -761,16 +901,6 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
||||
return sameStripeSkippable
|
||||
|
||||
case InternalKeyKindMerge:
|
||||
if i.rangeDelFrag.Covers(*key, i.curSnapshotSeqNum) == keyspan.CoversVisibly {
|
||||
// We change the kind of the result key to a Set so that it shadows
|
||||
// keys in lower levels. That is, MERGE+RANGEDEL -> SET. This isn't
|
||||
// strictly necessary, but provides consistency with the behavior of
|
||||
// MERGE+DEL.
|
||||
i.key.SetKind(InternalKeyKindSet)
|
||||
i.skip = true
|
||||
return sameStripeSkippable
|
||||
}
|
||||
|
||||
// We've hit another Merge value. Merge with the existing value and
|
||||
// continue looping.
|
||||
i.err = valueMerger.MergeOlder(i.iterValue)
|
||||
@@ -787,6 +917,16 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
||||
}
|
||||
}
|
||||
|
||||
// singleDeleteNext processes a SingleDelete point tombstone. A SingleDelete, or
|
||||
// SINGLEDEL, is unique in that it deletes exactly 1 internal key. It's a
|
||||
// performance optimization when the client knows a user key has not been
|
||||
// overwritten, allowing the elision of the tombstone earlier, avoiding write
|
||||
// amplification.
|
||||
//
|
||||
// singleDeleteNext returns a boolean indicating whether or not the caller
|
||||
// should yield the SingleDelete key to the consumer of the compactionIter. If
|
||||
// singleDeleteNext returns false, the caller may consume/elide the
|
||||
// SingleDelete.
|
||||
func (i *compactionIter) singleDeleteNext() bool {
|
||||
// Save the current key.
|
||||
i.saveKey()
|
||||
@@ -795,26 +935,85 @@ func (i *compactionIter) singleDeleteNext() bool {
|
||||
|
||||
// Loop until finds a key to be passed to the next level.
|
||||
for {
|
||||
// If we find a key that can't be skipped, return true so that the
|
||||
// caller yields the SingleDelete to the caller.
|
||||
if i.nextInStripe() != sameStripeSkippable {
|
||||
// This defers additional error checking regarding single delete
|
||||
// invariants to the compaction where the keys with the same user key as
|
||||
// the single delete are in the same stripe.
|
||||
i.pos = iterPosNext
|
||||
return true
|
||||
return i.err == nil
|
||||
}
|
||||
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
// INVARIANT: sameStripeSkippable.
|
||||
key := i.iterKey
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindMerge, InternalKeyKindSetWithDelete:
|
||||
// We've hit a Delete, Merge or SetWithDelete, transform the
|
||||
// SingleDelete into a full Delete.
|
||||
kind := key.Kind()
|
||||
switch kind {
|
||||
case InternalKeyKindDelete, InternalKeyKindSetWithDelete, InternalKeyKindDeleteSized:
|
||||
if (kind == InternalKeyKindDelete || kind == InternalKeyKindDeleteSized) &&
|
||||
i.ineffectualSingleDeleteCallback != nil {
|
||||
i.ineffectualSingleDeleteCallback(i.key.UserKey)
|
||||
}
|
||||
// We've hit a Delete, DeleteSized, SetWithDelete, transform
|
||||
// the SingleDelete into a full Delete.
|
||||
i.key.SetKind(InternalKeyKindDelete)
|
||||
i.skip = true
|
||||
return true
|
||||
|
||||
case InternalKeyKindSet:
|
||||
i.nextInStripe()
|
||||
case InternalKeyKindSet, InternalKeyKindMerge:
|
||||
// This SingleDelete deletes the Set/Merge, and we can now elide the
|
||||
// SingleDel as well. We advance past the Set and return false to
|
||||
// indicate to the main compaction loop that we should NOT yield the
|
||||
// current SingleDel key to the compaction loop.
|
||||
//
|
||||
// NB: singleDeleteNext was called with i.pos == iterPosCurForward, and
|
||||
// after the call to nextInStripe, we are still at iterPosCurForward,
|
||||
// since we are at the key after the Set/Merge that was single deleted.
|
||||
change := i.nextInStripe()
|
||||
switch change {
|
||||
case sameStripeSkippable, newStripeSameKey:
|
||||
// On the same user key.
|
||||
nextKind := i.iterKey.Kind()
|
||||
switch nextKind {
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete, InternalKeyKindMerge:
|
||||
if i.singleDeleteInvariantViolationCallback != nil {
|
||||
// sameStripeSkippable keys returned by nextInStripe() are already
|
||||
// known to not be covered by a RANGEDEL, so it is an invariant
|
||||
// violation. The rare case is newStripeSameKey, where it is a
|
||||
// violation if not covered by a RANGEDEL.
|
||||
if change == sameStripeSkippable ||
|
||||
i.rangeDelFrag.Covers(*i.iterKey, i.curSnapshotSeqNum) == keyspan.NoCover {
|
||||
i.singleDeleteInvariantViolationCallback(i.key.UserKey)
|
||||
}
|
||||
}
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized, InternalKeyKindSingleDelete,
|
||||
InternalKeyKindRangeDelete:
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected internal key kind: %d", errors.Safe(i.iterKey.Kind())))
|
||||
}
|
||||
case sameStripeNonSkippable:
|
||||
// No ability to check whether there is another Set/Merge below with
|
||||
// the same user key.
|
||||
//
|
||||
// TODO(sumeer): once range deletions are interleaved at the maximal
|
||||
// sequence number, this case will go away.
|
||||
case newStripeNewKey:
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
i.valid = false
|
||||
return false
|
||||
|
||||
case InternalKeyKindSingleDelete:
|
||||
// Two single deletes met in a compaction. The first single delete is
|
||||
// ineffectual.
|
||||
if i.ineffectualSingleDeleteCallback != nil {
|
||||
i.ineffectualSingleDeleteCallback(i.key.UserKey)
|
||||
}
|
||||
// Continue to apply the second single delete.
|
||||
continue
|
||||
|
||||
default:
|
||||
@@ -825,6 +1024,303 @@ func (i *compactionIter) singleDeleteNext() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// skipDueToSingleDeleteElision is called when the SingleDelete is being
|
||||
// elided because it is in the final snapshot stripe and there are no keys
|
||||
// with the same user key in lower levels in the LSM (below the files in this
|
||||
// compaction).
|
||||
//
|
||||
// TODO(sumeer): the only difference between singleDeleteNext and
|
||||
// skipDueToSingleDeleteElision is the fact that the caller knows it will be
|
||||
// eliding the single delete in the latter case. There are some similar things
|
||||
// happening in both implementations. My first attempt at combining them into
|
||||
// a single method was hard to comprehend. Try again.
|
||||
func (i *compactionIter) skipDueToSingleDeleteElision() {
|
||||
for {
|
||||
stripeChange := i.nextInStripe()
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
switch stripeChange {
|
||||
case newStripeNewKey:
|
||||
// The single delete is only now being elided, meaning it did not elide
|
||||
// any keys earlier in its descent down the LSM. We stepped onto a new
|
||||
// user key, meaning that even now at its moment of elision, it still
|
||||
// hasn't elided any other keys. The single delete was ineffectual (a
|
||||
// no-op).
|
||||
if i.ineffectualSingleDeleteCallback != nil {
|
||||
i.ineffectualSingleDeleteCallback(i.key.UserKey)
|
||||
}
|
||||
i.skip = false
|
||||
return
|
||||
case newStripeSameKey:
|
||||
// This should be impossible. If we're eliding a single delete, we
|
||||
// determined that the tombstone is in the final snapshot stripe, but we
|
||||
// stepped into a new stripe of the same key.
|
||||
panic(errors.AssertionFailedf("eliding single delete followed by same key in new stripe"))
|
||||
case sameStripeNonSkippable:
|
||||
// There's a key that we cannot skip. There are two possible cases:
|
||||
// a. The key is invalid. This is an error.
|
||||
// b. The key is a range deletion.
|
||||
// The second case may also be an ineffectual single delete. However, it
|
||||
// is possible that there is a SET that is at the same seqnum as the
|
||||
// RANGEDEL, and so is not deleted by that RANGEDEL, and will be deleted
|
||||
// by this single delete. So we cannot be certain that this is an
|
||||
// ineffectual single delete.
|
||||
//
|
||||
// TODO(sumeer): the existing todo to interleave range deletions at the
|
||||
// maximal sequence number will allow us to address this ambiguity.
|
||||
//
|
||||
// TODO(sumeer): by setting skip to true, the compactionIter is making a
|
||||
// single delete stronger (like a del), which will hide bugs in the use of
|
||||
// single delete.
|
||||
i.skip = true
|
||||
return
|
||||
case sameStripeSkippable:
|
||||
kind := i.iterKey.Kind()
|
||||
switch kind {
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized, InternalKeyKindSingleDelete:
|
||||
if i.ineffectualSingleDeleteCallback != nil {
|
||||
i.ineffectualSingleDeleteCallback(i.key.UserKey)
|
||||
}
|
||||
switch kind {
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized:
|
||||
i.skipInStripe()
|
||||
return
|
||||
case InternalKeyKindSingleDelete:
|
||||
// Repeat the same with this SingleDelete. We don't want to simply
|
||||
// call skipInStripe(), since it increases the strength of the
|
||||
// SingleDel, which hides bugs in the use of single delete.
|
||||
continue
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected internal key kind: %d", errors.Safe(i.iterKey.Kind())))
|
||||
}
|
||||
case InternalKeyKindSetWithDelete:
|
||||
// The SingleDelete should behave like a Delete.
|
||||
i.skipInStripe()
|
||||
return
|
||||
case InternalKeyKindSet, InternalKeyKindMerge:
|
||||
// This SingleDelete deletes the Set/Merge, and we are eliding the
|
||||
// SingleDel as well. Step to the next key (this is not deleted by the
|
||||
// SingleDelete).
|
||||
//
|
||||
// NB: skipDueToSingleDeleteElision was called with i.pos ==
|
||||
// iterPosCurForward, and after the call to nextInStripe, we are still
|
||||
// at iterPosCurForward, since we are at the key after the Set/Merge
|
||||
// that was single deleted.
|
||||
change := i.nextInStripe()
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
switch change {
|
||||
case newStripeSameKey:
|
||||
panic(errors.AssertionFailedf("eliding single delete followed by same key in new stripe"))
|
||||
case newStripeNewKey:
|
||||
case sameStripeSkippable:
|
||||
// On the same key.
|
||||
nextKind := i.iterKey.Kind()
|
||||
switch nextKind {
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete, InternalKeyKindMerge:
|
||||
if i.singleDeleteInvariantViolationCallback != nil {
|
||||
i.singleDeleteInvariantViolationCallback(i.key.UserKey)
|
||||
}
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized, InternalKeyKindSingleDelete,
|
||||
InternalKeyKindRangeDelete:
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected internal key kind: %d", errors.Safe(i.iterKey.Kind())))
|
||||
}
|
||||
case sameStripeNonSkippable:
|
||||
// No ability to check whether there is another Set/Merge below with
|
||||
// the same user key.
|
||||
//
|
||||
// TODO(sumeer): once range deletions are interleaved at the maximal
|
||||
// sequence number, this case will go away.
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
// Whether in same stripe or new stripe, this key is not consumed by
|
||||
// the SingleDelete.
|
||||
i.skip = false
|
||||
return
|
||||
default:
|
||||
panic(errors.AssertionFailedf(
|
||||
"unexpected internal key kind: %d", errors.Safe(i.iterKey.Kind())))
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteSizedNext processes a DELSIZED point tombstone. Unlike ordinary DELs,
|
||||
// these tombstones carry a value that's a varint indicating the size of the
|
||||
// entry (len(key)+len(value)) that the tombstone is expected to delete.
|
||||
//
|
||||
// When a deleteSizedNext is encountered, we skip ahead to see which keys, if
|
||||
// any, are elided as a result of the tombstone.
|
||||
func (i *compactionIter) deleteSizedNext() (*base.InternalKey, []byte) {
|
||||
i.saveKey()
|
||||
i.valid = true
|
||||
i.skip = true
|
||||
|
||||
// The DELSIZED tombstone may have no value at all. This happens when the
|
||||
// tombstone has already deleted the key that the user originally predicted.
|
||||
// In this case, we still peek forward in case there's another DELSIZED key
|
||||
// with a lower sequence number, in which case we'll adopt its value.
|
||||
if len(i.iterValue) == 0 {
|
||||
i.value = i.valueBuf[:0]
|
||||
} else {
|
||||
i.valueBuf = append(i.valueBuf[:0], i.iterValue...)
|
||||
i.value = i.valueBuf
|
||||
}
|
||||
|
||||
// Loop through all the keys within this stripe that are skippable.
|
||||
i.pos = iterPosNext
|
||||
for i.nextInStripe() == sameStripeSkippable {
|
||||
if i.err != nil {
|
||||
panic(i.err)
|
||||
}
|
||||
switch i.iterKey.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized, InternalKeyKindSingleDelete:
|
||||
// We encountered a tombstone (DEL, or DELSIZED) that's deleted by
|
||||
// the original DELSIZED tombstone. This can happen in two cases:
|
||||
//
|
||||
// (1) These tombstones were intended to delete two distinct values,
|
||||
// and this DELSIZED has already dropped the relevant key. For
|
||||
// example:
|
||||
//
|
||||
// a.DELSIZED.9 a.SET.7 a.DELSIZED.5 a.SET.4
|
||||
//
|
||||
// If a.DELSIZED.9 has already deleted a.SET.7, its size has
|
||||
// already been zeroed out. In this case, we want to adopt the
|
||||
// value of the DELSIZED with the lower sequence number, in
|
||||
// case the a.SET.4 key has not yet been elided.
|
||||
//
|
||||
// (2) This DELSIZED was missized. The user thought they were
|
||||
// deleting a key with this user key, but this user key had
|
||||
// already been deleted.
|
||||
//
|
||||
// We can differentiate these two cases by examining the length of
|
||||
// the DELSIZED's value. A DELSIZED's value holds the size of both
|
||||
// the user key and value that it intends to delete. For any user
|
||||
// key with a length > 0, a DELSIZED that has not deleted a key must
|
||||
// have a value with a length > 0.
|
||||
//
|
||||
// We treat both cases the same functionally, adopting the identity
|
||||
// of the lower-sequence numbered tombstone. However in the second
|
||||
// case, we also increment the stat counting missized tombstones.
|
||||
if len(i.value) > 0 {
|
||||
// The original DELSIZED key was missized. The key that the user
|
||||
// thought they were deleting does not exist.
|
||||
i.stats.countMissizedDels++
|
||||
}
|
||||
i.valueBuf = append(i.valueBuf[:0], i.iterValue...)
|
||||
i.value = i.valueBuf
|
||||
if i.iterKey.Kind() != InternalKeyKindDeleteSized {
|
||||
// Convert the DELSIZED to a DEL—The DEL/SINGLEDEL we're eliding
|
||||
// may not have deleted the key(s) it was intended to yet. The
|
||||
// ordinary DEL compaction heuristics are better suited at that,
|
||||
// plus we don't want to count it as a missized DEL. We early
|
||||
// exit in this case, after skipping the remainder of the
|
||||
// snapshot stripe.
|
||||
i.key.SetKind(InternalKeyKindDelete)
|
||||
// NB: We skipInStripe now, rather than returning leaving
|
||||
// i.skip=true and returning early, because Next() requires
|
||||
// that i.skip=true only if i.iterPos = iterPosCurForward.
|
||||
//
|
||||
// Ignore any error caused by skipInStripe since it does not affect
|
||||
// the key/value being returned here, and the next call to Next() will
|
||||
// expose it.
|
||||
i.skipInStripe()
|
||||
return &i.key, i.value
|
||||
}
|
||||
// Continue, in case we uncover another DELSIZED or a key this
|
||||
// DELSIZED deletes.
|
||||
|
||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindSetWithDelete:
|
||||
// If the DELSIZED is value-less, it already deleted the key that it
|
||||
// was intended to delete. This is possible with a sequence like:
|
||||
//
|
||||
// DELSIZED.8 SET.7 SET.3
|
||||
//
|
||||
// The DELSIZED only describes the size of the SET.7, which in this
|
||||
// case has already been elided. We don't count it as a missizing,
|
||||
// instead converting the DELSIZED to a DEL. Skip the remainder of
|
||||
// the snapshot stripe and return.
|
||||
if len(i.value) == 0 {
|
||||
i.key.SetKind(InternalKeyKindDelete)
|
||||
// NB: We skipInStripe now, rather than returning leaving
|
||||
// i.skip=true and returning early, because Next() requires
|
||||
// that i.skip=true only if i.iterPos = iterPosCurForward.
|
||||
//
|
||||
// Ignore any error caused by skipInStripe since it does not affect
|
||||
// the key/value being returned here, and the next call to Next() will
|
||||
// expose it.
|
||||
i.skipInStripe()
|
||||
return &i.key, i.value
|
||||
}
|
||||
// The deleted key is not a DEL, DELSIZED, and the DELSIZED in i.key
|
||||
// has a positive size.
|
||||
expectedSize, n := binary.Uvarint(i.value)
|
||||
if n != len(i.value) {
|
||||
i.err = base.CorruptionErrorf("DELSIZED holds invalid value: %x", errors.Safe(i.value))
|
||||
i.valid = false
|
||||
return nil, nil
|
||||
}
|
||||
elidedSize := uint64(len(i.iterKey.UserKey)) + uint64(len(i.iterValue))
|
||||
if elidedSize != expectedSize {
|
||||
// The original DELSIZED key was missized. It's unclear what to
|
||||
// do. The user-provided size was wrong, so it's unlikely to be
|
||||
// accurate or meaningful. We could:
|
||||
//
|
||||
// 1. return the DELSIZED with the original user-provided size unmodified
|
||||
// 2. return the DELZIZED with a zeroed size to reflect that a key was
|
||||
// elided, even if it wasn't the anticipated size.
|
||||
// 3. subtract the elided size from the estimate and re-encode.
|
||||
// 4. convert the DELSIZED into a value-less DEL, so that
|
||||
// ordinary DEL heuristics apply.
|
||||
//
|
||||
// We opt for (4) under the rationale that we can't rely on the
|
||||
// user-provided size for accuracy, so ordinary DEL heuristics
|
||||
// are safer.
|
||||
i.stats.countMissizedDels++
|
||||
i.key.SetKind(InternalKeyKindDelete)
|
||||
i.value = i.valueBuf[:0]
|
||||
// NB: We skipInStripe now, rather than returning leaving
|
||||
// i.skip=true and returning early, because Next() requires
|
||||
// that i.skip=true only if i.iterPos = iterPosCurForward.
|
||||
//
|
||||
// Ignore any error caused by skipInStripe since it does not affect
|
||||
// the key/value being returned here, and the next call to Next() will
|
||||
// expose it.
|
||||
i.skipInStripe()
|
||||
return &i.key, i.value
|
||||
}
|
||||
// NB: We remove the value regardless of whether the key was sized
|
||||
// appropriately. The size encoded is 'consumed' the first time it
|
||||
// meets a key that it deletes.
|
||||
i.value = i.valueBuf[:0]
|
||||
|
||||
default:
|
||||
i.err = base.CorruptionErrorf("invalid internal key kind: %d", errors.Safe(i.iterKey.Kind()))
|
||||
i.valid = false
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
// Reset skip if we landed outside the original stripe. Otherwise, we landed
|
||||
// in the same stripe on a non-skippable key. In that case we should preserve
|
||||
// `i.skip == true` such that later keys in the stripe will continue to be
|
||||
// skipped.
|
||||
if i.iterStripeChange == newStripeNewKey || i.iterStripeChange == newStripeSameKey {
|
||||
i.skip = false
|
||||
}
|
||||
if i.err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &i.key, i.value
|
||||
}
|
||||
|
||||
func (i *compactionIter) saveKey() {
|
||||
i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
|
||||
i.key.UserKey = i.keyBuf
|
||||
|
||||
725
vendor/github.com/cockroachdb/pebble/compaction_picker.go
generated
vendored
725
vendor/github.com/cockroachdb/pebble/compaction_picker.go
generated
vendored
File diff suppressed because it is too large
Load Diff
842
vendor/github.com/cockroachdb/pebble/db.go
generated
vendored
842
vendor/github.com/cockroachdb/pebble/db.go
generated
vendored
File diff suppressed because it is too large
Load Diff
80
vendor/github.com/cockroachdb/pebble/event.go
generated
vendored
80
vendor/github.com/cockroachdb/pebble/event.go
generated
vendored
@@ -39,10 +39,11 @@ func formatFileNums(tables []TableInfo) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// LevelInfo contains info pertaining to a partificular level.
|
||||
// LevelInfo contains info pertaining to a particular level.
|
||||
type LevelInfo struct {
|
||||
Level int
|
||||
Tables []TableInfo
|
||||
Score float64
|
||||
}
|
||||
|
||||
func (i LevelInfo) String() string {
|
||||
@@ -51,8 +52,11 @@ func (i LevelInfo) String() string {
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (i LevelInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("L%d [%s] (%s)", redact.Safe(i.Level), redact.Safe(formatFileNums(i.Tables)),
|
||||
redact.Safe(humanize.Uint64(tablesTotalSize(i.Tables))))
|
||||
w.Printf("L%d [%s] (%s) Score=%.2f",
|
||||
redact.Safe(i.Level),
|
||||
redact.Safe(formatFileNums(i.Tables)),
|
||||
redact.Safe(humanize.Bytes.Uint64(tablesTotalSize(i.Tables))),
|
||||
redact.Safe(i.Score))
|
||||
}
|
||||
|
||||
// CompactionInfo contains the info for a compaction event.
|
||||
@@ -75,6 +79,27 @@ type CompactionInfo struct {
|
||||
TotalDuration time.Duration
|
||||
Done bool
|
||||
Err error
|
||||
|
||||
SingleLevelOverlappingRatio float64
|
||||
MultiLevelOverlappingRatio float64
|
||||
|
||||
// Annotations specifies additional info to appear in a compaction's event log line
|
||||
Annotations compactionAnnotations
|
||||
}
|
||||
|
||||
type compactionAnnotations []string
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (ca compactionAnnotations) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if len(ca) == 0 {
|
||||
return
|
||||
}
|
||||
for i := range ca {
|
||||
if i != 0 {
|
||||
w.Print(" ")
|
||||
}
|
||||
w.Printf("%s", redact.SafeString(ca[i]))
|
||||
}
|
||||
}
|
||||
|
||||
func (i CompactionInfo) String() string {
|
||||
@@ -90,20 +115,25 @@ func (i CompactionInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
}
|
||||
|
||||
if !i.Done {
|
||||
w.Printf("[JOB %d] compacting(%s) ", redact.Safe(i.JobID), redact.SafeString(i.Reason))
|
||||
w.Print(levelInfos(i.Input))
|
||||
w.Printf("[JOB %d] compacting(%s) ",
|
||||
redact.Safe(i.JobID),
|
||||
redact.SafeString(i.Reason))
|
||||
w.Printf("%s", i.Annotations)
|
||||
w.Printf("%s; ", levelInfos(i.Input))
|
||||
w.Printf("OverlappingRatio: Single %.2f, Multi %.2f", i.SingleLevelOverlappingRatio, i.MultiLevelOverlappingRatio)
|
||||
return
|
||||
}
|
||||
outputSize := tablesTotalSize(i.Output.Tables)
|
||||
w.Printf("[JOB %d] compacted(%s) ", redact.Safe(i.JobID), redact.SafeString(i.Reason))
|
||||
w.Printf("%s", i.Annotations)
|
||||
w.Print(levelInfos(i.Input))
|
||||
w.Printf(" -> L%d [%s] (%s), in %.1fs (%.1fs total), output rate %s/s",
|
||||
redact.Safe(i.Output.Level),
|
||||
redact.Safe(formatFileNums(i.Output.Tables)),
|
||||
redact.Safe(humanize.Uint64(outputSize)),
|
||||
redact.Safe(humanize.Bytes.Uint64(outputSize)),
|
||||
redact.Safe(i.Duration.Seconds()),
|
||||
redact.Safe(i.TotalDuration.Seconds()),
|
||||
redact.Safe(humanize.Uint64(uint64(float64(outputSize)/i.Duration.Seconds()))))
|
||||
redact.Safe(humanize.Bytes.Uint64(uint64(float64(outputSize)/i.Duration.Seconds()))))
|
||||
}
|
||||
|
||||
type levelInfos []LevelInfo
|
||||
@@ -129,6 +159,9 @@ type FlushInfo struct {
|
||||
Reason string
|
||||
// Input contains the count of input memtables that were flushed.
|
||||
Input int
|
||||
// InputBytes contains the total in-memory size of the memtable(s) that were
|
||||
// flushed. This size includes skiplist indexing data structures.
|
||||
InputBytes uint64
|
||||
// Output contains the ouptut table generated by the flush. The output info
|
||||
// is empty for the flush begin event.
|
||||
Output []TableInfo
|
||||
@@ -168,7 +201,7 @@ func (i FlushInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if !i.Ingest {
|
||||
w.Printf("flushing %d memtable", redact.Safe(i.Input))
|
||||
w.SafeString(plural)
|
||||
w.Printf(" to L0")
|
||||
w.Printf(" (%s) to L0", redact.Safe(humanize.Bytes.Uint64(i.InputBytes)))
|
||||
} else {
|
||||
w.Printf("flushing %d ingested table%s", redact.Safe(i.Input), plural)
|
||||
}
|
||||
@@ -180,13 +213,14 @@ func (i FlushInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if invariants.Enabled && len(i.IngestLevels) > 0 {
|
||||
panic(errors.AssertionFailedf("pebble: expected len(IngestedLevels) == 0"))
|
||||
}
|
||||
w.Printf("[JOB %d] flushed %d memtable%s to L0 [%s] (%s), in %.1fs (%.1fs total), output rate %s/s",
|
||||
w.Printf("[JOB %d] flushed %d memtable%s (%s) to L0 [%s] (%s), in %.1fs (%.1fs total), output rate %s/s",
|
||||
redact.Safe(i.JobID), redact.Safe(i.Input), plural,
|
||||
redact.Safe(humanize.Bytes.Uint64(i.InputBytes)),
|
||||
redact.Safe(formatFileNums(i.Output)),
|
||||
redact.Safe(humanize.Uint64(outputSize)),
|
||||
redact.Safe(humanize.Bytes.Uint64(outputSize)),
|
||||
redact.Safe(i.Duration.Seconds()),
|
||||
redact.Safe(i.TotalDuration.Seconds()),
|
||||
redact.Safe(humanize.Uint64(uint64(float64(outputSize)/i.Duration.Seconds()))))
|
||||
redact.Safe(humanize.Bytes.Uint64(uint64(float64(outputSize)/i.Duration.Seconds()))))
|
||||
} else {
|
||||
if invariants.Enabled && len(i.IngestLevels) == 0 {
|
||||
panic(errors.AssertionFailedf("pebble: expected len(IngestedLevels) > 0"))
|
||||
@@ -198,12 +232,12 @@ func (i FlushInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if j > 0 {
|
||||
w.Printf(" +")
|
||||
}
|
||||
w.Printf(" L%d:%s (%s)", level, redact.Safe(file.FileNum), humanize.IEC.Uint64(file.Size))
|
||||
w.Printf(" L%d:%s (%s)", level, file.FileNum, humanize.Bytes.Uint64(file.Size))
|
||||
}
|
||||
w.Printf(" in %.1fs (%.1fs total), output rate %s/s",
|
||||
redact.Safe(i.Duration.Seconds()),
|
||||
redact.Safe(i.TotalDuration.Seconds()),
|
||||
redact.Safe(humanize.Uint64(uint64(float64(outputSize)/i.Duration.Seconds()))))
|
||||
redact.Safe(humanize.Bytes.Uint64(uint64(float64(outputSize)/i.Duration.Seconds()))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +261,7 @@ func (i ManifestCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("[JOB %d] MANIFEST create error: %s", redact.Safe(i.JobID), i.Err)
|
||||
return
|
||||
}
|
||||
w.Printf("[JOB %d] MANIFEST created %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] MANIFEST created %s", redact.Safe(i.JobID), i.FileNum)
|
||||
}
|
||||
|
||||
// ManifestDeleteInfo contains the info for a Manifest deletion event.
|
||||
@@ -249,7 +283,7 @@ func (i ManifestDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("[JOB %d] MANIFEST delete error: %s", redact.Safe(i.JobID), i.Err)
|
||||
return
|
||||
}
|
||||
w.Printf("[JOB %d] MANIFEST deleted %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] MANIFEST deleted %s", redact.Safe(i.JobID), i.FileNum)
|
||||
}
|
||||
|
||||
// TableCreateInfo contains the info for a table creation event.
|
||||
@@ -269,7 +303,7 @@ func (i TableCreateInfo) String() string {
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (i TableCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("[JOB %d] %s: sstable created %s",
|
||||
redact.Safe(i.JobID), redact.Safe(i.Reason), redact.Safe(i.FileNum))
|
||||
redact.Safe(i.JobID), redact.Safe(i.Reason), i.FileNum)
|
||||
}
|
||||
|
||||
// TableDeleteInfo contains the info for a table deletion event.
|
||||
@@ -288,10 +322,10 @@ func (i TableDeleteInfo) String() string {
|
||||
func (i TableDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if i.Err != nil {
|
||||
w.Printf("[JOB %d] sstable delete error %s: %s",
|
||||
redact.Safe(i.JobID), redact.Safe(i.FileNum), i.Err)
|
||||
redact.Safe(i.JobID), i.FileNum, i.Err)
|
||||
return
|
||||
}
|
||||
w.Printf("[JOB %d] sstable deleted %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] sstable deleted %s", redact.Safe(i.JobID), i.FileNum)
|
||||
}
|
||||
|
||||
// TableIngestInfo contains the info for a table ingestion event.
|
||||
@@ -337,8 +371,8 @@ func (i TableIngestInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
if !i.flushable {
|
||||
levelStr = fmt.Sprintf("L%d:", t.Level)
|
||||
}
|
||||
w.Printf(" %s%s (%s)", redact.Safe(levelStr), redact.Safe(t.FileNum),
|
||||
redact.Safe(humanize.Uint64(t.Size)))
|
||||
w.Printf(" %s%s (%s)", redact.Safe(levelStr), t.FileNum,
|
||||
redact.Safe(humanize.Bytes.Uint64(t.Size)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,12 +433,12 @@ func (i WALCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
}
|
||||
|
||||
if i.RecycledFileNum == 0 {
|
||||
w.Printf("[JOB %d] WAL created %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] WAL created %s", redact.Safe(i.JobID), i.FileNum)
|
||||
return
|
||||
}
|
||||
|
||||
w.Printf("[JOB %d] WAL created %s (recycled %s)",
|
||||
redact.Safe(i.JobID), redact.Safe(i.FileNum), redact.Safe(i.RecycledFileNum))
|
||||
redact.Safe(i.JobID), i.FileNum, i.RecycledFileNum)
|
||||
}
|
||||
|
||||
// WALDeleteInfo contains the info for a WAL deletion event.
|
||||
@@ -426,7 +460,7 @@ func (i WALDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("[JOB %d] WAL delete error: %s", redact.Safe(i.JobID), i.Err)
|
||||
return
|
||||
}
|
||||
w.Printf("[JOB %d] WAL deleted %s", redact.Safe(i.JobID), redact.Safe(i.FileNum))
|
||||
w.Printf("[JOB %d] WAL deleted %s", redact.Safe(i.JobID), i.FileNum)
|
||||
}
|
||||
|
||||
// WriteStallBeginInfo contains the info for a write stall begin event.
|
||||
|
||||
28
vendor/github.com/cockroachdb/pebble/external_iterator.go
generated
vendored
28
vendor/github.com/cockroachdb/pebble/external_iterator.go
generated
vendored
@@ -209,15 +209,15 @@ func createExternalPointIter(ctx context.Context, it *Iterator) (internalIterato
|
||||
pointIter internalIterator
|
||||
err error
|
||||
)
|
||||
pointIter, err = r.NewIterWithBlockPropertyFiltersAndContext(
|
||||
ctx,
|
||||
it.opts.LowerBound,
|
||||
it.opts.UpperBound,
|
||||
nil, /* BlockPropertiesFilterer */
|
||||
false, /* useFilterBlock */
|
||||
&it.stats.InternalStats,
|
||||
sstable.TrivialReaderProvider{Reader: r},
|
||||
)
|
||||
// We could set hideObsoletePoints=true, since we are reading at
|
||||
// InternalKeySeqNumMax, but we don't bother since these sstables should
|
||||
// not have obsolete points (so the performance optimization is
|
||||
// unnecessary), and we don't want to bother constructing a
|
||||
// BlockPropertiesFilterer that includes obsoleteKeyBlockPropertyFilter.
|
||||
pointIter, err = r.NewIterWithBlockPropertyFiltersAndContextEtc(
|
||||
ctx, it.opts.LowerBound, it.opts.UpperBound, nil, /* BlockPropertiesFilterer */
|
||||
false /* hideObsoletePoints */, false, /* useFilterBlock */
|
||||
&it.stats.InternalStats, sstable.TrivialReaderProvider{Reader: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -311,7 +311,7 @@ func finishInitializingExternal(ctx context.Context, it *Iterator) {
|
||||
base.InternalKeySeqNumMax,
|
||||
it.opts.LowerBound, it.opts.UpperBound,
|
||||
&it.hasPrefix, &it.prefixOrFullSeekKey,
|
||||
true /* onlySets */, &it.rangeKey.internal,
|
||||
false /* internalKeys */, &it.rangeKey.internal,
|
||||
)
|
||||
for i := range rangeKeyIters {
|
||||
it.rangeKey.iterConfig.AddLevel(rangeKeyIters[i])
|
||||
@@ -319,8 +319,12 @@ func finishInitializingExternal(ctx context.Context, it *Iterator) {
|
||||
}
|
||||
}
|
||||
if it.rangeKey != nil {
|
||||
it.rangeKey.iiter.Init(&it.comparer, it.iter, it.rangeKey.rangeKeyIter, &it.rangeKeyMasking,
|
||||
it.opts.LowerBound, it.opts.UpperBound)
|
||||
it.rangeKey.iiter.Init(&it.comparer, it.iter, it.rangeKey.rangeKeyIter,
|
||||
keyspan.InterleavingIterOpts{
|
||||
Mask: &it.rangeKeyMasking,
|
||||
LowerBound: it.opts.LowerBound,
|
||||
UpperBound: it.opts.UpperBound,
|
||||
})
|
||||
it.iter = &it.rangeKey.iiter
|
||||
}
|
||||
}
|
||||
|
||||
33
vendor/github.com/cockroachdb/pebble/flushable.go
generated
vendored
33
vendor/github.com/cockroachdb/pebble/flushable.go
generated
vendored
@@ -60,7 +60,7 @@ type flushableEntry struct {
|
||||
// flushable is a memTable, when the reader refs drops to zero, the writer
|
||||
// refs will already be zero because the memtable will have been flushed and
|
||||
// that only occurs once the writer refs drops to zero.
|
||||
readerRefs int32
|
||||
readerRefs atomic.Int32
|
||||
// Closure to invoke to release memory accounting.
|
||||
releaseMemAccounting func()
|
||||
// unrefFiles, if not nil, should be invoked to decrease the ref count of
|
||||
@@ -73,7 +73,7 @@ type flushableEntry struct {
|
||||
}
|
||||
|
||||
func (e *flushableEntry) readerRef() {
|
||||
switch v := atomic.AddInt32(&e.readerRefs, 1); {
|
||||
switch v := e.readerRefs.Add(1); {
|
||||
case v <= 1:
|
||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func (e *flushableEntry) readerUnrefLocked(deleteFiles bool) {
|
||||
func (e *flushableEntry) readerUnrefHelper(
|
||||
deleteFiles bool, deleteFn func(obsolete []*fileBacking),
|
||||
) {
|
||||
switch v := atomic.AddInt32(&e.readerRefs, -1); {
|
||||
switch v := e.readerRefs.Add(-1); {
|
||||
case v < 0:
|
||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||
case v == 0:
|
||||
@@ -117,8 +117,7 @@ type flushableList []*flushableEntry
|
||||
// ingesting sstables which are added to the flushable list.
|
||||
type ingestedFlushable struct {
|
||||
files []physicalMeta
|
||||
cmp Compare
|
||||
split Split
|
||||
comparer *Comparer
|
||||
newIters tableNewIters
|
||||
newRangeKeyIters keyspan.TableNewSpanIter
|
||||
|
||||
@@ -131,8 +130,7 @@ type ingestedFlushable struct {
|
||||
|
||||
func newIngestedFlushable(
|
||||
files []*fileMetadata,
|
||||
cmp Compare,
|
||||
split Split,
|
||||
comparer *Comparer,
|
||||
newIters tableNewIters,
|
||||
newRangeKeyIters keyspan.TableNewSpanIter,
|
||||
) *ingestedFlushable {
|
||||
@@ -147,12 +145,11 @@ func newIngestedFlushable(
|
||||
|
||||
ret := &ingestedFlushable{
|
||||
files: physicalFiles,
|
||||
cmp: cmp,
|
||||
split: split,
|
||||
comparer: comparer,
|
||||
newIters: newIters,
|
||||
newRangeKeyIters: newRangeKeyIters,
|
||||
// slice is immutable and can be set once and used many times.
|
||||
slice: manifest.NewLevelSliceKeySorted(cmp, files),
|
||||
slice: manifest.NewLevelSliceKeySorted(comparer.Compare, files),
|
||||
hasRangeKeys: hasRangeKeys,
|
||||
}
|
||||
|
||||
@@ -162,6 +159,7 @@ func newIngestedFlushable(
|
||||
// TODO(sumeer): ingestedFlushable iters also need to plumb context for
|
||||
// tracing.
|
||||
|
||||
// newIter is part of the flushable interface.
|
||||
func (s *ingestedFlushable) newIter(o *IterOptions) internalIterator {
|
||||
var opts IterOptions
|
||||
if o != nil {
|
||||
@@ -172,10 +170,11 @@ func (s *ingestedFlushable) newIter(o *IterOptions) internalIterator {
|
||||
// aren't truly levels in the lsm. Right now, the encoding only supports
|
||||
// L0 sublevels, and the rest of the levels in the lsm.
|
||||
return newLevelIter(
|
||||
opts, s.cmp, s.split, s.newIters, s.slice.Iter(), manifest.Level(0), nil,
|
||||
opts, s.comparer, s.newIters, s.slice.Iter(), manifest.Level(0), internalIterOpts{},
|
||||
)
|
||||
}
|
||||
|
||||
// newFlushIter is part of the flushable interface.
|
||||
func (s *ingestedFlushable) newFlushIter(o *IterOptions, bytesFlushed *uint64) internalIterator {
|
||||
// newFlushIter is only used for writing memtables to disk as sstables.
|
||||
// Since ingested sstables are already present on disk, they don't need to
|
||||
@@ -184,7 +183,7 @@ func (s *ingestedFlushable) newFlushIter(o *IterOptions, bytesFlushed *uint64) i
|
||||
}
|
||||
|
||||
func (s *ingestedFlushable) constructRangeDelIter(
|
||||
file *manifest.FileMetadata, _ *keyspan.SpanIterOptions,
|
||||
file *manifest.FileMetadata, _ keyspan.SpanIterOptions,
|
||||
) (keyspan.FragmentIterator, error) {
|
||||
// Note that the keyspan level iter expects a non-nil iterator to be
|
||||
// returned even if there is an error. So, we return the emptyKeyspanIter.
|
||||
@@ -199,41 +198,47 @@ func (s *ingestedFlushable) constructRangeDelIter(
|
||||
return rangeDelIter, nil
|
||||
}
|
||||
|
||||
// newRangeDelIter is part of the flushable interface.
|
||||
// TODO(bananabrick): Using a level iter instead of a keyspan level iter to
|
||||
// surface range deletes is more efficient.
|
||||
func (s *ingestedFlushable) newRangeDelIter(_ *IterOptions) keyspan.FragmentIterator {
|
||||
return keyspan.NewLevelIter(
|
||||
keyspan.SpanIterOptions{}, s.cmp,
|
||||
keyspan.SpanIterOptions{}, s.comparer.Compare,
|
||||
s.constructRangeDelIter, s.slice.Iter(), manifest.Level(0),
|
||||
manifest.KeyTypePoint,
|
||||
)
|
||||
}
|
||||
|
||||
// newRangeKeyIter is part of the flushable interface.
|
||||
func (s *ingestedFlushable) newRangeKeyIter(o *IterOptions) keyspan.FragmentIterator {
|
||||
if !s.containsRangeKeys() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return keyspan.NewLevelIter(
|
||||
keyspan.SpanIterOptions{}, s.cmp, s.newRangeKeyIters,
|
||||
keyspan.SpanIterOptions{}, s.comparer.Compare, s.newRangeKeyIters,
|
||||
s.slice.Iter(), manifest.Level(0), manifest.KeyTypeRange,
|
||||
)
|
||||
}
|
||||
|
||||
// containsRangeKeys is part of the flushable interface.
|
||||
func (s *ingestedFlushable) containsRangeKeys() bool {
|
||||
return s.hasRangeKeys
|
||||
}
|
||||
|
||||
// inuseBytes is part of the flushable interface.
|
||||
func (s *ingestedFlushable) inuseBytes() uint64 {
|
||||
// inuseBytes is only used when memtables are flushed to disk as sstables.
|
||||
panic("pebble: not implemented")
|
||||
}
|
||||
|
||||
// totalBytes is part of the flushable interface.
|
||||
func (s *ingestedFlushable) totalBytes() uint64 {
|
||||
// We don't allocate additional bytes for the ingestedFlushable.
|
||||
return 0
|
||||
}
|
||||
|
||||
// readyForFlush is part of the flushable interface.
|
||||
func (s *ingestedFlushable) readyForFlush() bool {
|
||||
// ingestedFlushable should always be ready to flush. However, note that
|
||||
// memtables before the ingested sstables in the memtable queue must be
|
||||
|
||||
84
vendor/github.com/cockroachdb/pebble/format_major_version.go
generated
vendored
84
vendor/github.com/cockroachdb/pebble/format_major_version.go
generated
vendored
@@ -28,10 +28,13 @@ import (
|
||||
// FormatVersion that the default corresponds to may change with time.
|
||||
type FormatMajorVersion uint64
|
||||
|
||||
// SafeValue implements redact.SafeValue.
|
||||
func (v FormatMajorVersion) SafeValue() {}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (v FormatMajorVersion) String() string {
|
||||
// NB: This must not change. It's used as the value for the the
|
||||
// on-disk version marker file.
|
||||
// NB: This must not change. It's used as the value for the on-disk
|
||||
// version marker file.
|
||||
//
|
||||
// Specifically, this value must always parse as a base 10 integer
|
||||
// that fits in a uint64. We format it as zero-padded, 3-digit
|
||||
@@ -110,12 +113,12 @@ const (
|
||||
|
||||
// 23.1 versions.
|
||||
|
||||
// FormatUnusedPrePebblev1MarkedCompacted is an unused format major version.
|
||||
// formatUnusedPrePebblev1MarkedCompacted is an unused format major version.
|
||||
// This format major version was originally intended to ship in the 23.1
|
||||
// release. It was later decided that this should be deferred until a
|
||||
// subsequent release. The original ordering is preserved so as not to
|
||||
// introduce breaking changes in Cockroach.
|
||||
FormatUnusedPrePebblev1MarkedCompacted
|
||||
formatUnusedPrePebblev1MarkedCompacted
|
||||
|
||||
// FormatSSTableValueBlocks is a format major version that adds support for
|
||||
// storing values in value blocks in the sstable. Value block support is not
|
||||
@@ -151,8 +154,28 @@ const (
|
||||
// compactions for files marked for compaction are complete.
|
||||
FormatPrePebblev1MarkedCompacted
|
||||
|
||||
// FormatDeleteSizedAndObsolete is a format major version that adds support
|
||||
// for deletion tombstones that encode the size of the value they're
|
||||
// expected to delete. This format major version is required before the
|
||||
// associated key kind may be committed through batch applications or
|
||||
// ingests. It also adds support for keys that are marked obsolete (see
|
||||
// sstable/format.go for details).
|
||||
FormatDeleteSizedAndObsolete
|
||||
|
||||
// FormatVirtualSSTables is a format major version that adds support for
|
||||
// virtual sstables that can reference a sub-range of keys in an underlying
|
||||
// physical sstable. This information is persisted through new,
|
||||
// backward-incompatible fields in the Manifest, and therefore requires
|
||||
// a format major version.
|
||||
FormatVirtualSSTables
|
||||
|
||||
// internalFormatNewest holds the newest format major version, including
|
||||
// experimental ones excluded from the exported FormatNewest constant until
|
||||
// they've stabilized. Used in tests.
|
||||
internalFormatNewest FormatMajorVersion = iota - 1
|
||||
|
||||
// FormatNewest always contains the most recent format major version.
|
||||
FormatNewest FormatMajorVersion = iota - 1
|
||||
FormatNewest FormatMajorVersion = internalFormatNewest
|
||||
)
|
||||
|
||||
// MaxTableFormat returns the maximum sstable.TableFormat that can be used at
|
||||
@@ -166,10 +189,12 @@ func (v FormatMajorVersion) MaxTableFormat() sstable.TableFormat {
|
||||
FormatSplitUserKeysMarkedCompacted:
|
||||
return sstable.TableFormatPebblev1
|
||||
case FormatRangeKeys, FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
|
||||
FormatUnusedPrePebblev1MarkedCompacted:
|
||||
formatUnusedPrePebblev1MarkedCompacted:
|
||||
return sstable.TableFormatPebblev2
|
||||
case FormatSSTableValueBlocks, FormatFlushableIngest, FormatPrePebblev1MarkedCompacted:
|
||||
return sstable.TableFormatPebblev3
|
||||
case FormatDeleteSizedAndObsolete, FormatVirtualSSTables:
|
||||
return sstable.TableFormatPebblev4
|
||||
default:
|
||||
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
|
||||
}
|
||||
@@ -185,14 +210,27 @@ func (v FormatMajorVersion) MinTableFormat() sstable.TableFormat {
|
||||
FormatRangeKeys:
|
||||
return sstable.TableFormatLevelDB
|
||||
case FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
|
||||
FormatUnusedPrePebblev1MarkedCompacted, FormatSSTableValueBlocks,
|
||||
FormatFlushableIngest, FormatPrePebblev1MarkedCompacted:
|
||||
formatUnusedPrePebblev1MarkedCompacted, FormatSSTableValueBlocks,
|
||||
FormatFlushableIngest, FormatPrePebblev1MarkedCompacted,
|
||||
FormatDeleteSizedAndObsolete, FormatVirtualSSTables:
|
||||
return sstable.TableFormatPebblev1
|
||||
default:
|
||||
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
|
||||
}
|
||||
}
|
||||
|
||||
// orderingInvariants returns an enum encoding the set of invariants that must
|
||||
// hold within the receiver format major version. Invariants only get stricter
|
||||
// as the format major version advances, so it is okay to retrieve the
|
||||
// invariants from the current format major version and by the time the
|
||||
// invariants are enforced, the format major version has advanced.
|
||||
func (v FormatMajorVersion) orderingInvariants() manifest.OrderingInvariants {
|
||||
if v < FormatSplitUserKeysMarkedCompacted {
|
||||
return manifest.AllowSplitUserKeys
|
||||
}
|
||||
return manifest.ProhibitSplitUserKeys
|
||||
}
|
||||
|
||||
// formatMajorVersionMigrations defines the migrations from one format
|
||||
// major version to the next. Each migration is defined as a closure
|
||||
// which will be invoked on the database before the new format major
|
||||
@@ -300,9 +338,9 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
|
||||
}
|
||||
return d.finalizeFormatVersUpgrade(FormatPrePebblev1Marked)
|
||||
},
|
||||
FormatUnusedPrePebblev1MarkedCompacted: func(d *DB) error {
|
||||
formatUnusedPrePebblev1MarkedCompacted: func(d *DB) error {
|
||||
// Intentional no-op.
|
||||
return d.finalizeFormatVersUpgrade(FormatUnusedPrePebblev1MarkedCompacted)
|
||||
return d.finalizeFormatVersUpgrade(formatUnusedPrePebblev1MarkedCompacted)
|
||||
},
|
||||
FormatSSTableValueBlocks: func(d *DB) error {
|
||||
return d.finalizeFormatVersUpgrade(FormatSSTableValueBlocks)
|
||||
@@ -319,6 +357,12 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
|
||||
}
|
||||
return d.finalizeFormatVersUpgrade(FormatPrePebblev1MarkedCompacted)
|
||||
},
|
||||
FormatDeleteSizedAndObsolete: func(d *DB) error {
|
||||
return d.finalizeFormatVersUpgrade(FormatDeleteSizedAndObsolete)
|
||||
},
|
||||
FormatVirtualSSTables: func(d *DB) error {
|
||||
return d.finalizeFormatVersUpgrade(FormatVirtualSSTables)
|
||||
},
|
||||
}
|
||||
|
||||
const formatVersionMarkerName = `format-version`
|
||||
@@ -341,7 +385,7 @@ func lookupFormatMajorVersion(
|
||||
if vers == FormatDefault {
|
||||
return 0, nil, errors.Newf("pebble: default format major version should not persisted", vers)
|
||||
}
|
||||
if vers > FormatNewest {
|
||||
if vers > internalFormatNewest {
|
||||
return 0, nil, errors.Newf("pebble: database %q written in format major version %d", dirname, vers)
|
||||
}
|
||||
return vers, m, nil
|
||||
@@ -352,9 +396,7 @@ func lookupFormatMajorVersion(
|
||||
// provided in Options when the database was opened if the existing
|
||||
// database was written with a higher format version.
|
||||
func (d *DB) FormatMajorVersion() FormatMajorVersion {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
return d.mu.formatVers.vers
|
||||
return FormatMajorVersion(d.mu.formatVers.vers.Load())
|
||||
}
|
||||
|
||||
// RatchetFormatMajorVersion ratchets the opened database's format major
|
||||
@@ -377,13 +419,13 @@ func (d *DB) ratchetFormatMajorVersionLocked(formatVers FormatMajorVersion) erro
|
||||
if d.opts.ReadOnly {
|
||||
return ErrReadOnly
|
||||
}
|
||||
if formatVers > FormatNewest {
|
||||
// Guard against accidentally forgetting to update FormatNewest.
|
||||
if formatVers > internalFormatNewest {
|
||||
// Guard against accidentally forgetting to update internalFormatNewest.
|
||||
return errors.Errorf("pebble: unknown format version %d", formatVers)
|
||||
}
|
||||
if d.mu.formatVers.vers > formatVers {
|
||||
if currentVers := d.FormatMajorVersion(); currentVers > formatVers {
|
||||
return errors.Newf("pebble: database already at format major version %d; cannot reduce to %d",
|
||||
d.mu.formatVers.vers, formatVers)
|
||||
currentVers, formatVers)
|
||||
}
|
||||
if d.mu.formatVers.ratcheting {
|
||||
return errors.Newf("pebble: database format major version upgrade is in-progress")
|
||||
@@ -391,7 +433,7 @@ func (d *DB) ratchetFormatMajorVersionLocked(formatVers FormatMajorVersion) erro
|
||||
d.mu.formatVers.ratcheting = true
|
||||
defer func() { d.mu.formatVers.ratcheting = false }()
|
||||
|
||||
for nextVers := d.mu.formatVers.vers + 1; nextVers <= formatVers; nextVers++ {
|
||||
for nextVers := d.FormatMajorVersion() + 1; nextVers <= formatVers; nextVers++ {
|
||||
if err := formatMajorVersionMigrations[nextVers](d); err != nil {
|
||||
return errors.Wrapf(err, "migrating to version %d", nextVers)
|
||||
}
|
||||
@@ -402,7 +444,7 @@ func (d *DB) ratchetFormatMajorVersionLocked(formatVers FormatMajorVersion) erro
|
||||
// update in-memory state (without ever dropping locks) after
|
||||
// the upgrade is finalized. Here we assert that the upgrade
|
||||
// did occur.
|
||||
if d.mu.formatVers.vers != nextVers {
|
||||
if d.FormatMajorVersion() != nextVers {
|
||||
d.opts.Logger.Fatalf("pebble: successful migration to format version %d never finalized the upgrade", nextVers)
|
||||
}
|
||||
}
|
||||
@@ -421,7 +463,7 @@ func (d *DB) finalizeFormatVersUpgrade(formatVers FormatMajorVersion) error {
|
||||
if err := d.mu.formatVers.marker.Move(formatVers.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
d.mu.formatVers.vers = formatVers
|
||||
d.mu.formatVers.vers.Store(uint64(formatVers))
|
||||
d.opts.EventListener.FormatUpgrade(formatVers)
|
||||
return nil
|
||||
}
|
||||
|
||||
45
vendor/github.com/cockroachdb/pebble/get_iter.go
generated
vendored
45
vendor/github.com/cockroachdb/pebble/get_iter.go
generated
vendored
@@ -19,8 +19,7 @@ import (
|
||||
// lazily.
|
||||
type getIter struct {
|
||||
logger Logger
|
||||
cmp Compare
|
||||
equal Equal
|
||||
comparer *Comparer
|
||||
newIters tableNewIters
|
||||
snapshot uint64
|
||||
key []byte
|
||||
@@ -83,7 +82,7 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
// key. Every call to levelIter.Next() potentially switches to a new
|
||||
// table and thus reinitializes rangeDelIter.
|
||||
if g.rangeDelIter != nil {
|
||||
g.tombstone = keyspan.Get(g.cmp, g.rangeDelIter, g.key)
|
||||
g.tombstone = keyspan.Get(g.comparer.Compare, g.rangeDelIter, g.key)
|
||||
if g.err = g.rangeDelIter.Close(); g.err != nil {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
@@ -101,7 +100,7 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
g.iter = nil
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
if g.equal(g.key, key.UserKey) {
|
||||
if g.comparer.Equal(g.key, key.UserKey) {
|
||||
if !key.Visible(g.snapshot, base.InternalKeySeqNumMax) {
|
||||
g.iterKey, g.iterValue = g.iter.Next()
|
||||
continue
|
||||
@@ -158,12 +157,25 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
if n := len(g.l0); n > 0 {
|
||||
files := g.l0[n-1].Iter()
|
||||
g.l0 = g.l0[:n-1]
|
||||
iterOpts := IterOptions{logger: g.logger}
|
||||
g.levelIter.init(context.Background(), iterOpts, g.cmp, nil /* split */, g.newIters,
|
||||
iterOpts := IterOptions{logger: g.logger, snapshotForHideObsoletePoints: g.snapshot}
|
||||
g.levelIter.init(context.Background(), iterOpts, g.comparer, g.newIters,
|
||||
files, manifest.L0Sublevel(n), internalIterOpts{})
|
||||
g.levelIter.initRangeDel(&g.rangeDelIter)
|
||||
bc := levelIterBoundaryContext{}
|
||||
g.levelIter.initBoundaryContext(&bc)
|
||||
g.iter = &g.levelIter
|
||||
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, base.SeekGEFlagsNone)
|
||||
|
||||
// Compute the key prefix for bloom filtering if split function is
|
||||
// specified, or use the user key as default.
|
||||
prefix := g.key
|
||||
if g.comparer.Split != nil {
|
||||
prefix = g.key[:g.comparer.Split(g.key)]
|
||||
}
|
||||
g.iterKey, g.iterValue = g.iter.SeekPrefixGE(prefix, g.key, base.SeekGEFlagsNone)
|
||||
if bc.isSyntheticIterBoundsKey || bc.isIgnorableBoundaryKey {
|
||||
g.iterKey = nil
|
||||
g.iterValue = base.LazyValue{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
g.level++
|
||||
@@ -177,13 +189,26 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
iterOpts := IterOptions{logger: g.logger}
|
||||
g.levelIter.init(context.Background(), iterOpts, g.cmp, nil /* split */, g.newIters,
|
||||
iterOpts := IterOptions{logger: g.logger, snapshotForHideObsoletePoints: g.snapshot}
|
||||
g.levelIter.init(context.Background(), iterOpts, g.comparer, g.newIters,
|
||||
g.version.Levels[g.level].Iter(), manifest.Level(g.level), internalIterOpts{})
|
||||
g.levelIter.initRangeDel(&g.rangeDelIter)
|
||||
bc := levelIterBoundaryContext{}
|
||||
g.levelIter.initBoundaryContext(&bc)
|
||||
g.level++
|
||||
g.iter = &g.levelIter
|
||||
g.iterKey, g.iterValue = g.iter.SeekGE(g.key, base.SeekGEFlagsNone)
|
||||
|
||||
// Compute the key prefix for bloom filtering if split function is
|
||||
// specified, or use the user key as default.
|
||||
prefix := g.key
|
||||
if g.comparer.Split != nil {
|
||||
prefix = g.key[:g.comparer.Split(g.key)]
|
||||
}
|
||||
g.iterKey, g.iterValue = g.iter.SeekPrefixGE(prefix, g.key, base.SeekGEFlagsNone)
|
||||
if bc.isSyntheticIterBoundsKey || bc.isIgnorableBoundaryKey {
|
||||
g.iterKey = nil
|
||||
g.iterValue = base.LazyValue{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1438
vendor/github.com/cockroachdb/pebble/ingest.go
generated
vendored
1438
vendor/github.com/cockroachdb/pebble/ingest.go
generated
vendored
File diff suppressed because it is too large
Load Diff
1
vendor/github.com/cockroachdb/pebble/internal.go
generated
vendored
1
vendor/github.com/cockroachdb/pebble/internal.go
generated
vendored
@@ -23,6 +23,7 @@ const (
|
||||
InternalKeyKindRangeKeyUnset = base.InternalKeyKindRangeKeyUnset
|
||||
InternalKeyKindRangeKeyDelete = base.InternalKeyKindRangeKeyDelete
|
||||
InternalKeyKindIngestSST = base.InternalKeyKindIngestSST
|
||||
InternalKeyKindDeleteSized = base.InternalKeyKindDeleteSized
|
||||
InternalKeyKindInvalid = base.InternalKeyKindInvalid
|
||||
InternalKeySeqNumBatch = base.InternalKeySeqNumBatch
|
||||
InternalKeySeqNumMax = base.InternalKeySeqNumMax
|
||||
|
||||
56
vendor/github.com/cockroachdb/pebble/internal/arenaskl/arena.go
generated
vendored
56
vendor/github.com/cockroachdb/pebble/internal/arenaskl/arena.go
generated
vendored
@@ -18,22 +18,21 @@
|
||||
package arenaskl
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/constants"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
)
|
||||
|
||||
// Arena is lock-free.
|
||||
type Arena struct {
|
||||
n uint64
|
||||
n atomic.Uint64
|
||||
buf []byte
|
||||
}
|
||||
|
||||
const (
|
||||
align4 = 3
|
||||
)
|
||||
const nodeAlignment = 4
|
||||
|
||||
var (
|
||||
// ErrArenaFull indicates that the arena is full and cannot perform any more
|
||||
@@ -44,20 +43,28 @@ var (
|
||||
// NewArena allocates a new arena using the specified buffer as the backing
|
||||
// store.
|
||||
func NewArena(buf []byte) *Arena {
|
||||
// Don't store data at position 0 in order to reserve offset=0 as a kind
|
||||
// of nil pointer.
|
||||
return &Arena{
|
||||
n: 1,
|
||||
if len(buf) > constants.MaxUint32OrInt {
|
||||
if invariants.Enabled {
|
||||
panic(errors.AssertionFailedf("attempting to create arena of size %d", len(buf)))
|
||||
}
|
||||
buf = buf[:constants.MaxUint32OrInt]
|
||||
}
|
||||
a := &Arena{
|
||||
buf: buf,
|
||||
}
|
||||
// We don't store data at position 0 in order to reserve offset=0 as a kind of
|
||||
// nil pointer.
|
||||
a.n.Store(1)
|
||||
return a
|
||||
}
|
||||
|
||||
// Size returns the number of bytes allocated by the arena.
|
||||
func (a *Arena) Size() uint32 {
|
||||
s := atomic.LoadUint64(&a.n)
|
||||
if s > math.MaxUint32 {
|
||||
// Saturate at MaxUint32.
|
||||
return math.MaxUint32
|
||||
s := a.n.Load()
|
||||
if s > constants.MaxUint32OrInt {
|
||||
// The last failed allocation can push the size higher than len(a.buf).
|
||||
// Saturate at the maximum representable offset.
|
||||
return constants.MaxUint32OrInt
|
||||
}
|
||||
return uint32(s)
|
||||
}
|
||||
@@ -67,24 +74,33 @@ func (a *Arena) Capacity() uint32 {
|
||||
return uint32(len(a.buf))
|
||||
}
|
||||
|
||||
func (a *Arena) alloc(size, align, overflow uint32) (uint32, uint32, error) {
|
||||
// alloc allocates a buffer of the given size and with the given alignment
|
||||
// (which must be a power of 2).
|
||||
//
|
||||
// If overflow is not 0, it also ensures that many bytes after the buffer are
|
||||
// inside the arena (this is used for structures that are larger than the
|
||||
// requested size but don't use those extra bytes).
|
||||
func (a *Arena) alloc(size, alignment, overflow uint32) (uint32, uint32, error) {
|
||||
if invariants.Enabled && (alignment&(alignment-1)) != 0 {
|
||||
panic(errors.AssertionFailedf("invalid alignment %d", alignment))
|
||||
}
|
||||
// Verify that the arena isn't already full.
|
||||
origSize := atomic.LoadUint64(&a.n)
|
||||
origSize := a.n.Load()
|
||||
if int(origSize) > len(a.buf) {
|
||||
return 0, 0, ErrArenaFull
|
||||
}
|
||||
|
||||
// Pad the allocation with enough bytes to ensure the requested alignment.
|
||||
padded := uint32(size) + align
|
||||
padded := uint64(size) + uint64(alignment) - 1
|
||||
|
||||
newSize := atomic.AddUint64(&a.n, uint64(padded))
|
||||
if int(newSize)+int(overflow) > len(a.buf) {
|
||||
newSize := a.n.Add(padded)
|
||||
if newSize+uint64(overflow) > uint64(len(a.buf)) {
|
||||
return 0, 0, ErrArenaFull
|
||||
}
|
||||
|
||||
// Return the aligned offset.
|
||||
offset := (uint32(newSize) - padded + align) & ^align
|
||||
return offset, padded, nil
|
||||
offset := (uint32(newSize) - size) & ^(alignment - 1)
|
||||
return offset, uint32(padded), nil
|
||||
}
|
||||
|
||||
func (a *Arena) getBytes(offset uint32, size uint32) []byte {
|
||||
|
||||
14
vendor/github.com/cockroachdb/pebble/internal/arenaskl/iterator.go
generated
vendored
14
vendor/github.com/cockroachdb/pebble/internal/arenaskl/iterator.go
generated
vendored
@@ -18,7 +18,6 @@
|
||||
package arenaskl
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
@@ -240,17 +239,8 @@ func (it *Iterator) SetBounds(lower, upper []byte) {
|
||||
}
|
||||
|
||||
func (it *Iterator) decodeKey() {
|
||||
b := it.list.arena.getBytes(it.nd.keyOffset, it.nd.keySize)
|
||||
// This is a manual inline of base.DecodeInternalKey, because the Go compiler
|
||||
// seems to refuse to automatically inline it currently.
|
||||
l := len(b) - 8
|
||||
if l >= 0 {
|
||||
it.key.Trailer = binary.LittleEndian.Uint64(b[l:])
|
||||
it.key.UserKey = b[:l:l]
|
||||
} else {
|
||||
it.key.Trailer = uint64(base.InternalKeyKindInvalid)
|
||||
it.key.UserKey = nil
|
||||
}
|
||||
it.key.UserKey = it.list.arena.getBytes(it.nd.keyOffset, it.nd.keySize)
|
||||
it.key.Trailer = it.nd.keyTrailer
|
||||
}
|
||||
|
||||
func (it *Iterator) seekForBaseSplice(key []byte) (prev, next *node, found bool) {
|
||||
|
||||
36
vendor/github.com/cockroachdb/pebble/internal/arenaskl/node.go
generated
vendored
36
vendor/github.com/cockroachdb/pebble/internal/arenaskl/node.go
generated
vendored
@@ -29,25 +29,27 @@ import (
|
||||
// is used here. If a key/value overflows a uint32, it should not be added to
|
||||
// the skiplist.
|
||||
func MaxNodeSize(keySize, valueSize uint32) uint64 {
|
||||
return uint64(maxNodeSize) + uint64(keySize) + uint64(valueSize) + align4
|
||||
const maxPadding = nodeAlignment - 1
|
||||
return uint64(maxNodeSize) + uint64(keySize) + uint64(valueSize) + maxPadding
|
||||
}
|
||||
|
||||
type links struct {
|
||||
nextOffset uint32
|
||||
prevOffset uint32
|
||||
nextOffset atomic.Uint32
|
||||
prevOffset atomic.Uint32
|
||||
}
|
||||
|
||||
func (l *links) init(prevOffset, nextOffset uint32) {
|
||||
l.nextOffset = nextOffset
|
||||
l.prevOffset = prevOffset
|
||||
l.nextOffset.Store(nextOffset)
|
||||
l.prevOffset.Store(prevOffset)
|
||||
}
|
||||
|
||||
type node struct {
|
||||
// Immutable fields, so no need to lock to access key.
|
||||
keyOffset uint32
|
||||
keySize uint32
|
||||
valueSize uint32
|
||||
allocSize uint32
|
||||
keyOffset uint32
|
||||
keySize uint32
|
||||
keyTrailer uint64
|
||||
valueSize uint32
|
||||
allocSize uint32
|
||||
|
||||
// Most nodes do not need to use the full height of the tower, since the
|
||||
// probability of each successive level decreases exponentially. Because
|
||||
@@ -65,7 +67,7 @@ func newNode(
|
||||
if height < 1 || height > maxHeight {
|
||||
panic("height cannot be less than one or greater than the max height")
|
||||
}
|
||||
keySize := key.Size()
|
||||
keySize := len(key.UserKey)
|
||||
if int64(keySize) > math.MaxUint32 {
|
||||
panic("key is too large")
|
||||
}
|
||||
@@ -81,8 +83,8 @@ func newNode(
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
key.Encode(nd.getKeyBytes(arena))
|
||||
nd.keyTrailer = key.Trailer
|
||||
copy(nd.getKeyBytes(arena), key.UserKey)
|
||||
copy(nd.getValue(arena), value)
|
||||
return
|
||||
}
|
||||
@@ -93,7 +95,7 @@ func newRawNode(arena *Arena, height uint32, keySize, valueSize uint32) (nd *nod
|
||||
unusedSize := uint32((maxHeight - int(height)) * linksSize)
|
||||
nodeSize := uint32(maxNodeSize) - unusedSize
|
||||
|
||||
nodeOffset, allocSize, err := arena.alloc(nodeSize+keySize+valueSize, align4, unusedSize)
|
||||
nodeOffset, allocSize, err := arena.alloc(nodeSize+keySize+valueSize, nodeAlignment, unusedSize)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -115,17 +117,17 @@ func (n *node) getValue(arena *Arena) []byte {
|
||||
}
|
||||
|
||||
func (n *node) nextOffset(h int) uint32 {
|
||||
return atomic.LoadUint32(&n.tower[h].nextOffset)
|
||||
return n.tower[h].nextOffset.Load()
|
||||
}
|
||||
|
||||
func (n *node) prevOffset(h int) uint32 {
|
||||
return atomic.LoadUint32(&n.tower[h].prevOffset)
|
||||
return n.tower[h].prevOffset.Load()
|
||||
}
|
||||
|
||||
func (n *node) casNextOffset(h int, old, val uint32) bool {
|
||||
return atomic.CompareAndSwapUint32(&n.tower[h].nextOffset, old, val)
|
||||
return n.tower[h].nextOffset.CompareAndSwap(old, val)
|
||||
}
|
||||
|
||||
func (n *node) casPrevOffset(h int, old, val uint32) bool {
|
||||
return atomic.CompareAndSwapUint32(&n.tower[h].prevOffset, old, val)
|
||||
return n.tower[h].prevOffset.CompareAndSwap(old, val)
|
||||
}
|
||||
|
||||
51
vendor/github.com/cockroachdb/pebble/internal/arenaskl/skl.go
generated
vendored
51
vendor/github.com/cockroachdb/pebble/internal/arenaskl/skl.go
generated
vendored
@@ -44,7 +44,6 @@ Key differences:
|
||||
package arenaskl // import "github.com/cockroachdb/pebble/internal/arenaskl"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
@@ -80,7 +79,7 @@ type Skiplist struct {
|
||||
cmp base.Compare
|
||||
head *node
|
||||
tail *node
|
||||
height uint32 // Current height. 1 <= height <= maxHeight. CAS.
|
||||
height atomic.Uint32 // Current height. 1 <= height <= maxHeight. CAS.
|
||||
|
||||
// If set to true by tests, then extra delays are added to make it easier to
|
||||
// detect unusual race conditions.
|
||||
@@ -140,22 +139,22 @@ func (s *Skiplist) Reset(arena *Arena, cmp base.Compare) {
|
||||
headOffset := arena.getPointerOffset(unsafe.Pointer(head))
|
||||
tailOffset := arena.getPointerOffset(unsafe.Pointer(tail))
|
||||
for i := 0; i < maxHeight; i++ {
|
||||
head.tower[i].nextOffset = tailOffset
|
||||
tail.tower[i].prevOffset = headOffset
|
||||
head.tower[i].nextOffset.Store(tailOffset)
|
||||
tail.tower[i].prevOffset.Store(headOffset)
|
||||
}
|
||||
|
||||
*s = Skiplist{
|
||||
arena: arena,
|
||||
cmp: cmp,
|
||||
head: head,
|
||||
tail: tail,
|
||||
height: 1,
|
||||
arena: arena,
|
||||
cmp: cmp,
|
||||
head: head,
|
||||
tail: tail,
|
||||
}
|
||||
s.height.Store(1)
|
||||
}
|
||||
|
||||
// Height returns the height of the highest tower within any of the nodes that
|
||||
// have ever been allocated as part of this skiplist.
|
||||
func (s *Skiplist) Height() uint32 { return atomic.LoadUint32(&s.height) }
|
||||
func (s *Skiplist) Height() uint32 { return s.height.Load() }
|
||||
|
||||
// Arena returns the arena backing this skiplist.
|
||||
func (s *Skiplist) Arena() *Arena { return s.arena }
|
||||
@@ -327,7 +326,7 @@ func (s *Skiplist) newNode(
|
||||
// Try to increase s.height via CAS.
|
||||
listHeight := s.Height()
|
||||
for height > listHeight {
|
||||
if atomic.CompareAndSwapUint32(&s.height, listHeight, height) {
|
||||
if s.height.CompareAndSwap(listHeight, height) {
|
||||
// Successfully increased skiplist.height.
|
||||
break
|
||||
}
|
||||
@@ -412,26 +411,19 @@ func (s *Skiplist) findSpliceForLevel(
|
||||
|
||||
offset, size := next.keyOffset, next.keySize
|
||||
nextKey := s.arena.buf[offset : offset+size]
|
||||
n := int32(size) - 8
|
||||
cmp := s.cmp(key.UserKey, nextKey[:n])
|
||||
cmp := s.cmp(key.UserKey, nextKey)
|
||||
if cmp < 0 {
|
||||
// We are done for this level, since prev.key < key < next.key.
|
||||
break
|
||||
}
|
||||
if cmp == 0 {
|
||||
// User-key equality.
|
||||
var nextTrailer uint64
|
||||
if n >= 0 {
|
||||
nextTrailer = binary.LittleEndian.Uint64(nextKey[n:])
|
||||
} else {
|
||||
nextTrailer = uint64(base.InternalKeyKindInvalid)
|
||||
}
|
||||
if key.Trailer == nextTrailer {
|
||||
if key.Trailer == next.keyTrailer {
|
||||
// Internal key equality.
|
||||
found = true
|
||||
break
|
||||
}
|
||||
if key.Trailer > nextTrailer {
|
||||
if key.Trailer > next.keyTrailer {
|
||||
// We are done for this level, since prev.key < key < next.key.
|
||||
break
|
||||
}
|
||||
@@ -446,8 +438,7 @@ func (s *Skiplist) findSpliceForLevel(
|
||||
|
||||
func (s *Skiplist) keyIsAfterNode(nd *node, key base.InternalKey) bool {
|
||||
ndKey := s.arena.buf[nd.keyOffset : nd.keyOffset+nd.keySize]
|
||||
n := int32(nd.keySize) - 8
|
||||
cmp := s.cmp(ndKey[:n], key.UserKey)
|
||||
cmp := s.cmp(ndKey, key.UserKey)
|
||||
if cmp < 0 {
|
||||
return true
|
||||
}
|
||||
@@ -455,25 +446,19 @@ func (s *Skiplist) keyIsAfterNode(nd *node, key base.InternalKey) bool {
|
||||
return false
|
||||
}
|
||||
// User-key equality.
|
||||
var ndTrailer uint64
|
||||
if n >= 0 {
|
||||
ndTrailer = binary.LittleEndian.Uint64(ndKey[n:])
|
||||
} else {
|
||||
ndTrailer = uint64(base.InternalKeyKindInvalid)
|
||||
}
|
||||
if key.Trailer == ndTrailer {
|
||||
if key.Trailer == nd.keyTrailer {
|
||||
// Internal key equality.
|
||||
return false
|
||||
}
|
||||
return key.Trailer < ndTrailer
|
||||
return key.Trailer < nd.keyTrailer
|
||||
}
|
||||
|
||||
func (s *Skiplist) getNext(nd *node, h int) *node {
|
||||
offset := atomic.LoadUint32(&nd.tower[h].nextOffset)
|
||||
offset := nd.tower[h].nextOffset.Load()
|
||||
return (*node)(s.arena.getPointer(offset))
|
||||
}
|
||||
|
||||
func (s *Skiplist) getPrev(nd *node, h int) *node {
|
||||
offset := atomic.LoadUint32(&nd.tower[h].prevOffset)
|
||||
offset := nd.tower[h].prevOffset.Load()
|
||||
return (*node)(s.arena.getPointer(offset))
|
||||
}
|
||||
|
||||
10
vendor/github.com/cockroachdb/pebble/internal/base/filenames.go
generated
vendored
10
vendor/github.com/cockroachdb/pebble/internal/base/filenames.go
generated
vendored
@@ -20,6 +20,11 @@ type FileNum uint64
|
||||
// String returns a string representation of the file number.
|
||||
func (fn FileNum) String() string { return fmt.Sprintf("%06d", fn) }
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (fn FileNum) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Printf("%06d", redact.SafeUint(fn))
|
||||
}
|
||||
|
||||
// DiskFileNum converts a FileNum to a DiskFileNum. DiskFileNum should only be
|
||||
// called if the caller can ensure that the FileNum belongs to a physical file
|
||||
// on disk. These could be manifests, log files, physical sstables on disk, the
|
||||
@@ -40,6 +45,11 @@ type DiskFileNum struct {
|
||||
|
||||
func (dfn DiskFileNum) String() string { return dfn.fn.String() }
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (dfn DiskFileNum) SafeFormat(w redact.SafePrinter, verb rune) {
|
||||
dfn.fn.SafeFormat(w, verb)
|
||||
}
|
||||
|
||||
// FileNum converts a DiskFileNum to a FileNum. This conversion is always valid.
|
||||
func (dfn DiskFileNum) FileNum() FileNum {
|
||||
return dfn.fn
|
||||
|
||||
56
vendor/github.com/cockroachdb/pebble/internal/base/internal.go
generated
vendored
56
vendor/github.com/cockroachdb/pebble/internal/base/internal.go
generated
vendored
@@ -9,6 +9,17 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/redact"
|
||||
)
|
||||
|
||||
const (
|
||||
// SeqNumZero is the zero sequence number, set by compactions if they can
|
||||
// guarantee there are no keys underneath an internal key.
|
||||
SeqNumZero = uint64(0)
|
||||
// SeqNumStart is the first sequence number assigned to a key. Sequence
|
||||
// numbers 1-9 are reserved for potential future use.
|
||||
SeqNumStart = uint64(10)
|
||||
)
|
||||
|
||||
// InternalKeyKind enumerates the kind of key: a deletion tombstone, a set
|
||||
@@ -24,6 +35,14 @@ const (
|
||||
//InternalKeyKindColumnFamilyDeletion InternalKeyKind = 4
|
||||
//InternalKeyKindColumnFamilyValue InternalKeyKind = 5
|
||||
//InternalKeyKindColumnFamilyMerge InternalKeyKind = 6
|
||||
|
||||
// InternalKeyKindSingleDelete (SINGLEDEL) is a performance optimization
|
||||
// solely for compactions (to reduce write amp and space amp). Readers other
|
||||
// than compactions should treat SINGLEDEL as equivalent to a DEL.
|
||||
// Historically, it was simpler for readers other than compactions to treat
|
||||
// SINGLEDEL as equivalent to DEL, but as of the introduction of
|
||||
// InternalKeyKindSSTableInternalObsoleteBit, this is also necessary for
|
||||
// correctness.
|
||||
InternalKeyKindSingleDelete InternalKeyKind = 7
|
||||
//InternalKeyKindColumnFamilySingleDelete InternalKeyKind = 8
|
||||
//InternalKeyKindBeginPrepareXID InternalKeyKind = 9
|
||||
@@ -66,8 +85,15 @@ const (
|
||||
// batch, or in an sstable.
|
||||
InternalKeyKindIngestSST InternalKeyKind = 22
|
||||
|
||||
// This maximum value isn't part of the file format. It's unlikely,
|
||||
// but future extensions may increase this value.
|
||||
// InternalKeyKindDeleteSized keys behave identically to
|
||||
// InternalKeyKindDelete keys, except that they hold an associated uint64
|
||||
// value indicating the (len(key)+len(value)) of the shadowed entry the
|
||||
// tombstone is expected to delete. This value is used to inform compaction
|
||||
// heuristics, but is not required to be accurate for correctness.
|
||||
InternalKeyKindDeleteSized InternalKeyKind = 23
|
||||
|
||||
// This maximum value isn't part of the file format. Future extensions may
|
||||
// increase this value.
|
||||
//
|
||||
// When constructing an internal key to pass to DB.Seek{GE,LE},
|
||||
// internalKeyComparer sorts decreasing by kind (after sorting increasing by
|
||||
@@ -75,14 +101,19 @@ const (
|
||||
// which sorts 'less than or equal to' any other valid internalKeyKind, when
|
||||
// searching for any kind of internal key formed by a certain user key and
|
||||
// seqNum.
|
||||
InternalKeyKindMax InternalKeyKind = 22
|
||||
InternalKeyKindMax InternalKeyKind = 23
|
||||
|
||||
// Internal to the sstable format. Not exposed by any sstable iterator.
|
||||
// Declared here to prevent definition of valid key kinds that set this bit.
|
||||
InternalKeyKindSSTableInternalObsoleteBit InternalKeyKind = 64
|
||||
InternalKeyKindSSTableInternalObsoleteMask InternalKeyKind = 191
|
||||
|
||||
// InternalKeyZeroSeqnumMaxTrailer is the largest trailer with a
|
||||
// zero sequence number.
|
||||
InternalKeyZeroSeqnumMaxTrailer = uint64(InternalKeyKindInvalid)
|
||||
InternalKeyZeroSeqnumMaxTrailer = uint64(255)
|
||||
|
||||
// A marker for an invalid key.
|
||||
InternalKeyKindInvalid InternalKeyKind = 255
|
||||
InternalKeyKindInvalid InternalKeyKind = InternalKeyKindSSTableInternalObsoleteMask
|
||||
|
||||
// InternalKeySeqNumBatch is a bit that is set on batch sequence numbers
|
||||
// which prevents those entries from being excluded from iteration.
|
||||
@@ -105,6 +136,9 @@ const (
|
||||
InternalKeyBoundaryRangeKey = (InternalKeySeqNumMax << 8) | uint64(InternalKeyKindRangeKeySet)
|
||||
)
|
||||
|
||||
// Assert InternalKeyKindSSTableInternalObsoleteBit > InternalKeyKindMax
|
||||
const _ = uint(InternalKeyKindSSTableInternalObsoleteBit - InternalKeyKindMax - 1)
|
||||
|
||||
var internalKeyKindNames = []string{
|
||||
InternalKeyKindDelete: "DEL",
|
||||
InternalKeyKindSet: "SET",
|
||||
@@ -118,6 +152,7 @@ var internalKeyKindNames = []string{
|
||||
InternalKeyKindRangeKeyUnset: "RANGEKEYUNSET",
|
||||
InternalKeyKindRangeKeyDelete: "RANGEKEYDEL",
|
||||
InternalKeyKindIngestSST: "INGESTSST",
|
||||
InternalKeyKindDeleteSized: "DELSIZED",
|
||||
InternalKeyKindInvalid: "INVALID",
|
||||
}
|
||||
|
||||
@@ -128,6 +163,11 @@ func (k InternalKeyKind) String() string {
|
||||
return fmt.Sprintf("UNKNOWN:%d", k)
|
||||
}
|
||||
|
||||
// SafeFormat implements redact.SafeFormatter.
|
||||
func (k InternalKeyKind) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
w.Print(redact.SafeString(k.String()))
|
||||
}
|
||||
|
||||
// InternalKey is a key used for the in-memory and on-disk partial DBs that
|
||||
// make up a pebble DB.
|
||||
//
|
||||
@@ -204,6 +244,7 @@ var kindsMap = map[string]InternalKeyKind{
|
||||
"RANGEKEYUNSET": InternalKeyKindRangeKeyUnset,
|
||||
"RANGEKEYDEL": InternalKeyKindRangeKeyDelete,
|
||||
"INGESTSST": InternalKeyKindIngestSST,
|
||||
"DELSIZED": InternalKeyKindDeleteSized,
|
||||
}
|
||||
|
||||
// ParseInternalKey parses the string representation of an internal key. The
|
||||
@@ -340,6 +381,11 @@ func (k InternalKey) SeqNum() uint64 {
|
||||
return k.Trailer >> 8
|
||||
}
|
||||
|
||||
// SeqNumFromTrailer returns the sequence number component of a trailer.
|
||||
func SeqNumFromTrailer(t uint64) uint64 {
|
||||
return t >> 8
|
||||
}
|
||||
|
||||
// Visible returns true if the key is visible at the specified snapshot
|
||||
// sequence number.
|
||||
func (k InternalKey) Visible(snapshot, batchSnapshot uint64) bool {
|
||||
|
||||
35
vendor/github.com/cockroachdb/pebble/internal/base/iterator.go
generated
vendored
35
vendor/github.com/cockroachdb/pebble/internal/base/iterator.go
generated
vendored
@@ -7,6 +7,9 @@ package base
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/humanize"
|
||||
"github.com/cockroachdb/redact"
|
||||
)
|
||||
|
||||
// InternalIterator iterates over a DB's key/value pairs in key order. Unlike
|
||||
@@ -407,3 +410,35 @@ func (s *InternalIteratorStats) Merge(from InternalIteratorStats) {
|
||||
s.SeparatedPointValue.ValueBytes += from.SeparatedPointValue.ValueBytes
|
||||
s.SeparatedPointValue.ValueBytesFetched += from.SeparatedPointValue.ValueBytesFetched
|
||||
}
|
||||
|
||||
func (s *InternalIteratorStats) String() string {
|
||||
return redact.StringWithoutMarkers(s)
|
||||
}
|
||||
|
||||
// SafeFormat implements the redact.SafeFormatter interface.
|
||||
func (s *InternalIteratorStats) SafeFormat(p redact.SafePrinter, verb rune) {
|
||||
p.Printf("blocks: %s cached",
|
||||
humanize.Bytes.Uint64(s.BlockBytesInCache),
|
||||
)
|
||||
if s.BlockBytes != s.BlockBytesInCache || s.BlockReadDuration != 0 {
|
||||
p.Printf(", %s not cached (read time: %s)",
|
||||
humanize.Bytes.Uint64(s.BlockBytes-s.BlockBytesInCache),
|
||||
humanize.FormattedString(s.BlockReadDuration.String()),
|
||||
)
|
||||
}
|
||||
p.Printf("; points: %s", humanize.Count.Uint64(s.PointCount))
|
||||
|
||||
if s.PointsCoveredByRangeTombstones != 0 {
|
||||
p.Printf("(%s tombstoned)", humanize.Count.Uint64(s.PointsCoveredByRangeTombstones))
|
||||
}
|
||||
p.Printf(" (%s keys, %s values)",
|
||||
humanize.Bytes.Uint64(s.KeyBytes),
|
||||
humanize.Bytes.Uint64(s.ValueBytes),
|
||||
)
|
||||
if s.SeparatedPointValue.Count != 0 {
|
||||
p.Printf("; separated: %s (%s, %s fetched)",
|
||||
humanize.Count.Uint64(s.SeparatedPointValue.Count),
|
||||
humanize.Bytes.Uint64(s.SeparatedPointValue.ValueBytes),
|
||||
humanize.Bytes.Uint64(s.SeparatedPointValue.ValueBytesFetched))
|
||||
}
|
||||
}
|
||||
|
||||
9
vendor/github.com/cockroachdb/pebble/internal/base/metrics.go
generated
vendored
9
vendor/github.com/cockroachdb/pebble/internal/base/metrics.go
generated
vendored
@@ -53,6 +53,15 @@ func (tm *ThroughputMetric) Rate() int64 {
|
||||
float64(time.Second))
|
||||
}
|
||||
|
||||
// Utilization returns a percent [0, 1.0] indicating the percent of time
|
||||
// work was performed.
|
||||
func (tm *ThroughputMetric) Utilization() float64 {
|
||||
if tm.WorkDuration == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(tm.WorkDuration) / float64(tm.WorkDuration+tm.IdleDuration)
|
||||
}
|
||||
|
||||
// GaugeSampleMetric is used to measure a gauge value (e.g. queue length) by
|
||||
// accumulating samples of that gauge.
|
||||
type GaugeSampleMetric struct {
|
||||
|
||||
54
vendor/github.com/cockroachdb/pebble/internal/base/seqnums.go
generated
vendored
54
vendor/github.com/cockroachdb/pebble/internal/base/seqnums.go
generated
vendored
@@ -1,54 +0,0 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package base
|
||||
|
||||
// This file defines sequence numbers that are reserved for foreign keys i.e.
|
||||
// internal keys coming from other Pebble instances and existing in shared
|
||||
// storage, as those will "slot below" any internal keys added by our own Pebble
|
||||
// instance. Any keys created by this Pebble instance need to be greater than
|
||||
// all reserved sequence numbers (i.e. >= SeqNumStart).
|
||||
const (
|
||||
// SeqNumZero is the zero sequence number, set by compactions if they can
|
||||
// guarantee there are no keys underneath an internal key.
|
||||
SeqNumZero = uint64(0)
|
||||
|
||||
// SeqNumL6Point is the sequence number reserved for foreign point keys in L6.
|
||||
// This sequence number must be lower than SeqNumL6RangeKey for range key
|
||||
// masking to work correctly.
|
||||
SeqNumL6Point = uint64(1)
|
||||
|
||||
// SeqNumL6RangeKey is the sequence number reserved for foreign range keys in
|
||||
// L6. Only RangeKeySets are expected at this level.
|
||||
SeqNumL6RangeKey = uint64(2)
|
||||
|
||||
// SeqNumL5RangeDel is the sequence number reserved for foreign range deletes
|
||||
// in L5. These keys could delete L6 points.
|
||||
SeqNumL5RangeDel = uint64(3)
|
||||
|
||||
// SeqNumL5Point is the sequence number reserved for foreign point keys in L5.
|
||||
// Any sst-local range deletions would have already been applied to these keys,
|
||||
// so they can safely get a sequence number higher than SeqNumL5RangeDel.
|
||||
// However they must have a sequence number lower than SeqNumL5RangeKey* for
|
||||
// range key masking to work correctly.
|
||||
SeqNumL5Point = uint64(4)
|
||||
|
||||
// SeqNumL5RangeKeyUnsetDel is the sequence number reserved for foreign
|
||||
// range key unsets/deletes in L5. These operations could apply to L6
|
||||
// RangeKeySets, so this sequence number must be > SeqNumL6RangeKey.
|
||||
SeqNumL5RangeKeyUnsetDel = uint64(5)
|
||||
|
||||
// SeqNumL5RangeKeySet is the sequence number reserved for foreign range key
|
||||
// Sets in L5. These operations could apply to L6 RangeKeySets, so this
|
||||
// sequence number must be > SeqNumL6RangeKey. Any SST-local rangekey
|
||||
// unsets/dels have already been applied to them, so their sequence number must
|
||||
// be > SeqNumL5RangeKeyUnsetDel.
|
||||
SeqNumL5RangeKeySet = uint64(6)
|
||||
|
||||
// Sequence numbers 7-9 are reserved for future use.
|
||||
|
||||
// SeqNumStart is the first sequence number assigned to a key written by
|
||||
// ourselves.
|
||||
SeqNumStart = uint64(10)
|
||||
)
|
||||
15
vendor/github.com/cockroachdb/pebble/internal/batchskl/skl.go
generated
vendored
15
vendor/github.com/cockroachdb/pebble/internal/batchskl/skl.go
generated
vendored
@@ -63,14 +63,15 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/constants"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
maxHeight = 20
|
||||
maxNodeSize = int(unsafe.Sizeof(node{}))
|
||||
linksSize = int(unsafe.Sizeof(links{}))
|
||||
maxNodesSize = math.MaxUint32
|
||||
maxNodeSize = uint64(unsafe.Sizeof(node{}))
|
||||
linksSize = uint64(unsafe.Sizeof(links{}))
|
||||
maxNodesSize = constants.MaxUint32OrInt
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -281,7 +282,7 @@ func (s *Skiplist) newNode(
|
||||
panic("height cannot be less than one or greater than the max height")
|
||||
}
|
||||
|
||||
unusedSize := (maxHeight - int(height)) * linksSize
|
||||
unusedSize := uint64(maxHeight-int(height)) * linksSize
|
||||
nodeOffset, err := s.alloc(uint32(maxNodeSize - unusedSize))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -296,13 +297,13 @@ func (s *Skiplist) newNode(
|
||||
}
|
||||
|
||||
func (s *Skiplist) alloc(size uint32) (uint32, error) {
|
||||
offset := len(s.nodes)
|
||||
offset := uint64(len(s.nodes))
|
||||
|
||||
// We only have a need for memory up to offset + size, but we never want
|
||||
// to allocate a node whose tail points into unallocated memory.
|
||||
minAllocSize := offset + maxNodeSize
|
||||
if cap(s.nodes) < minAllocSize {
|
||||
allocSize := cap(s.nodes) * 2
|
||||
if uint64(cap(s.nodes)) < minAllocSize {
|
||||
allocSize := uint64(cap(s.nodes)) * 2
|
||||
if allocSize < minAllocSize {
|
||||
allocSize = minAllocSize
|
||||
}
|
||||
|
||||
183
vendor/github.com/cockroachdb/pebble/internal/cache/clockpro.go
generated
vendored
183
vendor/github.com/cockroachdb/pebble/internal/cache/clockpro.go
generated
vendored
@@ -71,14 +71,12 @@ func (h Handle) Get() []byte {
|
||||
|
||||
// Release releases the reference to the cache entry.
|
||||
func (h Handle) Release() {
|
||||
if h.value != nil {
|
||||
h.value.release()
|
||||
}
|
||||
h.value.release()
|
||||
}
|
||||
|
||||
type shard struct {
|
||||
hits int64
|
||||
misses int64
|
||||
hits atomic.Int64
|
||||
misses atomic.Int64
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
@@ -119,15 +117,15 @@ func (c *shard) Get(id uint64, fileNum base.DiskFileNum, offset uint64) Handle {
|
||||
if e := c.blocks.Get(key{fileKey{id, fileNum}, offset}); e != nil {
|
||||
value = e.acquireValue()
|
||||
if value != nil {
|
||||
atomic.StoreInt32(&e.referenced, 1)
|
||||
e.referenced.Store(true)
|
||||
}
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
if value == nil {
|
||||
atomic.AddInt64(&c.misses, 1)
|
||||
c.misses.Add(1)
|
||||
return Handle{}
|
||||
}
|
||||
atomic.AddInt64(&c.hits, 1)
|
||||
c.hits.Add(1)
|
||||
return Handle{value: value}
|
||||
}
|
||||
|
||||
@@ -160,7 +158,7 @@ func (c *shard) Set(id uint64, fileNum base.DiskFileNum, offset uint64, value *V
|
||||
case e.peekValue() != nil:
|
||||
// cache entry was a hot or cold page
|
||||
e.setValue(value)
|
||||
atomic.StoreInt32(&e.referenced, 1)
|
||||
e.referenced.Store(true)
|
||||
delta := int64(len(value.buf)) - e.size
|
||||
e.size = int64(len(value.buf))
|
||||
if e.ptype == etHot {
|
||||
@@ -176,7 +174,7 @@ func (c *shard) Set(id uint64, fileNum base.DiskFileNum, offset uint64, value *V
|
||||
// cache entry was a test page
|
||||
c.sizeTest -= e.size
|
||||
c.countTest--
|
||||
c.metaDel(e)
|
||||
c.metaDel(e).release()
|
||||
c.metaCheck(e)
|
||||
|
||||
e.size = int64(len(value.buf))
|
||||
@@ -185,7 +183,7 @@ func (c *shard) Set(id uint64, fileNum base.DiskFileNum, offset uint64, value *V
|
||||
c.coldTarget = c.targetSize()
|
||||
}
|
||||
|
||||
atomic.StoreInt32(&e.referenced, 0)
|
||||
e.referenced.Store(false)
|
||||
e.setValue(value)
|
||||
e.ptype = etHot
|
||||
if c.metaAdd(k, e) {
|
||||
@@ -233,37 +231,75 @@ func (c *shard) Delete(id uint64, fileNum base.DiskFileNum, offset uint64) {
|
||||
return
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
var deletedValue *Value
|
||||
func() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
e := c.blocks.Get(k)
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
c.metaEvict(e)
|
||||
|
||||
c.checkConsistency()
|
||||
e := c.blocks.Get(k)
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
deletedValue = c.metaEvict(e)
|
||||
c.checkConsistency()
|
||||
}()
|
||||
// Now that the mutex has been dropped, release the reference which will
|
||||
// potentially free the memory associated with the previous cached value.
|
||||
deletedValue.release()
|
||||
}
|
||||
|
||||
// EvictFile evicts all of the cache values for the specified file.
|
||||
func (c *shard) EvictFile(id uint64, fileNum base.DiskFileNum) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
fkey := key{fileKey{id, fileNum}, 0}
|
||||
for c.evictFileRun(fkey) {
|
||||
// Sched switch to give another goroutine an opportunity to acquire the
|
||||
// shard mutex.
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *shard) evictFileRun(fkey key) (moreRemaining bool) {
|
||||
// If most of the file's blocks are held in the block cache, evicting all
|
||||
// the blocks may take a while. We don't want to block the entire cache
|
||||
// shard, forcing concurrent readers to wait until we're finished. We drop
|
||||
// the mutex every [blocksPerMutexAcquisition] blocks to give other
|
||||
// goroutines an opportunity to make progress.
|
||||
const blocksPerMutexAcquisition = 5
|
||||
c.mu.Lock()
|
||||
|
||||
// Releasing a value may result in free-ing it back to the memory allocator.
|
||||
// This can have a nontrivial cost that we'd prefer to not pay while holding
|
||||
// the shard mutex, so we collect the evicted values in a local slice and
|
||||
// only release them in a defer after dropping the cache mutex.
|
||||
var obsoleteValuesAlloc [blocksPerMutexAcquisition]*Value
|
||||
obsoleteValues := obsoleteValuesAlloc[:0]
|
||||
defer func() {
|
||||
c.mu.Unlock()
|
||||
for _, v := range obsoleteValues {
|
||||
v.release()
|
||||
}
|
||||
}()
|
||||
|
||||
blocks := c.files.Get(fkey)
|
||||
if blocks == nil {
|
||||
return
|
||||
}
|
||||
for b, n := blocks, (*entry)(nil); ; b = n {
|
||||
n = b.fileLink.next
|
||||
c.metaEvict(b)
|
||||
if b == n {
|
||||
break
|
||||
}
|
||||
// No blocks for this file.
|
||||
return false
|
||||
}
|
||||
|
||||
c.checkConsistency()
|
||||
// b is the current head of the doubly linked list, and n is the entry after b.
|
||||
for b, n := blocks, (*entry)(nil); len(obsoleteValues) < cap(obsoleteValues); b = n {
|
||||
n = b.fileLink.next
|
||||
obsoleteValues = append(obsoleteValues, c.metaEvict(b))
|
||||
if b == n {
|
||||
// b == n represents the case where b was the last entry remaining
|
||||
// in the doubly linked list, which is why it pointed at itself. So
|
||||
// no more entries left.
|
||||
c.checkConsistency()
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Exhausted blocksPerMutexAcquisition.
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *shard) Free() {
|
||||
@@ -274,7 +310,7 @@ func (c *shard) Free() {
|
||||
// metaCheck call when the "invariants" build tag is specified.
|
||||
for c.handHot != nil {
|
||||
e := c.handHot
|
||||
c.metaDel(c.handHot)
|
||||
c.metaDel(c.handHot).release()
|
||||
e.free()
|
||||
}
|
||||
|
||||
@@ -359,12 +395,14 @@ func (c *shard) metaAdd(key key, e *entry) bool {
|
||||
|
||||
// Remove the entry from the cache. This removes the entry from the blocks map,
|
||||
// the files map, and ensures that hand{Hot,Cold,Test} are not pointing at the
|
||||
// entry.
|
||||
func (c *shard) metaDel(e *entry) {
|
||||
// entry. Returns the deleted value that must be released, if any.
|
||||
func (c *shard) metaDel(e *entry) (deletedValue *Value) {
|
||||
if value := e.peekValue(); value != nil {
|
||||
value.ref.trace("metaDel")
|
||||
}
|
||||
e.setValue(nil)
|
||||
// Remove the pointer to the value.
|
||||
deletedValue = e.val
|
||||
e.val = nil
|
||||
|
||||
c.blocks.Delete(e.key)
|
||||
if entriesGoAllocated {
|
||||
@@ -396,6 +434,7 @@ func (c *shard) metaDel(e *entry) {
|
||||
} else {
|
||||
c.files.Put(fkey, next)
|
||||
}
|
||||
return deletedValue
|
||||
}
|
||||
|
||||
// Check that the specified entry is not referenced by the cache.
|
||||
@@ -455,7 +494,7 @@ func (c *shard) metaCheck(e *entry) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *shard) metaEvict(e *entry) {
|
||||
func (c *shard) metaEvict(e *entry) (evictedValue *Value) {
|
||||
switch e.ptype {
|
||||
case etHot:
|
||||
c.sizeHot -= e.size
|
||||
@@ -467,9 +506,10 @@ func (c *shard) metaEvict(e *entry) {
|
||||
c.sizeTest -= e.size
|
||||
c.countTest--
|
||||
}
|
||||
c.metaDel(e)
|
||||
evictedValue = c.metaDel(e)
|
||||
c.metaCheck(e)
|
||||
e.free()
|
||||
return evictedValue
|
||||
}
|
||||
|
||||
func (c *shard) evict() {
|
||||
@@ -491,8 +531,8 @@ func (c *shard) runHandCold(countColdDebug, sizeColdDebug int64) {
|
||||
|
||||
e := c.handCold
|
||||
if e.ptype == etCold {
|
||||
if atomic.LoadInt32(&e.referenced) == 1 {
|
||||
atomic.StoreInt32(&e.referenced, 0)
|
||||
if e.referenced.Load() {
|
||||
e.referenced.Store(false)
|
||||
e.ptype = etHot
|
||||
c.sizeCold -= e.size
|
||||
c.countCold--
|
||||
@@ -528,8 +568,8 @@ func (c *shard) runHandHot() {
|
||||
|
||||
e := c.handHot
|
||||
if e.ptype == etHot {
|
||||
if atomic.LoadInt32(&e.referenced) == 1 {
|
||||
atomic.StoreInt32(&e.referenced, 0)
|
||||
if e.referenced.Load() {
|
||||
e.referenced.Store(false)
|
||||
} else {
|
||||
e.ptype = etCold
|
||||
c.sizeHot -= e.size
|
||||
@@ -564,7 +604,7 @@ func (c *shard) runHandTest() {
|
||||
if c.coldTarget < 0 {
|
||||
c.coldTarget = 0
|
||||
}
|
||||
c.metaDel(e)
|
||||
c.metaDel(e).release()
|
||||
c.metaCheck(e)
|
||||
e.free()
|
||||
}
|
||||
@@ -587,7 +627,7 @@ type Metrics struct {
|
||||
// Cache implements Pebble's sharded block cache. The Clock-PRO algorithm is
|
||||
// used for page replacement
|
||||
// (http://static.usenix.org/event/usenix05/tech/general/full_papers/jiang/jiang_html/html.html). In
|
||||
// order to provide better concurrency, 2 x NumCPUs shards are created, with
|
||||
// order to provide better concurrency, 4 x NumCPUs shards are created, with
|
||||
// each shard being given 1/n of the target cache size. The Clock-PRO algorithm
|
||||
// is run independently on each shard.
|
||||
//
|
||||
@@ -621,9 +661,9 @@ type Metrics struct {
|
||||
// used in combination by specifying `-tags invariants,tracing`. Note that
|
||||
// "tracing" produces a significant slowdown, while "invariants" does not.
|
||||
type Cache struct {
|
||||
refs int64
|
||||
refs atomic.Int64
|
||||
maxSize int64
|
||||
idAlloc uint64
|
||||
idAlloc atomic.Uint64
|
||||
shards []shard
|
||||
|
||||
// Traces recorded by Cache.trace. Used for debugging.
|
||||
@@ -643,17 +683,44 @@ type Cache struct {
|
||||
// defer c.Unref()
|
||||
// d, err := pebble.Open(pebble.Options{Cache: c})
|
||||
func New(size int64) *Cache {
|
||||
return newShards(size, 2*runtime.GOMAXPROCS(0))
|
||||
// How many cache shards should we create?
|
||||
//
|
||||
// Note that the probability two processors will try to access the same
|
||||
// shard at the same time increases superlinearly with the number of
|
||||
// processors (Eg, consider the brithday problem where each CPU is a person,
|
||||
// and each shard is a possible birthday).
|
||||
//
|
||||
// We could consider growing the number of shards superlinearly, but
|
||||
// increasing the shard count may reduce the effectiveness of the caching
|
||||
// algorithm if frequently-accessed blocks are insufficiently distributed
|
||||
// across shards. If a shard's size is smaller than a single frequently
|
||||
// scanned sstable, then the shard will be unable to hold the entire
|
||||
// frequently-scanned table in memory despite other shards still holding
|
||||
// infrequently accessed blocks.
|
||||
//
|
||||
// Experimentally, we've observed contention contributing to tail latencies
|
||||
// at 2 shards per processor. For now we use 4 shards per processor,
|
||||
// recognizing this may not be final word.
|
||||
m := 4 * runtime.GOMAXPROCS(0)
|
||||
|
||||
// In tests we can use large CPU machines with small cache sizes and have
|
||||
// many caches in existence at a time. If sharding into m shards would
|
||||
// produce too small shards, constrain the number of shards to 4.
|
||||
const minimumShardSize = 4 << 20 // 4 MiB
|
||||
if m > 4 && int(size)/m < minimumShardSize {
|
||||
m = 4
|
||||
}
|
||||
return newShards(size, m)
|
||||
}
|
||||
|
||||
func newShards(size int64, shards int) *Cache {
|
||||
c := &Cache{
|
||||
refs: 1,
|
||||
maxSize: size,
|
||||
idAlloc: 1,
|
||||
shards: make([]shard, shards),
|
||||
}
|
||||
c.trace("alloc", c.refs)
|
||||
c.refs.Store(1)
|
||||
c.idAlloc.Store(1)
|
||||
c.trace("alloc", c.refs.Load())
|
||||
for i := range c.shards {
|
||||
c.shards[i] = shard{
|
||||
maxSize: size / int64(len(c.shards)),
|
||||
@@ -669,7 +736,7 @@ func newShards(size int64, shards int) *Cache {
|
||||
// Note: this is a no-op if invariants are disabled or race is enabled.
|
||||
invariants.SetFinalizer(c, func(obj interface{}) {
|
||||
c := obj.(*Cache)
|
||||
if v := atomic.LoadInt64(&c.refs); v != 0 {
|
||||
if v := c.refs.Load(); v != 0 {
|
||||
c.tr.Lock()
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"pebble: cache (%p) has non-zero reference count: %d\n", c, v)
|
||||
@@ -716,7 +783,7 @@ func (c *Cache) getShard(id uint64, fileNum base.DiskFileNum, offset uint64) *sh
|
||||
// Ref adds a reference to the cache. The cache only remains valid as long a
|
||||
// reference is maintained to it.
|
||||
func (c *Cache) Ref() {
|
||||
v := atomic.AddInt64(&c.refs, 1)
|
||||
v := c.refs.Add(1)
|
||||
if v <= 1 {
|
||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||
}
|
||||
@@ -725,7 +792,7 @@ func (c *Cache) Ref() {
|
||||
|
||||
// Unref releases a reference on the cache.
|
||||
func (c *Cache) Unref() {
|
||||
v := atomic.AddInt64(&c.refs, -1)
|
||||
v := c.refs.Add(-1)
|
||||
c.trace("unref", v)
|
||||
switch {
|
||||
case v < 0:
|
||||
@@ -785,14 +852,14 @@ func (c *Cache) Size() int64 {
|
||||
// manually managed. The caller MUST either add the value to the cache (via
|
||||
// Cache.Set), or release the value (via Cache.Free). Failure to do so will
|
||||
// result in a memory leak.
|
||||
func (c *Cache) Alloc(n int) *Value {
|
||||
func Alloc(n int) *Value {
|
||||
return newValue(n)
|
||||
}
|
||||
|
||||
// Free frees the specified value. The buffer associated with the value will
|
||||
// possibly be reused, making it invalid to use the buffer after calling
|
||||
// Free. Do not call Free on a value that has been added to the cache.
|
||||
func (c *Cache) Free(v *Value) {
|
||||
func Free(v *Value) {
|
||||
if n := v.refs(); n > 1 {
|
||||
panic(fmt.Sprintf("pebble: Value has been added to the cache: refs=%d", n))
|
||||
}
|
||||
@@ -829,8 +896,8 @@ func (c *Cache) Metrics() Metrics {
|
||||
m.Count += int64(s.blocks.Count())
|
||||
m.Size += s.sizeHot + s.sizeCold
|
||||
s.mu.RUnlock()
|
||||
m.Hits += atomic.LoadInt64(&s.hits)
|
||||
m.Misses += atomic.LoadInt64(&s.misses)
|
||||
m.Hits += s.hits.Load()
|
||||
m.Misses += s.misses.Load()
|
||||
}
|
||||
return m
|
||||
}
|
||||
@@ -838,5 +905,5 @@ func (c *Cache) Metrics() Metrics {
|
||||
// NewID returns a new ID to be used as a namespace for cached file
|
||||
// blocks.
|
||||
func (c *Cache) NewID() uint64 {
|
||||
return atomic.AddUint64(&c.idAlloc, 1)
|
||||
return c.idAlloc.Add(1)
|
||||
}
|
||||
|
||||
8
vendor/github.com/cockroachdb/pebble/internal/cache/entry.go
generated
vendored
8
vendor/github.com/cockroachdb/pebble/internal/cache/entry.go
generated
vendored
@@ -4,6 +4,8 @@
|
||||
|
||||
package cache
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
type entryType int8
|
||||
|
||||
const (
|
||||
@@ -56,7 +58,7 @@ type entry struct {
|
||||
ptype entryType
|
||||
// referenced is atomically set to indicate that this entry has been accessed
|
||||
// since the last time one of the clock hands swept it.
|
||||
referenced int32
|
||||
referenced atomic.Bool
|
||||
shard *shard
|
||||
// Reference count for the entry. The entry is freed when the reference count
|
||||
// drops to zero.
|
||||
@@ -137,9 +139,7 @@ func (e *entry) setValue(v *Value) {
|
||||
}
|
||||
old := e.val
|
||||
e.val = v
|
||||
if old != nil {
|
||||
old.release()
|
||||
}
|
||||
old.release()
|
||||
}
|
||||
|
||||
func (e *entry) peekValue() *Value {
|
||||
|
||||
18
vendor/github.com/cockroachdb/pebble/internal/cache/refcnt_normal.go
generated
vendored
18
vendor/github.com/cockroachdb/pebble/internal/cache/refcnt_normal.go
generated
vendored
@@ -10,33 +10,37 @@ package cache
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/cockroachdb/redact"
|
||||
)
|
||||
|
||||
// refcnt provides an atomic reference count. This version is used when the
|
||||
// "tracing" build tag is not enabled. See refcnt_tracing.go for the "tracing"
|
||||
// enabled version.
|
||||
type refcnt int32
|
||||
type refcnt struct {
|
||||
val atomic.Int32
|
||||
}
|
||||
|
||||
// initialize the reference count to the specified value.
|
||||
func (v *refcnt) init(val int32) {
|
||||
*v = refcnt(val)
|
||||
v.val.Store(val)
|
||||
}
|
||||
|
||||
func (v *refcnt) refs() int32 {
|
||||
return atomic.LoadInt32((*int32)(v))
|
||||
return v.val.Load()
|
||||
}
|
||||
|
||||
func (v *refcnt) acquire() {
|
||||
switch v := atomic.AddInt32((*int32)(v), 1); {
|
||||
switch v := v.val.Add(1); {
|
||||
case v <= 1:
|
||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||
panic(redact.Safe(fmt.Sprintf("pebble: inconsistent reference count: %d", v)))
|
||||
}
|
||||
}
|
||||
|
||||
func (v *refcnt) release() bool {
|
||||
switch v := atomic.AddInt32((*int32)(v), -1); {
|
||||
switch v := v.val.Add(-1); {
|
||||
case v < 0:
|
||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||
panic(redact.Safe(fmt.Sprintf("pebble: inconsistent reference count: %d", v)))
|
||||
case v == 0:
|
||||
return true
|
||||
default:
|
||||
|
||||
10
vendor/github.com/cockroachdb/pebble/internal/cache/refcnt_tracing.go
generated
vendored
10
vendor/github.com/cockroachdb/pebble/internal/cache/refcnt_tracing.go
generated
vendored
@@ -19,22 +19,22 @@ import (
|
||||
// debugging logic errors in manipulating the reference count. This version is
|
||||
// used when the "tracing" build tag is enabled.
|
||||
type refcnt struct {
|
||||
val int32
|
||||
val atomic.Int32
|
||||
sync.Mutex
|
||||
msgs []string
|
||||
}
|
||||
|
||||
func (v *refcnt) init(val int32) {
|
||||
v.val = val
|
||||
v.val.Store(val)
|
||||
v.trace("init")
|
||||
}
|
||||
|
||||
func (v *refcnt) refs() int32 {
|
||||
return atomic.LoadInt32(&v.val)
|
||||
return v.val.Load()
|
||||
}
|
||||
|
||||
func (v *refcnt) acquire() {
|
||||
switch n := atomic.AddInt32(&v.val, 1); {
|
||||
switch n := v.val.Add(1); {
|
||||
case n <= 1:
|
||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", n))
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func (v *refcnt) acquire() {
|
||||
}
|
||||
|
||||
func (v *refcnt) release() bool {
|
||||
n := atomic.AddInt32(&v.val, -1)
|
||||
n := v.val.Add(-1)
|
||||
switch {
|
||||
case n < 0:
|
||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", n))
|
||||
|
||||
2
vendor/github.com/cockroachdb/pebble/internal/cache/value.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/internal/cache/value.go
generated
vendored
@@ -40,7 +40,7 @@ func (v *Value) acquire() {
|
||||
}
|
||||
|
||||
func (v *Value) release() {
|
||||
if v.ref.release() {
|
||||
if v != nil && v.ref.release() {
|
||||
v.free()
|
||||
}
|
||||
}
|
||||
|
||||
17
vendor/github.com/cockroachdb/pebble/internal/constants/constants.go
generated
vendored
Normal file
17
vendor/github.com/cockroachdb/pebble/internal/constants/constants.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package constants
|
||||
|
||||
const (
|
||||
// oneIf64Bit is 1 on 64-bit platforms and 0 on 32-bit platforms.
|
||||
oneIf64Bit = ^uint(0) >> 63
|
||||
|
||||
// MaxUint32OrInt returns min(MaxUint32, MaxInt), i.e
|
||||
// - MaxUint32 on 64-bit platforms;
|
||||
// - MaxInt on 32-bit platforms.
|
||||
// It is used when slices are limited to Uint32 on 64-bit platforms (the
|
||||
// length limit for slices is naturally MaxInt on 32-bit platforms).
|
||||
MaxUint32OrInt = (1<<31)<<oneIf64Bit - 1
|
||||
)
|
||||
20
vendor/github.com/cockroachdb/pebble/internal/humanize/humanize.go
generated
vendored
20
vendor/github.com/cockroachdb/pebble/internal/humanize/humanize.go
generated
vendored
@@ -35,11 +35,11 @@ type config struct {
|
||||
suffix []string
|
||||
}
|
||||
|
||||
// IEC produces human readable representations of integer values in IEC units.
|
||||
var IEC = config{1024, []string{" B", " K", " M", " G", " T", " P", " E"}}
|
||||
// Bytes produces human readable representations of byte values in IEC units.
|
||||
var Bytes = config{1024, []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}}
|
||||
|
||||
// SI produces human readable representations of integer values in SI units.
|
||||
var SI = config{1000, []string{"", " K", " M", " G", " T", " P", " E"}}
|
||||
// Count produces human readable representations of unitless values in SI units.
|
||||
var Count = config{1000, []string{"", "K", "M", "G", "T", "P", "E"}}
|
||||
|
||||
// Int64 produces a human readable representation of the value.
|
||||
func (c *config) Int64(s int64) FormattedString {
|
||||
@@ -54,18 +54,6 @@ func (c *config) Uint64(s uint64) FormattedString {
|
||||
return FormattedString(humanate(s, c.base, c.suffix))
|
||||
}
|
||||
|
||||
// Int64 produces a human readable representation of the value in IEC units
|
||||
// (base 1024).
|
||||
func Int64(s int64) FormattedString {
|
||||
return IEC.Int64(s)
|
||||
}
|
||||
|
||||
// Uint64 produces a human readable representation of the value in IEC units
|
||||
// (base 1024).
|
||||
func Uint64(s uint64) FormattedString {
|
||||
return IEC.Uint64(s)
|
||||
}
|
||||
|
||||
// FormattedString represents a human readable representation of a value. It
|
||||
// implements the redact.SafeValue interface to signal that it represents a
|
||||
// a string that does not need to be redacted.
|
||||
|
||||
162
vendor/github.com/cockroachdb/pebble/internal/invalidating/iter.go
generated
vendored
Normal file
162
vendor/github.com/cockroachdb/pebble/internal/invalidating/iter.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package invalidating
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/fastrand"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
)
|
||||
|
||||
// MaybeWrapIfInvariants wraps some iterators with an invalidating iterator.
|
||||
// MaybeWrapIfInvariants does nothing in non-invariant builds.
|
||||
func MaybeWrapIfInvariants(iter base.InternalIterator) base.InternalIterator {
|
||||
if invariants.Enabled {
|
||||
if fastrand.Uint32n(10) == 1 {
|
||||
return NewIter(iter)
|
||||
}
|
||||
}
|
||||
return iter
|
||||
}
|
||||
|
||||
// iter tests unsafe key/value slice reuse by modifying the last
|
||||
// returned key/value to all 1s.
|
||||
type iter struct {
|
||||
iter base.InternalIterator
|
||||
lastKey *base.InternalKey
|
||||
lastValue base.LazyValue
|
||||
ignoreKinds [base.InternalKeyKindMax + 1]bool
|
||||
err error
|
||||
}
|
||||
|
||||
// Option configures the behavior of an invalidating iterator.
|
||||
type Option interface {
|
||||
apply(*iter)
|
||||
}
|
||||
|
||||
type funcOpt func(*iter)
|
||||
|
||||
func (f funcOpt) apply(i *iter) { f(i) }
|
||||
|
||||
// IgnoreKinds constructs an Option that configures an invalidating iterator to
|
||||
// skip trashing k/v pairs with the provided key kinds. Some iterators provided
|
||||
// key stability guarantees for specific key kinds.
|
||||
func IgnoreKinds(kinds ...base.InternalKeyKind) Option {
|
||||
return funcOpt(func(i *iter) {
|
||||
for _, kind := range kinds {
|
||||
i.ignoreKinds[kind] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// NewIter constructs a new invalidating iterator that wraps the provided
|
||||
// iterator, trashing buffers for previously returned keys.
|
||||
func NewIter(originalIterator base.InternalIterator, opts ...Option) base.InternalIterator {
|
||||
i := &iter{iter: originalIterator}
|
||||
for _, opt := range opts {
|
||||
opt.apply(i)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *iter) update(
|
||||
key *base.InternalKey, value base.LazyValue,
|
||||
) (*base.InternalKey, base.LazyValue) {
|
||||
i.trashLastKV()
|
||||
if key == nil {
|
||||
i.lastKey = nil
|
||||
i.lastValue = base.LazyValue{}
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
|
||||
i.lastKey = &base.InternalKey{}
|
||||
*i.lastKey = key.Clone()
|
||||
i.lastValue = base.LazyValue{
|
||||
ValueOrHandle: append(make([]byte, 0, len(value.ValueOrHandle)), value.ValueOrHandle...),
|
||||
}
|
||||
if value.Fetcher != nil {
|
||||
fetcher := new(base.LazyFetcher)
|
||||
*fetcher = *value.Fetcher
|
||||
i.lastValue.Fetcher = fetcher
|
||||
}
|
||||
return i.lastKey, i.lastValue
|
||||
}
|
||||
|
||||
func (i *iter) trashLastKV() {
|
||||
if i.lastKey == nil {
|
||||
return
|
||||
}
|
||||
if i.ignoreKinds[i.lastKey.Kind()] {
|
||||
return
|
||||
}
|
||||
|
||||
if i.lastKey != nil {
|
||||
for j := range i.lastKey.UserKey {
|
||||
i.lastKey.UserKey[j] = 0xff
|
||||
}
|
||||
i.lastKey.Trailer = 0xffffffffffffffff
|
||||
}
|
||||
for j := range i.lastValue.ValueOrHandle {
|
||||
i.lastValue.ValueOrHandle[j] = 0xff
|
||||
}
|
||||
if i.lastValue.Fetcher != nil {
|
||||
// Not all the LazyFetcher fields are visible, so we zero out the last
|
||||
// value's Fetcher struct entirely.
|
||||
*i.lastValue.Fetcher = base.LazyFetcher{}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *iter) SeekGE(key []byte, flags base.SeekGEFlags) (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.SeekGE(key, flags))
|
||||
}
|
||||
|
||||
func (i *iter) SeekPrefixGE(
|
||||
prefix, key []byte, flags base.SeekGEFlags,
|
||||
) (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.SeekPrefixGE(prefix, key, flags))
|
||||
}
|
||||
|
||||
func (i *iter) SeekLT(key []byte, flags base.SeekLTFlags) (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.SeekLT(key, flags))
|
||||
}
|
||||
|
||||
func (i *iter) First() (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.First())
|
||||
}
|
||||
|
||||
func (i *iter) Last() (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.Last())
|
||||
}
|
||||
|
||||
func (i *iter) Next() (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.Next())
|
||||
}
|
||||
|
||||
func (i *iter) Prev() (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.Prev())
|
||||
}
|
||||
|
||||
func (i *iter) NextPrefix(succKey []byte) (*base.InternalKey, base.LazyValue) {
|
||||
return i.update(i.iter.NextPrefix(succKey))
|
||||
}
|
||||
|
||||
func (i *iter) Error() error {
|
||||
if err := i.iter.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
return i.err
|
||||
}
|
||||
|
||||
func (i *iter) Close() error {
|
||||
return i.iter.Close()
|
||||
}
|
||||
|
||||
func (i *iter) SetBounds(lower, upper []byte) {
|
||||
i.iter.SetBounds(lower, upper)
|
||||
}
|
||||
|
||||
func (i *iter) String() string {
|
||||
return i.iter.String()
|
||||
}
|
||||
38
vendor/github.com/cockroachdb/pebble/internal/keyspan/defragment.go
generated
vendored
38
vendor/github.com/cockroachdb/pebble/internal/keyspan/defragment.go
generated
vendored
@@ -228,6 +228,14 @@ func (i *DefragmentingIter) SeekGE(key []byte) *Span {
|
||||
// fragments before this span. Defragment backward to find the start of the
|
||||
// defragmented span.
|
||||
i.defragmentBackward()
|
||||
|
||||
// Defragmenting backward may have stopped because it encountered an error.
|
||||
// If so, we must not continue so that i.iter.Error() (and thus i.Error())
|
||||
// yields the error.
|
||||
if i.iterSpan == nil && i.iter.Error() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.iterPos == iterPosPrev {
|
||||
// Next once back onto the span.
|
||||
i.iterSpan = i.iter.Next()
|
||||
@@ -259,6 +267,14 @@ func (i *DefragmentingIter) SeekLT(key []byte) *Span {
|
||||
// fragments after this span. Defragment forward to find the end of the
|
||||
// defragmented span.
|
||||
i.defragmentForward()
|
||||
|
||||
// Defragmenting forward may have stopped because it encountered an error.
|
||||
// If so, we must not continue so that i.iter.Error() (and thus i.Error())
|
||||
// yields the error.
|
||||
if i.iterSpan == nil && i.iter.Error() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.iterPos == iterPosNext {
|
||||
// Prev once back onto the span.
|
||||
i.iterSpan = i.iter.Prev()
|
||||
@@ -304,7 +320,7 @@ func (i *DefragmentingIter) Next() *Span {
|
||||
// Next once to move onto y, defragment forward to land on the first z
|
||||
// position.
|
||||
i.iterSpan = i.iter.Next()
|
||||
if invariants.Enabled && i.iterSpan == nil {
|
||||
if invariants.Enabled && i.iterSpan == nil && i.iter.Error() == nil {
|
||||
panic("pebble: invariant violation: no next span while switching directions")
|
||||
}
|
||||
// We're now positioned on the first span that was defragmented into the
|
||||
@@ -380,7 +396,7 @@ func (i *DefragmentingIter) Prev() *Span {
|
||||
// Prev once to move onto y, defragment backward to land on the last x
|
||||
// position.
|
||||
i.iterSpan = i.iter.Prev()
|
||||
if invariants.Enabled && i.iterSpan == nil {
|
||||
if invariants.Enabled && i.iterSpan == nil && i.iter.Error() == nil {
|
||||
panic("pebble: invariant violation: no previous span while switching directions")
|
||||
}
|
||||
// We're now positioned on the last span that was defragmented into the
|
||||
@@ -437,6 +453,15 @@ func (i *DefragmentingIter) defragmentForward() *Span {
|
||||
i.keysBuf = i.reduce(i.keysBuf, i.iterSpan.Keys)
|
||||
i.iterSpan = i.iter.Next()
|
||||
}
|
||||
// i.iterSpan == nil
|
||||
//
|
||||
// The inner iterator may return nil when it encounters an error. If there
|
||||
// was an error, we don't know whether there is another span we should
|
||||
// defragment or not. Return nil so that the caller knows they should check
|
||||
// Error().
|
||||
if i.iter.Error() != nil {
|
||||
return nil
|
||||
}
|
||||
i.curr.Keys = i.keysBuf
|
||||
return &i.curr
|
||||
}
|
||||
@@ -470,6 +495,15 @@ func (i *DefragmentingIter) defragmentBackward() *Span {
|
||||
i.keysBuf = i.reduce(i.keysBuf, i.iterSpan.Keys)
|
||||
i.iterSpan = i.iter.Prev()
|
||||
}
|
||||
// i.iterSpan == nil
|
||||
//
|
||||
// The inner iterator may return nil when it encounters an error. If there
|
||||
// was an error, we don't know whether there is another span we should
|
||||
// defragment or not. Return nil so that the caller knows they should check
|
||||
// Error().
|
||||
if i.iter.Error() != nil {
|
||||
return nil
|
||||
}
|
||||
i.curr.Keys = i.keysBuf
|
||||
return &i.curr
|
||||
}
|
||||
|
||||
25
vendor/github.com/cockroachdb/pebble/internal/keyspan/filter.go
generated
vendored
25
vendor/github.com/cockroachdb/pebble/internal/keyspan/filter.go
generated
vendored
@@ -4,6 +4,8 @@
|
||||
|
||||
package keyspan
|
||||
|
||||
import "github.com/cockroachdb/pebble/internal/base"
|
||||
|
||||
// FilterFunc defines a transform from the input Span into the output Span. The
|
||||
// function returns true if the Span should be returned by the iterator, and
|
||||
// false if the Span should be skipped. The FilterFunc is permitted to mutate
|
||||
@@ -21,6 +23,7 @@ type FilterFunc func(in *Span, out *Span) (keep bool)
|
||||
type filteringIter struct {
|
||||
iter FragmentIterator
|
||||
filterFn FilterFunc
|
||||
cmp base.Compare
|
||||
|
||||
// span is a mutable Span passed to the filterFn. The filterFn is free to
|
||||
// mutate this Span. The slice of Keys in the Span is reused with every call
|
||||
@@ -32,18 +35,32 @@ var _ FragmentIterator = (*filteringIter)(nil)
|
||||
|
||||
// Filter returns a new filteringIter that will filter the Spans from the
|
||||
// provided child iterator using the provided FilterFunc.
|
||||
func Filter(iter FragmentIterator, filter FilterFunc) FragmentIterator {
|
||||
return &filteringIter{iter: iter, filterFn: filter}
|
||||
func Filter(iter FragmentIterator, filter FilterFunc, cmp base.Compare) FragmentIterator {
|
||||
return &filteringIter{iter: iter, filterFn: filter, cmp: cmp}
|
||||
}
|
||||
|
||||
// SeekGE implements FragmentIterator.
|
||||
func (i *filteringIter) SeekGE(key []byte) *Span {
|
||||
return i.filter(i.iter.SeekGE(key), +1)
|
||||
span := i.filter(i.iter.SeekGE(key), +1)
|
||||
// i.filter could return a span that's less than key, _if_ the filterFunc
|
||||
// (which has no knowledge of the seek key) mutated the span to end at a key
|
||||
// less than or equal to `key`. Detect this case and next/invalidate the iter.
|
||||
if span != nil && i.cmp(span.End, key) <= 0 {
|
||||
return i.Next()
|
||||
}
|
||||
return span
|
||||
}
|
||||
|
||||
// SeekLT implements FragmentIterator.
|
||||
func (i *filteringIter) SeekLT(key []byte) *Span {
|
||||
return i.filter(i.iter.SeekLT(key), -1)
|
||||
span := i.filter(i.iter.SeekLT(key), -1)
|
||||
// i.filter could return a span that's >= key, _if_ the filterFunc (which has
|
||||
// no knowledge of the seek key) mutated the span to start at a key greater
|
||||
// than or equal to `key`. Detect this case and prev/invalidate the iter.
|
||||
if span != nil && i.cmp(span.Start, key) >= 0 {
|
||||
return i.Prev()
|
||||
}
|
||||
return span
|
||||
}
|
||||
|
||||
// First implements FragmentIterator.
|
||||
|
||||
925
vendor/github.com/cockroachdb/pebble/internal/keyspan/interleaving_iter.go
generated
vendored
925
vendor/github.com/cockroachdb/pebble/internal/keyspan/interleaving_iter.go
generated
vendored
File diff suppressed because it is too large
Load Diff
4
vendor/github.com/cockroachdb/pebble/internal/keyspan/iter.go
generated
vendored
4
vendor/github.com/cockroachdb/pebble/internal/keyspan/iter.go
generated
vendored
@@ -51,6 +51,8 @@ type FragmentIterator interface {
|
||||
Prev() *Span
|
||||
|
||||
// Error returns any accumulated error.
|
||||
//
|
||||
// TODO(jackson): Lift errors into return values on the positioning methods.
|
||||
Error() error
|
||||
|
||||
// Close closes the iterator and returns any accumulated error. Exhausting
|
||||
@@ -62,7 +64,7 @@ type FragmentIterator interface {
|
||||
|
||||
// TableNewSpanIter creates a new iterator for range key spans for the given
|
||||
// file.
|
||||
type TableNewSpanIter func(file *manifest.FileMetadata, iterOptions *SpanIterOptions) (FragmentIterator, error)
|
||||
type TableNewSpanIter func(file *manifest.FileMetadata, iterOptions SpanIterOptions) (FragmentIterator, error)
|
||||
|
||||
// SpanIterOptions is a subset of IterOptions that are necessary to instantiate
|
||||
// per-sstable span iterators.
|
||||
|
||||
4
vendor/github.com/cockroachdb/pebble/internal/keyspan/level_iter.go
generated
vendored
4
vendor/github.com/cockroachdb/pebble/internal/keyspan/level_iter.go
generated
vendored
@@ -98,7 +98,7 @@ func (l *LevelIter) Init(
|
||||
) {
|
||||
l.err = nil
|
||||
l.level = level
|
||||
l.tableOpts.RangeKeyFilters = opts.RangeKeyFilters
|
||||
l.tableOpts = opts
|
||||
l.cmp = cmp
|
||||
l.iterFile = nil
|
||||
l.newIter = newIter
|
||||
@@ -179,7 +179,7 @@ func (l *LevelIter) loadFile(file *manifest.FileMetadata, dir int) loadFileRetur
|
||||
return noFileLoaded
|
||||
}
|
||||
if indicator != fileAlreadyLoaded {
|
||||
l.iter, l.err = l.newIter(file, &l.tableOpts)
|
||||
l.iter, l.err = l.newIter(file, l.tableOpts)
|
||||
indicator = newFileLoaded
|
||||
}
|
||||
if l.err != nil {
|
||||
|
||||
43
vendor/github.com/cockroachdb/pebble/internal/keyspan/merging_iter.go
generated
vendored
43
vendor/github.com/cockroachdb/pebble/internal/keyspan/merging_iter.go
generated
vendored
@@ -25,49 +25,6 @@ import (
|
||||
// MergingIter implementation, but will require a bit of plumbing to thread the
|
||||
// Equal function.
|
||||
|
||||
// Transformer defines a transformation to be applied to a Span.
|
||||
type Transformer interface {
|
||||
// Transform takes a Span as input and writes the transformed Span to the
|
||||
// provided output *Span pointer. The output Span's Keys slice may be reused
|
||||
// by Transform to reduce allocations.
|
||||
Transform(cmp base.Compare, in Span, out *Span) error
|
||||
}
|
||||
|
||||
// The TransformerFunc type is an adapter to allow the use of ordinary functions
|
||||
// as Transformers. If f is a function with the appropriate signature,
|
||||
// TransformerFunc(f) is a Transformer that calls f.
|
||||
type TransformerFunc func(base.Compare, Span, *Span) error
|
||||
|
||||
// Transform calls f(cmp, in, out).
|
||||
func (tf TransformerFunc) Transform(cmp base.Compare, in Span, out *Span) error {
|
||||
return tf(cmp, in, out)
|
||||
}
|
||||
|
||||
var noopTransform Transformer = TransformerFunc(func(_ base.Compare, s Span, dst *Span) error {
|
||||
dst.Start, dst.End = s.Start, s.End
|
||||
dst.Keys = append(dst.Keys[:0], s.Keys...)
|
||||
return nil
|
||||
})
|
||||
|
||||
// VisibleTransform filters keys that are invisible at the provided snapshot
|
||||
// sequence number.
|
||||
func VisibleTransform(snapshot uint64) Transformer {
|
||||
return TransformerFunc(func(_ base.Compare, s Span, dst *Span) error {
|
||||
dst.Start, dst.End = s.Start, s.End
|
||||
dst.Keys = dst.Keys[:0]
|
||||
for _, k := range s.Keys {
|
||||
// NB: The InternalKeySeqNumMax value is used for the batch snapshot
|
||||
// because a batch's visible span keys are filtered when they're
|
||||
// fragmented. There's no requirement to enforce visibility at
|
||||
// iteration time.
|
||||
if base.Visible(k.SeqNum(), snapshot, base.InternalKeySeqNumMax) {
|
||||
dst.Keys = append(dst.Keys, k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// MergingIter merges spans across levels of the LSM, exposing an iterator over
|
||||
// spans that yields sets of spans fragmented at unique user key boundaries.
|
||||
//
|
||||
|
||||
1
vendor/github.com/cockroachdb/pebble/internal/keyspan/span.go
generated
vendored
1
vendor/github.com/cockroachdb/pebble/internal/keyspan/span.go
generated
vendored
@@ -368,6 +368,7 @@ func (s Span) String() string {
|
||||
|
||||
// Pretty returns a formatter for the span.
|
||||
func (s Span) Pretty(f base.FormatKey) fmt.Formatter {
|
||||
// TODO(jackson): Take a base.FormatValue to format Key.Value too.
|
||||
return prettySpan{s, f}
|
||||
}
|
||||
|
||||
|
||||
50
vendor/github.com/cockroachdb/pebble/internal/keyspan/transformer.go
generated
vendored
Normal file
50
vendor/github.com/cockroachdb/pebble/internal/keyspan/transformer.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package keyspan
|
||||
|
||||
import "github.com/cockroachdb/pebble/internal/base"
|
||||
|
||||
// Transformer defines a transformation to be applied to a Span.
|
||||
type Transformer interface {
|
||||
// Transform takes a Span as input and writes the transformed Span to the
|
||||
// provided output *Span pointer. The output Span's Keys slice may be reused
|
||||
// by Transform to reduce allocations.
|
||||
Transform(cmp base.Compare, in Span, out *Span) error
|
||||
}
|
||||
|
||||
// The TransformerFunc type is an adapter to allow the use of ordinary functions
|
||||
// as Transformers. If f is a function with the appropriate signature,
|
||||
// TransformerFunc(f) is a Transformer that calls f.
|
||||
type TransformerFunc func(base.Compare, Span, *Span) error
|
||||
|
||||
// Transform calls f(cmp, in, out).
|
||||
func (tf TransformerFunc) Transform(cmp base.Compare, in Span, out *Span) error {
|
||||
return tf(cmp, in, out)
|
||||
}
|
||||
|
||||
var noopTransform Transformer = TransformerFunc(func(_ base.Compare, s Span, dst *Span) error {
|
||||
dst.Start, dst.End = s.Start, s.End
|
||||
dst.Keys = append(dst.Keys[:0], s.Keys...)
|
||||
return nil
|
||||
})
|
||||
|
||||
// VisibleTransform filters keys that are invisible at the provided snapshot
|
||||
// sequence number.
|
||||
func VisibleTransform(snapshot uint64) Transformer {
|
||||
return TransformerFunc(func(_ base.Compare, s Span, dst *Span) error {
|
||||
dst.Start, dst.End = s.Start, s.End
|
||||
dst.Keys = dst.Keys[:0]
|
||||
for _, k := range s.Keys {
|
||||
// NB: The InternalKeySeqNumMax value is used for the batch snapshot
|
||||
// because a batch's visible span keys are filtered when they're
|
||||
// fragmented. There's no requirement to enforce visibility at
|
||||
// iteration time.
|
||||
if base.Visible(k.SeqNum(), snapshot, base.InternalKeySeqNumMax) {
|
||||
dst.Keys = append(dst.Keys, k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
9
vendor/github.com/cockroachdb/pebble/internal/keyspan/truncate.go
generated
vendored
9
vendor/github.com/cockroachdb/pebble/internal/keyspan/truncate.go
generated
vendored
@@ -14,7 +14,7 @@ func Truncate(
|
||||
iter FragmentIterator,
|
||||
lower, upper []byte,
|
||||
start, end *base.InternalKey,
|
||||
panicOnPartialOverlap bool,
|
||||
panicOnUpperTruncate bool,
|
||||
) FragmentIterator {
|
||||
return Filter(iter, func(in *Span, out *Span) (keep bool) {
|
||||
out.Start, out.End = in.Start, in.End
|
||||
@@ -57,7 +57,6 @@ func Truncate(
|
||||
var truncated bool
|
||||
// Truncate the bounds to lower and upper.
|
||||
if cmp(in.Start, lower) < 0 {
|
||||
truncated = true
|
||||
out.Start = lower
|
||||
}
|
||||
if cmp(in.End, upper) > 0 {
|
||||
@@ -65,10 +64,10 @@ func Truncate(
|
||||
out.End = upper
|
||||
}
|
||||
|
||||
if panicOnPartialOverlap && truncated {
|
||||
panic("pebble: bounds should not be truncated")
|
||||
if panicOnUpperTruncate && truncated {
|
||||
panic("pebble: upper bound should not be truncated")
|
||||
}
|
||||
|
||||
return !out.Empty() && cmp(out.Start, out.End) < 0
|
||||
})
|
||||
}, cmp)
|
||||
}
|
||||
|
||||
26
vendor/github.com/cockroachdb/pebble/internal/manifest/btree.go
generated
vendored
26
vendor/github.com/cockroachdb/pebble/internal/manifest/btree.go
generated
vendored
@@ -108,7 +108,7 @@ type annotation struct {
|
||||
}
|
||||
|
||||
type leafNode struct {
|
||||
ref int32
|
||||
ref atomic.Int32
|
||||
count int16
|
||||
leaf bool
|
||||
// subtreeCount holds the count of files in the entire subtree formed by
|
||||
@@ -139,13 +139,13 @@ func leafToNode(ln *leafNode) *node {
|
||||
func newLeafNode() *node {
|
||||
n := leafToNode(new(leafNode))
|
||||
n.leaf = true
|
||||
n.ref = 1
|
||||
n.ref.Store(1)
|
||||
return n
|
||||
}
|
||||
|
||||
func newNode() *node {
|
||||
n := new(node)
|
||||
n.ref = 1
|
||||
n.ref.Store(1)
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ func newNode() *node {
|
||||
// When a node is cloned, the provided pointer will be redirected to the new
|
||||
// mutable node.
|
||||
func mut(n **node) *node {
|
||||
if atomic.LoadInt32(&(*n).ref) == 1 {
|
||||
if (*n).ref.Load() == 1 {
|
||||
// Exclusive ownership. Can mutate in place.
|
||||
|
||||
// Whenever a node will be mutated, reset its annotations to be marked
|
||||
@@ -186,7 +186,7 @@ func mut(n **node) *node {
|
||||
|
||||
// incRef acquires a reference to the node.
|
||||
func (n *node) incRef() {
|
||||
atomic.AddInt32(&n.ref, 1)
|
||||
n.ref.Add(1)
|
||||
}
|
||||
|
||||
// decRef releases a reference to the node. If requested, the method will unref
|
||||
@@ -195,8 +195,8 @@ func (n *node) incRef() {
|
||||
// new nodes pass contentsToo=false to preserve existing reference counts during
|
||||
// operations that should yield a net-zero change to descendant refcounts.
|
||||
// When a node is released, its contained files are dereferenced.
|
||||
func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
||||
if atomic.AddInt32(&n.ref, -1) > 0 {
|
||||
func (n *node) decRef(contentsToo bool, obsolete *[]*FileBacking) {
|
||||
if n.ref.Add(-1) > 0 {
|
||||
// Other references remain. Can't free.
|
||||
return
|
||||
}
|
||||
@@ -215,7 +215,13 @@ func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
||||
if obsolete == nil {
|
||||
panic(fmt.Sprintf("file metadata %s dereferenced to zero during tree mutation", f.FileNum))
|
||||
}
|
||||
*obsolete = append(*obsolete, f)
|
||||
// Reference counting is performed on the FileBacking. In the case
|
||||
// of a virtual sstable, this reference counting is performed on
|
||||
// a FileBacking which is shared by every single virtual sstable
|
||||
// with the same backing sstable. If the reference count hits 0,
|
||||
// then we know that the FileBacking won't be required by any
|
||||
// sstable in Pebble, and that the backing sstable can be deleted.
|
||||
*obsolete = append(*obsolete, f.FileBacking)
|
||||
}
|
||||
}
|
||||
if !n.leaf {
|
||||
@@ -761,8 +767,8 @@ type btree struct {
|
||||
|
||||
// Release dereferences and clears the root node of the btree, removing all
|
||||
// items from the btree. In doing so, it decrements contained file counts.
|
||||
// It returns a slice of newly obsolete files, if any.
|
||||
func (t *btree) Release() (obsolete []*FileMetadata) {
|
||||
// It returns a slice of newly obsolete backing files, if any.
|
||||
func (t *btree) Release() (obsolete []*FileBacking) {
|
||||
if t.root != nil {
|
||||
t.root.decRef(true /* contentsToo */, &obsolete)
|
||||
t.root = nil
|
||||
|
||||
9
vendor/github.com/cockroachdb/pebble/internal/manifest/l0_sublevels.go
generated
vendored
9
vendor/github.com/cockroachdb/pebble/internal/manifest/l0_sublevels.go
generated
vendored
@@ -712,7 +712,7 @@ func (s *L0Sublevels) calculateFlushSplitKeys(flushSplitMaxBytes int64) {
|
||||
// InitCompactingFileInfo initializes internal flags relating to compacting
|
||||
// files. Must be called after sublevel initialization.
|
||||
//
|
||||
// Requires DB.mu to be held.
|
||||
// Requires DB.mu *and* the manifest lock to be held.
|
||||
func (s *L0Sublevels) InitCompactingFileInfo(inProgress []L0Compaction) {
|
||||
for i := range s.orderedIntervals {
|
||||
s.orderedIntervals[i].compactingFileCount = 0
|
||||
@@ -735,6 +735,13 @@ func (s *L0Sublevels) InitCompactingFileInfo(inProgress []L0Compaction) {
|
||||
if !f.IsCompacting() {
|
||||
continue
|
||||
}
|
||||
if invariants.Enabled {
|
||||
if s.cmp(s.orderedIntervals[f.minIntervalIndex].startKey.key, f.Smallest.UserKey) != 0 || s.cmp(s.orderedIntervals[f.maxIntervalIndex+1].startKey.key, f.Largest.UserKey) != 0 {
|
||||
panic(fmt.Sprintf("file %s has inconsistent L0 Sublevel interval bounds: %s-%s, %s-%s", f.FileNum,
|
||||
s.orderedIntervals[f.minIntervalIndex].startKey.key, s.orderedIntervals[f.maxIntervalIndex+1].startKey.key,
|
||||
f.Smallest.UserKey, f.Largest.UserKey))
|
||||
}
|
||||
}
|
||||
for i := f.minIntervalIndex; i <= f.maxIntervalIndex; i++ {
|
||||
interval := &s.orderedIntervals[i]
|
||||
interval.compactingFileCount++
|
||||
|
||||
77
vendor/github.com/cockroachdb/pebble/internal/manifest/level_metadata.go
generated
vendored
77
vendor/github.com/cockroachdb/pebble/internal/manifest/level_metadata.go
generated
vendored
@@ -15,20 +15,28 @@ import (
|
||||
// LevelMetadata contains metadata for all of the files within
|
||||
// a level of the LSM.
|
||||
type LevelMetadata struct {
|
||||
level int
|
||||
tree btree
|
||||
level int
|
||||
totalSize uint64
|
||||
// NumVirtual is the number of virtual sstables in the level.
|
||||
NumVirtual uint64
|
||||
// VirtualSize is the size of the virtual sstables in the level.
|
||||
VirtualSize uint64
|
||||
tree btree
|
||||
}
|
||||
|
||||
// clone makes a copy of the level metadata, implicitly increasing the ref
|
||||
// count of every file contained within lm.
|
||||
func (lm *LevelMetadata) clone() LevelMetadata {
|
||||
return LevelMetadata{
|
||||
level: lm.level,
|
||||
tree: lm.tree.Clone(),
|
||||
level: lm.level,
|
||||
totalSize: lm.totalSize,
|
||||
NumVirtual: lm.NumVirtual,
|
||||
VirtualSize: lm.VirtualSize,
|
||||
tree: lm.tree.Clone(),
|
||||
}
|
||||
}
|
||||
|
||||
func (lm *LevelMetadata) release() (obsolete []*FileMetadata) {
|
||||
func (lm *LevelMetadata) release() (obsolete []*FileBacking) {
|
||||
return lm.tree.Release()
|
||||
}
|
||||
|
||||
@@ -40,6 +48,13 @@ func makeLevelMetadata(cmp Compare, level int, files []*FileMetadata) LevelMetad
|
||||
var lm LevelMetadata
|
||||
lm.level = level
|
||||
lm.tree, _ = makeBTree(bcmp, files)
|
||||
for _, f := range files {
|
||||
lm.totalSize += f.Size
|
||||
if f.Virtual {
|
||||
lm.NumVirtual++
|
||||
lm.VirtualSize += f.Size
|
||||
}
|
||||
}
|
||||
return lm
|
||||
}
|
||||
|
||||
@@ -52,6 +67,27 @@ func makeBTree(cmp btreeCmp, files []*FileMetadata) (btree, LevelSlice) {
|
||||
return t, newLevelSlice(t.Iter())
|
||||
}
|
||||
|
||||
func (lm *LevelMetadata) insert(f *FileMetadata) error {
|
||||
if err := lm.tree.Insert(f); err != nil {
|
||||
return err
|
||||
}
|
||||
lm.totalSize += f.Size
|
||||
if f.Virtual {
|
||||
lm.NumVirtual++
|
||||
lm.VirtualSize += f.Size
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lm *LevelMetadata) remove(f *FileMetadata) bool {
|
||||
lm.totalSize -= f.Size
|
||||
if f.Virtual {
|
||||
lm.NumVirtual--
|
||||
lm.VirtualSize -= f.Size
|
||||
}
|
||||
return lm.tree.Delete(f)
|
||||
}
|
||||
|
||||
// Empty indicates whether there are any files in the level.
|
||||
func (lm *LevelMetadata) Empty() bool {
|
||||
return lm.tree.Count() == 0
|
||||
@@ -62,6 +98,11 @@ func (lm *LevelMetadata) Len() int {
|
||||
return lm.tree.Count()
|
||||
}
|
||||
|
||||
// Size returns the cumulative size of all the files within the level.
|
||||
func (lm *LevelMetadata) Size() uint64 {
|
||||
return lm.totalSize
|
||||
}
|
||||
|
||||
// Iter constructs a LevelIterator over the entire level.
|
||||
func (lm *LevelMetadata) Iter() LevelIterator {
|
||||
return LevelIterator{iter: lm.tree.Iter()}
|
||||
@@ -280,6 +321,32 @@ func (ls *LevelSlice) SizeSum() uint64 {
|
||||
return sum
|
||||
}
|
||||
|
||||
// NumVirtual returns the number of virtual sstables in the level. Its runtime is
|
||||
// linear in the length of the slice.
|
||||
func (ls *LevelSlice) NumVirtual() uint64 {
|
||||
var n uint64
|
||||
iter := ls.Iter()
|
||||
for f := iter.First(); f != nil; f = iter.Next() {
|
||||
if f.Virtual {
|
||||
n++
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// VirtualSizeSum returns the sum of the sizes of the virtual sstables in the
|
||||
// level.
|
||||
func (ls *LevelSlice) VirtualSizeSum() uint64 {
|
||||
var sum uint64
|
||||
iter := ls.Iter()
|
||||
for f := iter.First(); f != nil; f = iter.Next() {
|
||||
if f.Virtual {
|
||||
sum += f.Size
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
// Reslice constructs a new slice backed by the same underlying level, with
|
||||
// new start and end positions. Reslice invokes the provided function, passing
|
||||
// two LevelIterators: one positioned to i's inclusive start and one
|
||||
|
||||
180
vendor/github.com/cockroachdb/pebble/internal/manifest/version.go
generated
vendored
180
vendor/github.com/cockroachdb/pebble/internal/manifest/version.go
generated
vendored
@@ -49,6 +49,9 @@ type TableStats struct {
|
||||
// The number of point and range deletion entries in the table.
|
||||
NumDeletions uint64
|
||||
// NumRangeKeySets is the total number of range key sets in the table.
|
||||
//
|
||||
// NB: If there's a chance that the sstable contains any range key sets,
|
||||
// then NumRangeKeySets must be > 0.
|
||||
NumRangeKeySets uint64
|
||||
// Estimate of the total disk space that may be dropped by this table's
|
||||
// point deletions by compacting them.
|
||||
@@ -169,22 +172,13 @@ type FileMetadata struct {
|
||||
// FileNum is the file number.
|
||||
//
|
||||
// INVARIANT: when !FileMetadata.Virtual, FileNum == FileBacking.DiskFileNum.
|
||||
//
|
||||
// TODO(bananabrick): Consider creating separate types for
|
||||
// FileMetadata.FileNum and FileBacking.FileNum. FileNum is used both as
|
||||
// an indentifier for the FileMetadata in Pebble, and also as a handle to
|
||||
// perform reads and writes. We should ensure through types that
|
||||
// FileMetadata.FileNum isn't used to perform reads, and that
|
||||
// FileBacking.FileNum isn't used as an identifier for the FileMetadata.
|
||||
FileNum base.FileNum
|
||||
// Size is the size of the file, in bytes. Size is an approximate value for
|
||||
// virtual sstables.
|
||||
//
|
||||
// INVARIANT: when !FileMetadata.Virtual, Size == FileBacking.Size.
|
||||
//
|
||||
// TODO(bananabrick): Size is currently used in metrics, and for many key
|
||||
// Pebble level heuristics. Make sure that the heuristics will still work
|
||||
// appropriately with an approximate value of size.
|
||||
// INVARIANTS:
|
||||
// - When !FileMetadata.Virtual, Size == FileBacking.Size.
|
||||
// - Size should be non-zero. Size 0 virtual sstables must not be created.
|
||||
Size uint64
|
||||
// File creation time in seconds since the epoch (1970-01-01 00:00:00
|
||||
// UTC). For ingested sstables, this corresponds to the time the file was
|
||||
@@ -227,6 +221,8 @@ type FileMetadata struct {
|
||||
// we'd have to write virtual sstable stats to the version edit.
|
||||
Stats TableStats
|
||||
|
||||
// For L0 files only. Protected by DB.mu. Used to generate L0 sublevels and
|
||||
// pick L0 compactions. Only accurate for the most recent Version.
|
||||
SubLevel int
|
||||
L0Index int
|
||||
minIntervalIndex int
|
||||
@@ -235,9 +231,6 @@ type FileMetadata struct {
|
||||
// NB: the alignment of this struct is 8 bytes. We pack all the bools to
|
||||
// ensure an optimal packing.
|
||||
|
||||
// For L0 files only. Protected by DB.mu. Used to generate L0 sublevels and
|
||||
// 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, IsCompacting must also return true. If
|
||||
// Compacting is true and IsIntraL0Compacting is false for an L0 file, the
|
||||
@@ -289,6 +282,34 @@ type PhysicalFileMeta struct {
|
||||
// VirtualFileMeta is used by functions which want a guarantee that their input
|
||||
// belongs to a virtual sst and not a physical sst.
|
||||
//
|
||||
// A VirtualFileMeta inherits all the same fields as a FileMetadata. These
|
||||
// fields have additional invariants imposed on them, and/or slightly varying
|
||||
// meanings:
|
||||
// - Smallest and Largest (and their counterparts
|
||||
// {Smallest, Largest}{Point,Range}Key) remain tight bounds that represent a
|
||||
// key at that exact bound. We make the effort to determine the next smallest
|
||||
// or largest key in an sstable after virtualizing it, to maintain this
|
||||
// tightness. If the largest is a sentinel key (IsExclusiveSentinel()), it
|
||||
// could mean that a rangedel or range key ends at that user key, or has been
|
||||
// truncated to that user key.
|
||||
// - One invariant is that if a rangedel or range key is truncated on its
|
||||
// upper bound, the virtual sstable *must* have a rangedel or range key
|
||||
// sentinel key as its upper bound. This is because truncation yields
|
||||
// an exclusive upper bound for the rangedel/rangekey, and if there are
|
||||
// any points at that exclusive upper bound within the same virtual
|
||||
// sstable, those could get uncovered by this truncation. We enforce this
|
||||
// invariant in calls to keyspan.Truncate.
|
||||
// - Size is an estimate of the size of the virtualized portion of this sstable.
|
||||
// The underlying file's size is stored in FileBacking.Size, though it could
|
||||
// also be estimated or could correspond to just the referenced portion of
|
||||
// a file (eg. if the file originated on another node).
|
||||
// - Size must be > 0.
|
||||
// - SmallestSeqNum and LargestSeqNum are loose bounds for virtual sstables.
|
||||
// This means that all keys in the virtual sstable must have seqnums within
|
||||
// [SmallestSeqNum, LargestSeqNum], however there's no guarantee that there's
|
||||
// a key with a seqnum at either of the bounds. Calculating tight seqnum
|
||||
// bounds would be too expensive and deliver little value.
|
||||
//
|
||||
// NB: This type should only be constructed by calling FileMetadata.VirtualMeta.
|
||||
type VirtualFileMeta struct {
|
||||
*FileMetadata
|
||||
@@ -370,6 +391,17 @@ func (m *FileMetadata) InitPhysicalBacking() {
|
||||
}
|
||||
}
|
||||
|
||||
// InitProviderBacking creates a new FileBacking for a file backed by
|
||||
// an objstorage.Provider.
|
||||
func (m *FileMetadata) InitProviderBacking(fileNum base.DiskFileNum) {
|
||||
if !m.Virtual {
|
||||
panic("pebble: provider-backed sstables must be virtual")
|
||||
}
|
||||
if m.FileBacking == nil {
|
||||
m.FileBacking = &FileBacking{DiskFileNum: fileNum}
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateVirtual should be called once the FileMetadata for a virtual sstable
|
||||
// is created to verify that the fields of the virtual sstable are sound.
|
||||
func (m *FileMetadata) ValidateVirtual(createdFrom *FileMetadata) {
|
||||
@@ -575,6 +607,13 @@ func (m *FileMetadata) Overlaps(cmp Compare, start []byte, end []byte, exclusive
|
||||
return true
|
||||
}
|
||||
|
||||
// ContainedWithinSpan returns true if the file key range completely overlaps with the
|
||||
// given range ("end" is assumed to exclusive).
|
||||
func (m *FileMetadata) ContainedWithinSpan(cmp Compare, start, end []byte) bool {
|
||||
lowerCmp, upperCmp := cmp(m.Smallest.UserKey, start), cmp(m.Largest.UserKey, end)
|
||||
return lowerCmp >= 0 && (upperCmp < 0 || (upperCmp == 0 && m.Largest.IsExclusiveSentinel()))
|
||||
}
|
||||
|
||||
// ContainsKeyType returns whether or not the file contains keys of the provided
|
||||
// type.
|
||||
func (m *FileMetadata) ContainsKeyType(kt KeyType) bool {
|
||||
@@ -672,6 +711,7 @@ func (m *FileMetadata) DebugString(format base.FormatKey, verbose bool) string {
|
||||
if !verbose {
|
||||
return b.String()
|
||||
}
|
||||
fmt.Fprintf(&b, " seqnums:[%d-%d]", m.SmallestSeqNum, m.LargestSeqNum)
|
||||
if m.HasPointKeys {
|
||||
fmt.Fprintf(&b, " points:[%s-%s]",
|
||||
m.SmallestPointKey.Pretty(format), m.LargestPointKey.Pretty(format))
|
||||
@@ -687,7 +727,7 @@ func (m *FileMetadata) DebugString(format base.FormatKey, verbose bool) string {
|
||||
// representation.
|
||||
func ParseFileMetadataDebug(s string) (*FileMetadata, error) {
|
||||
// Split lines of the form:
|
||||
// 000000:[a#0,SET-z#0,SET] points:[...] ranges:[...]
|
||||
// 000000:[a#0,SET-z#0,SET] seqnums:[5-5] points:[...] ranges:[...]
|
||||
fields := strings.FieldsFunc(s, func(c rune) bool {
|
||||
switch c {
|
||||
case ':', '[', '-', ']':
|
||||
@@ -702,6 +742,19 @@ func ParseFileMetadataDebug(s string) (*FileMetadata, error) {
|
||||
m := &FileMetadata{}
|
||||
for len(fields) > 0 {
|
||||
prefix := fields[0]
|
||||
if prefix == "seqnums" {
|
||||
smallestSeqNum, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
return m, errors.Newf("malformed input: %s: %s", s, err)
|
||||
}
|
||||
largestSeqNum, err := strconv.ParseUint(fields[2], 10, 64)
|
||||
if err != nil {
|
||||
return m, errors.Newf("malformed input: %s: %s", s, err)
|
||||
}
|
||||
m.SmallestSeqNum, m.LargestSeqNum = smallestSeqNum, largestSeqNum
|
||||
fields = fields[3:]
|
||||
continue
|
||||
}
|
||||
smallest := base.ParsePrettyInternalKey(fields[1])
|
||||
largest := base.ParsePrettyInternalKey(fields[2])
|
||||
switch prefix {
|
||||
@@ -969,6 +1022,9 @@ func NewVersion(
|
||||
} else {
|
||||
v.Levels[l].tree.cmp = btreeCmpSmallestKey(cmp)
|
||||
}
|
||||
for _, f := range files[l] {
|
||||
v.Levels[l].totalSize += f.Size
|
||||
}
|
||||
}
|
||||
if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
|
||||
panic(err)
|
||||
@@ -1001,7 +1057,7 @@ func NewVersion(
|
||||
// key in a higher level table that has both the same user key and a higher
|
||||
// sequence number.
|
||||
type Version struct {
|
||||
refs int32
|
||||
refs atomic.Int32
|
||||
|
||||
// The level 0 sstables are organized in a series of sublevels. Similar to
|
||||
// the seqnum invariant in normal levels, there is no internal key in a
|
||||
@@ -1131,12 +1187,12 @@ func ParseVersionDebug(
|
||||
|
||||
// Refs returns the number of references to the version.
|
||||
func (v *Version) Refs() int32 {
|
||||
return atomic.LoadInt32(&v.refs)
|
||||
return v.refs.Load()
|
||||
}
|
||||
|
||||
// Ref increments the version refcount.
|
||||
func (v *Version) Ref() {
|
||||
atomic.AddInt32(&v.refs, 1)
|
||||
v.refs.Add(1)
|
||||
}
|
||||
|
||||
// Unref decrements the version refcount. If the last reference to the version
|
||||
@@ -1144,16 +1200,11 @@ func (v *Version) Ref() {
|
||||
// Deleted callback is invoked. Requires that the VersionList mutex is NOT
|
||||
// locked.
|
||||
func (v *Version) Unref() {
|
||||
if atomic.AddInt32(&v.refs, -1) == 0 {
|
||||
if v.refs.Add(-1) == 0 {
|
||||
l := v.list
|
||||
l.mu.Lock()
|
||||
l.Remove(v)
|
||||
obsolete := v.unrefFiles()
|
||||
fileBacking := make([]*FileBacking, len(obsolete))
|
||||
for i, f := range obsolete {
|
||||
fileBacking[i] = f.FileBacking
|
||||
}
|
||||
v.Deleted(fileBacking)
|
||||
v.Deleted(v.unrefFiles())
|
||||
l.mu.Unlock()
|
||||
}
|
||||
}
|
||||
@@ -1163,19 +1214,14 @@ func (v *Version) Unref() {
|
||||
// the Deleted callback is invoked. Requires that the VersionList mutex is
|
||||
// already locked.
|
||||
func (v *Version) UnrefLocked() {
|
||||
if atomic.AddInt32(&v.refs, -1) == 0 {
|
||||
if v.refs.Add(-1) == 0 {
|
||||
v.list.Remove(v)
|
||||
obsolete := v.unrefFiles()
|
||||
fileBacking := make([]*FileBacking, len(obsolete))
|
||||
for i, f := range obsolete {
|
||||
fileBacking[i] = f.FileBacking
|
||||
}
|
||||
v.Deleted(fileBacking)
|
||||
v.Deleted(v.unrefFiles())
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Version) unrefFiles() []*FileMetadata {
|
||||
var obsolete []*FileMetadata
|
||||
func (v *Version) unrefFiles() []*FileBacking {
|
||||
var obsolete []*FileBacking
|
||||
for _, lm := range v.Levels {
|
||||
obsolete = append(obsolete, lm.release()...)
|
||||
}
|
||||
@@ -1307,16 +1353,20 @@ func (v *Version) Overlaps(
|
||||
// CheckOrdering checks that the files are consistent with respect to
|
||||
// increasing file numbers (for level 0 files) and increasing and non-
|
||||
// overlapping internal key ranges (for level non-0 files).
|
||||
func (v *Version) CheckOrdering(cmp Compare, format base.FormatKey) error {
|
||||
func (v *Version) CheckOrdering(
|
||||
cmp Compare, format base.FormatKey, order OrderingInvariants,
|
||||
) error {
|
||||
for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- {
|
||||
sublevelIter := v.L0SublevelFiles[sublevel].Iter()
|
||||
if err := CheckOrdering(cmp, format, L0Sublevel(sublevel), sublevelIter); err != nil {
|
||||
// Sublevels have NEVER allowed split user keys, so we can pass
|
||||
// ProhibitSplitUserKeys.
|
||||
if err := CheckOrdering(cmp, format, L0Sublevel(sublevel), sublevelIter, ProhibitSplitUserKeys); err != nil {
|
||||
return base.CorruptionErrorf("%s\n%s", err, v.DebugString(format))
|
||||
}
|
||||
}
|
||||
|
||||
for level, lm := range v.Levels {
|
||||
if err := CheckOrdering(cmp, format, Level(level), lm.Iter()); err != nil {
|
||||
if err := CheckOrdering(cmp, format, Level(level), lm.Iter(), order); err != nil {
|
||||
return base.CorruptionErrorf("%s\n%s", err, v.DebugString(format))
|
||||
}
|
||||
}
|
||||
@@ -1385,10 +1435,34 @@ func (l *VersionList) Remove(v *Version) {
|
||||
v.list = nil // avoid memory leaks
|
||||
}
|
||||
|
||||
// OrderingInvariants dictates the file ordering invariants active.
|
||||
type OrderingInvariants int8
|
||||
|
||||
const (
|
||||
// ProhibitSplitUserKeys indicates that adjacent files within a level cannot
|
||||
// contain the same user key.
|
||||
ProhibitSplitUserKeys OrderingInvariants = iota
|
||||
// AllowSplitUserKeys indicates that adjacent files within a level may
|
||||
// contain the same user key. This is only allowed by historical format
|
||||
// major versions.
|
||||
//
|
||||
// TODO(jackson): Remove.
|
||||
AllowSplitUserKeys
|
||||
)
|
||||
|
||||
// CheckOrdering checks that the files are consistent with respect to
|
||||
// seqnums (for level 0 files -- see detailed comment below) and increasing and non-
|
||||
// overlapping internal key ranges (for non-level 0 files).
|
||||
func CheckOrdering(cmp Compare, format base.FormatKey, level Level, files LevelIterator) error {
|
||||
//
|
||||
// The ordering field may be passed AllowSplitUserKeys to allow adjacent files that are both
|
||||
// inclusive of the same user key. Pebble no longer creates version edits
|
||||
// installing such files, and Pebble databases with sufficiently high format
|
||||
// major version should no longer have any such files within their LSM.
|
||||
// TODO(jackson): Remove AllowSplitUserKeys when we remove support for the
|
||||
// earlier format major versions.
|
||||
func CheckOrdering(
|
||||
cmp Compare, format base.FormatKey, level Level, files LevelIterator, ordering OrderingInvariants,
|
||||
) error {
|
||||
// The invariants to check for L0 sublevels are the same as the ones to
|
||||
// check for all other levels. However, if L0 is not organized into
|
||||
// sublevels, or if all L0 files are being passed in, we do the legacy L0
|
||||
@@ -1466,11 +1540,29 @@ func CheckOrdering(cmp Compare, format base.FormatKey, level Level, files LevelI
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
}
|
||||
if base.InternalCompare(cmp, prev.Largest, f.Smallest) >= 0 {
|
||||
return base.CorruptionErrorf("%s files %s and %s have overlapping ranges: [%s-%s] vs [%s-%s]",
|
||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
|
||||
// What's considered "overlapping" is dependent on the format
|
||||
// major version. If ordering=ProhibitSplitUserKeys, then both
|
||||
// files cannot contain keys with the same user keys. If the
|
||||
// bounds have the same user key, the previous file's boundary
|
||||
// must have a Trailer indicating that it's exclusive.
|
||||
switch ordering {
|
||||
case AllowSplitUserKeys:
|
||||
if base.InternalCompare(cmp, prev.Largest, f.Smallest) >= 0 {
|
||||
return base.CorruptionErrorf("%s files %s and %s have overlapping ranges: [%s-%s] vs [%s-%s]",
|
||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
}
|
||||
case ProhibitSplitUserKeys:
|
||||
if v := cmp(prev.Largest.UserKey, f.Smallest.UserKey); v > 0 || (v == 0 && !prev.Largest.IsExclusiveSentinel()) {
|
||||
return base.CorruptionErrorf("%s files %s and %s have overlapping ranges: [%s-%s] vs [%s-%s]",
|
||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
156
vendor/github.com/cockroachdb/pebble/internal/manifest/version_edit.go
generated
vendored
156
vendor/github.com/cockroachdb/pebble/internal/manifest/version_edit.go
generated
vendored
@@ -51,7 +51,9 @@ const (
|
||||
tagMaxColumnFamily = 203
|
||||
|
||||
// Pebble tags.
|
||||
tagNewFile5 = 104 // Range keys.
|
||||
tagNewFile5 = 104 // Range keys.
|
||||
tagCreatedBackingTable = 105
|
||||
tagRemovedBackingTable = 106
|
||||
|
||||
// The custom tags sub-format used by tagNewFile4 and above.
|
||||
customTagTerminate = 1
|
||||
@@ -59,6 +61,7 @@ const (
|
||||
customTagCreationTime = 6
|
||||
customTagPathID = 65
|
||||
customTagNonSafeIgnoreMask = 1 << 6
|
||||
customTagVirtual = 66
|
||||
)
|
||||
|
||||
// DeletedFileEntry holds the state for a file deletion from a level. The file
|
||||
@@ -73,6 +76,9 @@ type DeletedFileEntry struct {
|
||||
type NewFileEntry struct {
|
||||
Level int
|
||||
Meta *FileMetadata
|
||||
// BackingFileNum is only set during manifest replay, and only for virtual
|
||||
// sstables.
|
||||
BackingFileNum base.DiskFileNum
|
||||
}
|
||||
|
||||
// VersionEdit holds the state for an edit to a Version along with other
|
||||
@@ -143,7 +149,9 @@ type VersionEdit struct {
|
||||
|
||||
// Decode decodes an edit from the specified reader.
|
||||
//
|
||||
// TODO(bananabrick): Support decoding of virtual sstable state.
|
||||
// Note that the Decode step will not set the FileBacking for virtual sstables
|
||||
// and the responsibility is left to the caller. However, the Decode step will
|
||||
// populate the NewFileEntry.BackingFileNum in VersionEdit.NewFiles.
|
||||
func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
br, ok := r.(byteReader)
|
||||
if !ok {
|
||||
@@ -196,6 +204,28 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
// NB: RocksDB does not use compaction pointers anymore.
|
||||
|
||||
case tagRemovedBackingTable:
|
||||
n, err := d.readUvarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.RemovedBackingTables = append(
|
||||
v.RemovedBackingTables, base.FileNum(n).DiskFileNum(),
|
||||
)
|
||||
case tagCreatedBackingTable:
|
||||
dfn, err := d.readUvarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size, err := d.readUvarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileBacking := &FileBacking{
|
||||
DiskFileNum: base.FileNum(dfn).DiskFileNum(),
|
||||
Size: size,
|
||||
}
|
||||
v.CreatedBackingTables = append(v.CreatedBackingTables, fileBacking)
|
||||
case tagDeletedFile:
|
||||
level, err := d.readLevel()
|
||||
if err != nil {
|
||||
@@ -301,6 +331,10 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
var markedForCompaction bool
|
||||
var creationTime uint64
|
||||
virtualState := struct {
|
||||
virtual bool
|
||||
backingFileNum uint64
|
||||
}{}
|
||||
if tag == tagNewFile4 || tag == tagNewFile5 {
|
||||
for {
|
||||
customTag, err := d.readUvarint()
|
||||
@@ -309,7 +343,16 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
if customTag == customTagTerminate {
|
||||
break
|
||||
} else if customTag == customTagVirtual {
|
||||
virtualState.virtual = true
|
||||
n, err := d.readUvarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
virtualState.backingFileNum = n
|
||||
continue
|
||||
}
|
||||
|
||||
field, err := d.readBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -345,6 +388,7 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
SmallestSeqNum: smallestSeqNum,
|
||||
LargestSeqNum: largestSeqNum,
|
||||
MarkedForCompaction: markedForCompaction,
|
||||
Virtual: virtualState.virtual,
|
||||
}
|
||||
if tag != tagNewFile5 { // no range keys present
|
||||
m.SmallestPointKey = base.DecodeInternalKey(smallestPointKey)
|
||||
@@ -376,11 +420,18 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
}
|
||||
m.boundsSet = true
|
||||
m.InitPhysicalBacking()
|
||||
v.NewFiles = append(v.NewFiles, NewFileEntry{
|
||||
if !virtualState.virtual {
|
||||
m.InitPhysicalBacking()
|
||||
}
|
||||
|
||||
nfe := NewFileEntry{
|
||||
Level: level,
|
||||
Meta: m,
|
||||
})
|
||||
}
|
||||
if virtualState.virtual {
|
||||
nfe.BackingFileNum = base.FileNum(virtualState.backingFileNum).DiskFileNum()
|
||||
}
|
||||
v.NewFiles = append(v.NewFiles, nfe)
|
||||
|
||||
case tagPrevLogNumber:
|
||||
n, err := d.readUvarint()
|
||||
@@ -399,8 +450,7 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer for a VersionEdit.
|
||||
func (v *VersionEdit) String() string {
|
||||
func (v *VersionEdit) string(verbose bool, fmtKey base.FormatKey) string {
|
||||
var buf bytes.Buffer
|
||||
if v.ComparerName != "" {
|
||||
fmt.Fprintf(&buf, " comparer: %s", v.ComparerName)
|
||||
@@ -431,7 +481,12 @@ func (v *VersionEdit) String() string {
|
||||
fmt.Fprintf(&buf, " deleted: L%d %s\n", df.Level, df.FileNum)
|
||||
}
|
||||
for _, nf := range v.NewFiles {
|
||||
fmt.Fprintf(&buf, " added: L%d %s", nf.Level, nf.Meta.String())
|
||||
fmt.Fprintf(&buf, " added: L%d", nf.Level)
|
||||
if verbose {
|
||||
fmt.Fprintf(&buf, " %s", nf.Meta.DebugString(fmtKey, true /* verbose */))
|
||||
} else {
|
||||
fmt.Fprintf(&buf, " %s", nf.Meta.String())
|
||||
}
|
||||
if nf.Meta.CreationTime != 0 {
|
||||
fmt.Fprintf(&buf, " (%s)",
|
||||
time.Unix(nf.Meta.CreationTime, 0).UTC().Format(time.RFC3339))
|
||||
@@ -441,9 +496,17 @@ func (v *VersionEdit) String() string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// DebugString is a more verbose version of String(). Use this in tests.
|
||||
func (v *VersionEdit) DebugString(fmtKey base.FormatKey) string {
|
||||
return v.string(true /* verbose */, fmtKey)
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer for a VersionEdit.
|
||||
func (v *VersionEdit) String() string {
|
||||
return v.string(false /* verbose */, base.DefaultFormatter)
|
||||
}
|
||||
|
||||
// Encode encodes an edit to the specified writer.
|
||||
//
|
||||
// TODO(bananabrick): Support encoding of virtual sstable state.
|
||||
func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
e := versionEditEncoder{new(bytes.Buffer)}
|
||||
|
||||
@@ -463,6 +526,15 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
e.writeUvarint(tagNextFileNumber)
|
||||
e.writeUvarint(uint64(v.NextFileNum))
|
||||
}
|
||||
for _, dfn := range v.RemovedBackingTables {
|
||||
e.writeUvarint(tagRemovedBackingTable)
|
||||
e.writeUvarint(uint64(dfn.FileNum()))
|
||||
}
|
||||
for _, fileBacking := range v.CreatedBackingTables {
|
||||
e.writeUvarint(tagCreatedBackingTable)
|
||||
e.writeUvarint(uint64(fileBacking.DiskFileNum.FileNum()))
|
||||
e.writeUvarint(fileBacking.Size)
|
||||
}
|
||||
// RocksDB requires LastSeqNum to be encoded for the first MANIFEST entry,
|
||||
// even though its value is zero. We detect this by encoding LastSeqNum when
|
||||
// ComparerName is set.
|
||||
@@ -476,7 +548,7 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
e.writeUvarint(uint64(x.FileNum))
|
||||
}
|
||||
for _, x := range v.NewFiles {
|
||||
customFields := x.Meta.MarkedForCompaction || x.Meta.CreationTime != 0
|
||||
customFields := x.Meta.MarkedForCompaction || x.Meta.CreationTime != 0 || x.Meta.Virtual
|
||||
var tag uint64
|
||||
switch {
|
||||
case x.Meta.HasRangeKeys:
|
||||
@@ -529,6 +601,10 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
e.writeUvarint(customTagNeedsCompaction)
|
||||
e.writeBytes([]byte{1})
|
||||
}
|
||||
if x.Meta.Virtual {
|
||||
e.writeUvarint(customTagVirtual)
|
||||
e.writeUvarint(uint64(x.Meta.FileBacking.DiskFileNum.FileNum()))
|
||||
}
|
||||
e.writeUvarint(customTagTerminate)
|
||||
}
|
||||
}
|
||||
@@ -536,6 +612,7 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// versionEditDecoder should be used to decode version edits.
|
||||
type versionEditDecoder struct {
|
||||
byteReader
|
||||
}
|
||||
@@ -634,7 +711,9 @@ type BulkVersionEdit struct {
|
||||
Added [NumLevels]map[base.FileNum]*FileMetadata
|
||||
Deleted [NumLevels]map[base.FileNum]*FileMetadata
|
||||
|
||||
AddedFileBacking []*FileBacking
|
||||
// AddedFileBacking is a map to support lookup so that we can populate the
|
||||
// FileBacking of virtual sstables during manifest replay.
|
||||
AddedFileBacking map[base.DiskFileNum]*FileBacking
|
||||
RemovedFileBacking []base.DiskFileNum
|
||||
|
||||
// AddedByFileNum maps file number to file metadata for all added files
|
||||
@@ -697,6 +776,22 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate state for Added backing files. Note that these must be generated
|
||||
// before we loop through the NewFiles, because we need to populate the
|
||||
// FileBackings which might be used by the NewFiles loop.
|
||||
if b.AddedFileBacking == nil {
|
||||
b.AddedFileBacking = make(map[base.DiskFileNum]*FileBacking)
|
||||
}
|
||||
for _, fb := range ve.CreatedBackingTables {
|
||||
if _, ok := b.AddedFileBacking[fb.DiskFileNum]; ok {
|
||||
// There is already a FileBacking associated with fb.DiskFileNum.
|
||||
// This should never happen. There must always be only one FileBacking
|
||||
// associated with a backing sstable.
|
||||
panic(fmt.Sprintf("pebble: duplicate file backing %s", fb.DiskFileNum.String()))
|
||||
}
|
||||
b.AddedFileBacking[fb.DiskFileNum] = fb
|
||||
}
|
||||
|
||||
for _, nf := range ve.NewFiles {
|
||||
// A new file should not have been deleted in this or a preceding
|
||||
// VersionEdit at the same level (though files can move across levels).
|
||||
@@ -705,6 +800,17 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
||||
return base.CorruptionErrorf("pebble: file deleted L%d.%s before it was inserted", nf.Level, nf.Meta.FileNum)
|
||||
}
|
||||
}
|
||||
if nf.Meta.Virtual && nf.Meta.FileBacking == nil {
|
||||
// FileBacking for a virtual sstable must only be nil if we're performing
|
||||
// manifest replay.
|
||||
nf.Meta.FileBacking = b.AddedFileBacking[nf.BackingFileNum]
|
||||
if nf.Meta.FileBacking == nil {
|
||||
return errors.Errorf("FileBacking for virtual sstable must not be nil")
|
||||
}
|
||||
} else if nf.Meta.FileBacking == nil {
|
||||
return errors.Errorf("Added file L%d.%s's has no FileBacking", nf.Level, nf.Meta.FileNum)
|
||||
}
|
||||
|
||||
if b.Added[nf.Level] == nil {
|
||||
b.Added[nf.Level] = make(map[base.FileNum]*FileMetadata)
|
||||
}
|
||||
@@ -717,9 +823,6 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate state for the backing files.
|
||||
b.AddedFileBacking = append(b.AddedFileBacking, ve.CreatedBackingTables...)
|
||||
|
||||
// Since a file can be removed from backing files in exactly one version
|
||||
// edit it is safe to just append without any de-duplication.
|
||||
b.RemovedFileBacking = append(b.RemovedFileBacking, ve.RemovedBackingTables...)
|
||||
@@ -749,6 +852,9 @@ func AccumulateIncompleteAndApplySingleVE(
|
||||
flushSplitBytes int64,
|
||||
readCompactionRate int64,
|
||||
backingStateMap map[base.DiskFileNum]*FileBacking,
|
||||
addBackingFunc func(*FileBacking),
|
||||
removeBackingFunc func(base.DiskFileNum),
|
||||
orderingInvariants OrderingInvariants,
|
||||
) (_ *Version, zombies map[base.DiskFileNum]uint64, _ error) {
|
||||
if len(ve.RemovedBackingTables) != 0 {
|
||||
panic("pebble: invalid incomplete version edit")
|
||||
@@ -760,14 +866,14 @@ func AccumulateIncompleteAndApplySingleVE(
|
||||
}
|
||||
zombies = make(map[base.DiskFileNum]uint64)
|
||||
v, err := b.Apply(
|
||||
curr, cmp, formatKey, flushSplitBytes, readCompactionRate, zombies,
|
||||
curr, cmp, formatKey, flushSplitBytes, readCompactionRate, zombies, orderingInvariants,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, s := range b.AddedFileBacking {
|
||||
backingStateMap[s.DiskFileNum] = s
|
||||
addBackingFunc(s)
|
||||
}
|
||||
|
||||
for fileNum := range zombies {
|
||||
@@ -778,10 +884,9 @@ func AccumulateIncompleteAndApplySingleVE(
|
||||
ve.RemovedBackingTables = append(
|
||||
ve.RemovedBackingTables, fileNum,
|
||||
)
|
||||
delete(backingStateMap, fileNum)
|
||||
removeBackingFunc(fileNum)
|
||||
}
|
||||
}
|
||||
|
||||
return v, zombies, nil
|
||||
}
|
||||
|
||||
@@ -802,6 +907,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
flushSplitBytes int64,
|
||||
readCompactionRate int64,
|
||||
zombies map[base.DiskFileNum]uint64,
|
||||
orderingInvariants OrderingInvariants,
|
||||
) (*Version, error) {
|
||||
addZombie := func(state *FileBacking) {
|
||||
if zombies != nil {
|
||||
@@ -870,7 +976,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
// level.
|
||||
|
||||
for _, f := range deletedFilesMap {
|
||||
if obsolete := v.Levels[level].tree.Delete(f); obsolete {
|
||||
if obsolete := v.Levels[level].remove(f); obsolete {
|
||||
// Deleting a file from the B-Tree may decrement its
|
||||
// reference count. However, because we cloned the
|
||||
// previous level's B-Tree, this should never result in a
|
||||
@@ -879,7 +985,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
return nil, err
|
||||
}
|
||||
if f.HasRangeKeys {
|
||||
if obsolete := v.RangeKeyLevels[level].tree.Delete(f); obsolete {
|
||||
if obsolete := v.RangeKeyLevels[level].remove(f); obsolete {
|
||||
// Deleting a file from the B-Tree may decrement its
|
||||
// reference count. However, because we cloned the
|
||||
// previous level's B-Tree, this should never result in a
|
||||
@@ -932,7 +1038,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
f.AllowedSeeks.Store(allowedSeeks)
|
||||
f.InitAllowedSeeks = allowedSeeks
|
||||
|
||||
err := lm.tree.Insert(f)
|
||||
err := lm.insert(f)
|
||||
// We're adding this file to the new version, so increment the
|
||||
// latest refs count.
|
||||
f.LatestRef()
|
||||
@@ -940,7 +1046,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
return nil, errors.Wrap(err, "pebble")
|
||||
}
|
||||
if f.HasRangeKeys {
|
||||
err = lmRange.tree.Insert(f)
|
||||
err = lmRange.insert(f)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "pebble")
|
||||
}
|
||||
@@ -985,7 +1091,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
} else if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
|
||||
return nil, errors.Wrap(err, "pebble: internal error")
|
||||
}
|
||||
if err := CheckOrdering(cmp, formatKey, Level(0), v.Levels[level].Iter()); err != nil {
|
||||
if err := CheckOrdering(cmp, formatKey, Level(0), v.Levels[level].Iter(), orderingInvariants); err != nil {
|
||||
return nil, errors.Wrap(err, "pebble: internal error")
|
||||
}
|
||||
continue
|
||||
@@ -1006,7 +1112,7 @@ func (b *BulkVersionEdit) Apply(
|
||||
end.Prev()
|
||||
}
|
||||
})
|
||||
if err := CheckOrdering(cmp, formatKey, Level(level), check.Iter()); err != nil {
|
||||
if err := CheckOrdering(cmp, formatKey, Level(level), check.Iter(), orderingInvariants); err != nil {
|
||||
return nil, errors.Wrap(err, "pebble: internal error")
|
||||
}
|
||||
}
|
||||
|
||||
71
vendor/github.com/cockroachdb/pebble/internal/rangekey/coalesce.go
generated
vendored
71
vendor/github.com/cockroachdb/pebble/internal/rangekey/coalesce.go
generated
vendored
@@ -52,15 +52,15 @@ import (
|
||||
// │
|
||||
// ╰── <?>.Next
|
||||
type UserIteratorConfig struct {
|
||||
snapshot uint64
|
||||
comparer *base.Comparer
|
||||
miter keyspan.MergingIter
|
||||
biter keyspan.BoundedIter
|
||||
diter keyspan.DefragmentingIter
|
||||
liters [manifest.NumLevels]keyspan.LevelIter
|
||||
litersUsed int
|
||||
onlySets bool
|
||||
bufs *Buffers
|
||||
snapshot uint64
|
||||
comparer *base.Comparer
|
||||
miter keyspan.MergingIter
|
||||
biter keyspan.BoundedIter
|
||||
diter keyspan.DefragmentingIter
|
||||
liters [manifest.NumLevels]keyspan.LevelIter
|
||||
litersUsed int
|
||||
internalKeys bool
|
||||
bufs *Buffers
|
||||
}
|
||||
|
||||
// Buffers holds various buffers used for range key iteration. They're exposed
|
||||
@@ -79,9 +79,10 @@ func (bufs *Buffers) PrepareForReuse() {
|
||||
|
||||
// Init initializes the range key iterator stack for user iteration. The
|
||||
// resulting fragment iterator applies range key semantics, defragments spans
|
||||
// according to their user-observable state and, if onlySets = true, removes all
|
||||
// according to their user-observable state and, if !internalKeys, removes all
|
||||
// Keys other than RangeKeySets describing the current state of range keys. The
|
||||
// resulting spans contain Keys sorted by Suffix.
|
||||
// resulting spans contain Keys sorted by suffix (unless internalKeys is true,
|
||||
// in which case they remain sorted by trailer descending).
|
||||
//
|
||||
// The snapshot sequence number parameter determines which keys are visible. Any
|
||||
// keys not visible at the provided snapshot are ignored.
|
||||
@@ -91,16 +92,20 @@ func (ui *UserIteratorConfig) Init(
|
||||
lower, upper []byte,
|
||||
hasPrefix *bool,
|
||||
prefix *[]byte,
|
||||
onlySets bool,
|
||||
internalKeys bool,
|
||||
bufs *Buffers,
|
||||
iters ...keyspan.FragmentIterator,
|
||||
) keyspan.FragmentIterator {
|
||||
ui.snapshot = snapshot
|
||||
ui.comparer = comparer
|
||||
ui.onlySets = onlySets
|
||||
ui.internalKeys = internalKeys
|
||||
ui.miter.Init(comparer.Compare, ui, &bufs.merging, iters...)
|
||||
ui.biter.Init(comparer.Compare, comparer.Split, &ui.miter, lower, upper, hasPrefix, prefix)
|
||||
ui.diter.Init(comparer, &ui.biter, ui, keyspan.StaticDefragmentReducer, &bufs.defragmenting)
|
||||
if internalKeys {
|
||||
ui.diter.Init(comparer, &ui.biter, keyspan.DefragmentInternal, keyspan.StaticDefragmentReducer, &bufs.defragmenting)
|
||||
} else {
|
||||
ui.diter.Init(comparer, &ui.biter, ui, keyspan.StaticDefragmentReducer, &bufs.defragmenting)
|
||||
}
|
||||
ui.litersUsed = 0
|
||||
ui.bufs = bufs
|
||||
return &ui.diter
|
||||
@@ -136,7 +141,8 @@ func (ui *UserIteratorConfig) SetBounds(lower, upper []byte) {
|
||||
// of unset keys, removal of keys overwritten by a set at the same suffix, etc)
|
||||
// and then non-RangeKeySet keys are removed. The resulting transformed spans
|
||||
// only contain RangeKeySets describing the state visible at the provided
|
||||
// sequence number, and hold their Keys sorted by Suffix.
|
||||
// sequence number, and hold their Keys sorted by Suffix (except if internalKeys
|
||||
// is true, then keys remain sorted by trailer.
|
||||
func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error {
|
||||
// Apply shadowing of keys.
|
||||
dst.Start = s.Start
|
||||
@@ -148,9 +154,16 @@ func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *k
|
||||
if err := coalesce(ui.comparer.Equal, &ui.bufs.sortBuf, ui.snapshot, s.Keys); err != nil {
|
||||
return err
|
||||
}
|
||||
// During user iteration over range keys, unsets and deletes don't matter.
|
||||
// Remove them if onlySets = true. This step helps logical defragmentation
|
||||
// during iteration.
|
||||
if ui.internalKeys {
|
||||
if s.KeysOrder != keyspan.ByTrailerDesc {
|
||||
panic("unexpected key ordering in UserIteratorTransform with internalKeys = true")
|
||||
}
|
||||
dst.Keys = ui.bufs.sortBuf.Keys
|
||||
keyspan.SortKeysByTrailer(&dst.Keys)
|
||||
return nil
|
||||
}
|
||||
// During user iteration over range keys, unsets and deletes don't matter. This
|
||||
// step helps logical defragmentation during iteration.
|
||||
keys := ui.bufs.sortBuf.Keys
|
||||
dst.Keys = dst.Keys[:0]
|
||||
for i := range keys {
|
||||
@@ -164,17 +177,11 @@ func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *k
|
||||
if invariants.Enabled && len(dst.Keys) > 0 && cmp(dst.Keys[len(dst.Keys)-1].Suffix, keys[i].Suffix) > 0 {
|
||||
panic("pebble: keys unexpectedly not in ascending suffix order")
|
||||
}
|
||||
if ui.onlySets {
|
||||
// Skip.
|
||||
continue
|
||||
}
|
||||
dst.Keys = append(dst.Keys, keys[i])
|
||||
// Skip.
|
||||
continue
|
||||
case base.InternalKeyKindRangeKeyDelete:
|
||||
if ui.onlySets {
|
||||
// Skip.
|
||||
continue
|
||||
}
|
||||
dst.Keys = append(dst.Keys, keys[i])
|
||||
// Skip.
|
||||
continue
|
||||
default:
|
||||
return base.CorruptionErrorf("pebble: unrecognized range key kind %s", keys[i].Kind())
|
||||
}
|
||||
@@ -193,6 +200,10 @@ func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *k
|
||||
// sequence numbers). It's intended for use during user iteration, when the
|
||||
// wrapped keyspan iterator is merging spans across all levels of the LSM.
|
||||
func (ui *UserIteratorConfig) ShouldDefragment(equal base.Equal, a, b *keyspan.Span) bool {
|
||||
// This method is not called with internalKeys = true.
|
||||
if ui.internalKeys {
|
||||
panic("unexpected call to ShouldDefragment with internalKeys = true")
|
||||
}
|
||||
// This implementation must only be used on spans that have transformed by
|
||||
// ui.Transform. The transform applies shadowing, removes all keys besides
|
||||
// the resulting Sets and sorts the keys by suffix. Since shadowing has been
|
||||
@@ -208,8 +219,8 @@ func (ui *UserIteratorConfig) ShouldDefragment(equal base.Equal, a, b *keyspan.S
|
||||
ret := true
|
||||
for i := range a.Keys {
|
||||
if invariants.Enabled {
|
||||
if ui.onlySets && (a.Keys[i].Kind() != base.InternalKeyKindRangeKeySet ||
|
||||
b.Keys[i].Kind() != base.InternalKeyKindRangeKeySet) {
|
||||
if a.Keys[i].Kind() != base.InternalKeyKindRangeKeySet ||
|
||||
b.Keys[i].Kind() != base.InternalKeyKindRangeKeySet {
|
||||
panic("pebble: unexpected non-RangeKeySet during defragmentation")
|
||||
}
|
||||
if i > 0 && (ui.comparer.Compare(a.Keys[i].Suffix, a.Keys[i-1].Suffix) < 0 ||
|
||||
|
||||
387
vendor/github.com/cockroachdb/pebble/internal/rate/rate.go
generated
vendored
387
vendor/github.com/cockroachdb/pebble/internal/rate/rate.go
generated
vendored
@@ -1,387 +0,0 @@
|
||||
// Copyright 2015 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 rate provides a rate limiter.
|
||||
package rate // import "github.com/cockroachdb/pebble/internal/rate"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
// Limit defines the maximum frequency of some events.
|
||||
// Limit is represented as number of events per second.
|
||||
// A zero Limit allows no events.
|
||||
type Limit float64
|
||||
|
||||
// Inf is the infinite rate limit; it allows all events (even if burst is zero).
|
||||
const Inf = Limit(math.MaxFloat64)
|
||||
|
||||
// Every converts a minimum time interval between events to a Limit.
|
||||
func Every(interval time.Duration) Limit {
|
||||
if interval <= 0 {
|
||||
return Inf
|
||||
}
|
||||
return 1 / Limit(interval.Seconds())
|
||||
}
|
||||
|
||||
// A Limiter controls how frequently events are allowed to happen.
|
||||
// It implements a "token bucket" of size b, initially full and refilled
|
||||
// at rate r tokens per second.
|
||||
// Informally, in any large enough time interval, the Limiter limits the
|
||||
// rate to r tokens per second, with a maximum burst size of b events.
|
||||
// As a special case, if r == Inf (the infinite rate), b is ignored.
|
||||
// See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets.
|
||||
//
|
||||
// The zero value is a valid Limiter, but it will reject all events.
|
||||
// Use NewLimiter to create non-zero Limiters.
|
||||
//
|
||||
// Limiter has three main methods, Allow, Reserve, and Wait.
|
||||
// Most callers should use Wait.
|
||||
//
|
||||
// Each of the three methods consumes a single token.
|
||||
// They differ in their behavior when no token is available.
|
||||
// If no token is available, Allow returns false.
|
||||
// If no token is available, Reserve returns a reservation for a future token
|
||||
// and the amount of time the caller must wait before using it.
|
||||
// If no token is available, Wait blocks until one can be obtained
|
||||
// or its associated context.Context is canceled.
|
||||
//
|
||||
// The methods AllowN, ReserveN, and WaitN consume n tokens.
|
||||
type Limiter struct {
|
||||
limit Limit
|
||||
burst int
|
||||
|
||||
mu sync.Mutex
|
||||
tokens float64
|
||||
// last is the last time the limiter's tokens field was updated
|
||||
last time.Time
|
||||
// lastEvent is the latest time of a rate-limited event (past or future)
|
||||
lastEvent time.Time
|
||||
}
|
||||
|
||||
// Limit returns the maximum overall event rate.
|
||||
func (lim *Limiter) Limit() Limit {
|
||||
lim.mu.Lock()
|
||||
defer lim.mu.Unlock()
|
||||
return lim.limit
|
||||
}
|
||||
|
||||
// Burst returns the maximum burst size. Burst is the maximum number of tokens
|
||||
// that can be consumed in a single call to Allow, Reserve, or Wait, so higher
|
||||
// Burst values allow more events to happen at once.
|
||||
// A zero Burst allows no events, unless limit == Inf.
|
||||
func (lim *Limiter) Burst() int {
|
||||
return lim.burst
|
||||
}
|
||||
|
||||
// NewLimiter returns a new Limiter that allows events up to rate r and permits
|
||||
// bursts of at most b tokens.
|
||||
func NewLimiter(r Limit, b int) *Limiter {
|
||||
return &Limiter{
|
||||
limit: r,
|
||||
burst: b,
|
||||
}
|
||||
}
|
||||
|
||||
// Allow is shorthand for AllowN(time.Now(), 1).
|
||||
func (lim *Limiter) Allow() bool {
|
||||
return lim.AllowN(time.Now(), 1)
|
||||
}
|
||||
|
||||
// AllowN reports whether n events may happen at time now.
|
||||
// Use this method if you intend to drop / skip events that exceed the rate limit.
|
||||
// Otherwise use Reserve or Wait.
|
||||
func (lim *Limiter) AllowN(now time.Time, n int) bool {
|
||||
return lim.reserveN(now, n, 0).ok
|
||||
}
|
||||
|
||||
// A Reservation holds information about events that are permitted by a Limiter to happen after a delay.
|
||||
// A Reservation may be canceled, which may enable the Limiter to permit additional events.
|
||||
type Reservation struct {
|
||||
ok bool
|
||||
lim *Limiter
|
||||
tokens int
|
||||
timeToAct time.Time
|
||||
// This is the Limit at reservation time, it can change later.
|
||||
limit Limit
|
||||
}
|
||||
|
||||
// OK returns whether the limiter can provide the requested number of tokens
|
||||
// within the maximum wait time. If OK is false, Delay returns InfDuration, and
|
||||
// Cancel does nothing.
|
||||
func (r *Reservation) OK() bool {
|
||||
return r.ok
|
||||
}
|
||||
|
||||
// Delay is shorthand for DelayFrom(time.Now()).
|
||||
func (r *Reservation) Delay() time.Duration {
|
||||
return r.DelayFrom(time.Now())
|
||||
}
|
||||
|
||||
// InfDuration is the duration returned by Delay when a Reservation is not OK.
|
||||
const InfDuration = time.Duration(1<<63 - 1)
|
||||
|
||||
// DelayFrom returns the duration for which the reservation holder must wait
|
||||
// before taking the reserved action. Zero duration means act immediately.
|
||||
// InfDuration means the limiter cannot grant the tokens requested in this
|
||||
// Reservation within the maximum wait time.
|
||||
func (r *Reservation) DelayFrom(now time.Time) time.Duration {
|
||||
if !r.ok {
|
||||
return InfDuration
|
||||
}
|
||||
delay := r.timeToAct.Sub(now)
|
||||
if delay < 0 {
|
||||
return 0
|
||||
}
|
||||
return delay
|
||||
}
|
||||
|
||||
// Cancel is shorthand for CancelAt(time.Now()).
|
||||
func (r *Reservation) Cancel() {
|
||||
r.CancelAt(time.Now())
|
||||
}
|
||||
|
||||
// CancelAt indicates that the reservation holder will not perform the reserved action
|
||||
// and reverses the effects of this Reservation on the rate limit as much as possible,
|
||||
// considering that other reservations may have already been made.
|
||||
func (r *Reservation) CancelAt(now time.Time) {
|
||||
if !r.ok {
|
||||
return
|
||||
}
|
||||
|
||||
r.lim.mu.Lock()
|
||||
defer r.lim.mu.Unlock()
|
||||
|
||||
if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(now) {
|
||||
return
|
||||
}
|
||||
|
||||
// calculate tokens to restore
|
||||
// The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved
|
||||
// after r was obtained. These tokens should not be restored.
|
||||
restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct))
|
||||
if restoreTokens <= 0 {
|
||||
return
|
||||
}
|
||||
// advance time to now
|
||||
now, _, tokens := r.lim.advance(now)
|
||||
// calculate new number of tokens
|
||||
tokens += restoreTokens
|
||||
if burst := float64(r.lim.burst); tokens > burst {
|
||||
tokens = burst
|
||||
}
|
||||
// update state
|
||||
r.lim.last = now
|
||||
r.lim.tokens = tokens
|
||||
if r.timeToAct == r.lim.lastEvent {
|
||||
prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens)))
|
||||
if !prevEvent.Before(now) {
|
||||
r.lim.lastEvent = prevEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reserve is shorthand for ReserveN(time.Now(), 1).
|
||||
func (lim *Limiter) Reserve() Reservation {
|
||||
return lim.ReserveN(time.Now(), 1)
|
||||
}
|
||||
|
||||
// ReserveN returns a Reservation that indicates how long the caller must wait before n events happen.
|
||||
// The Limiter takes this Reservation into account when allowing future events.
|
||||
// ReserveN returns false if n exceeds the Limiter's burst size.
|
||||
// Usage example:
|
||||
//
|
||||
// r := lim.ReserveN(time.Now(), 1)
|
||||
// if !r.OK() {
|
||||
// // Not allowed to act! Did you remember to set lim.burst to be > 0 ?
|
||||
// return
|
||||
// }
|
||||
// time.Sleep(r.Delay())
|
||||
// Act()
|
||||
//
|
||||
// Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events.
|
||||
// If you need to respect a deadline or cancel the delay, use Wait instead.
|
||||
// To drop or skip events exceeding rate limit, use Allow instead.
|
||||
func (lim *Limiter) ReserveN(now time.Time, n int) Reservation {
|
||||
return lim.reserveN(now, n, InfDuration)
|
||||
}
|
||||
|
||||
// Wait is shorthand for WaitN(ctx, 1).
|
||||
func (lim *Limiter) Wait(ctx context.Context) (err error) {
|
||||
return lim.WaitN(ctx, 1)
|
||||
}
|
||||
|
||||
// WaitN blocks until lim permits n events to happen.
|
||||
// It returns an error if n exceeds the Limiter's burst size, the Context is
|
||||
// canceled, or the expected wait time exceeds the Context's Deadline.
|
||||
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) {
|
||||
if n > lim.burst && lim.limit != Inf {
|
||||
return errors.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", errors.Safe(n), errors.Safe(lim.burst))
|
||||
}
|
||||
// Check if ctx is already cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
// Determine wait limit
|
||||
now := time.Now()
|
||||
waitLimit := InfDuration
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
waitLimit = deadline.Sub(now)
|
||||
}
|
||||
// Reserve
|
||||
r := lim.reserveN(now, n, waitLimit)
|
||||
if !r.ok {
|
||||
return errors.Errorf("rate: Wait(n=%d) would exceed context deadline", errors.Safe(n))
|
||||
}
|
||||
// Wait
|
||||
d := r.DelayFrom(now)
|
||||
if d <= 0 {
|
||||
return nil
|
||||
}
|
||||
t := time.NewTimer(d)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-t.C:
|
||||
// We can proceed.
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
// Context was canceled before we could proceed. Cancel the
|
||||
// reservation, which may permit other events to proceed sooner.
|
||||
r.Cancel()
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// Delay is shorthand for DelayN(time.Now(), 1).
|
||||
func (lim *Limiter) Delay() time.Duration {
|
||||
return lim.DelayN(time.Now(), 1)
|
||||
}
|
||||
|
||||
// DelayN returns the delay to wait to permit n events to happen. Zero duration
|
||||
// means act immediately. InfDuration means the limiter cannot grant the tokens
|
||||
// requested within the maximum wait time.
|
||||
func (lim *Limiter) DelayN(now time.Time, n int) time.Duration {
|
||||
r := lim.reserveN(now, n, InfDuration)
|
||||
return r.DelayFrom(now)
|
||||
}
|
||||
|
||||
// SetLimit is shorthand for SetLimitAt(time.Now(), newLimit).
|
||||
func (lim *Limiter) SetLimit(newLimit Limit) {
|
||||
lim.SetLimitAt(time.Now(), newLimit)
|
||||
}
|
||||
|
||||
// SetLimitAt sets a new Limit for the limiter. The new Limit, and Burst, may be violated
|
||||
// or underutilized by those which reserved (using Reserve or Wait) but did not yet act
|
||||
// before SetLimitAt was called.
|
||||
func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit) {
|
||||
lim.mu.Lock()
|
||||
defer lim.mu.Unlock()
|
||||
|
||||
now, _, tokens := lim.advance(now)
|
||||
|
||||
lim.last = now
|
||||
lim.tokens = tokens
|
||||
lim.limit = newLimit
|
||||
}
|
||||
|
||||
// reserveN is a helper method for AllowN, ReserveN, and WaitN.
|
||||
// maxFutureReserve specifies the maximum reservation wait duration allowed.
|
||||
// reserveN returns Reservation, not *Reservation, to avoid allocation in AllowN and WaitN.
|
||||
func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
|
||||
lim.mu.Lock()
|
||||
|
||||
if lim.limit == Inf {
|
||||
lim.mu.Unlock()
|
||||
return Reservation{
|
||||
ok: true,
|
||||
lim: lim,
|
||||
tokens: n,
|
||||
timeToAct: now,
|
||||
}
|
||||
}
|
||||
|
||||
now, last, tokens := lim.advance(now)
|
||||
|
||||
// Calculate the remaining number of tokens resulting from the request.
|
||||
tokens -= float64(n)
|
||||
|
||||
// Calculate the wait duration
|
||||
var waitDuration time.Duration
|
||||
if tokens < 0 {
|
||||
waitDuration = lim.limit.durationFromTokens(-tokens)
|
||||
}
|
||||
|
||||
// Decide result
|
||||
ok := n <= lim.burst && waitDuration <= maxFutureReserve
|
||||
|
||||
// Prepare reservation
|
||||
r := Reservation{
|
||||
ok: ok,
|
||||
lim: lim,
|
||||
limit: lim.limit,
|
||||
}
|
||||
if ok {
|
||||
r.tokens = n
|
||||
r.timeToAct = now.Add(waitDuration)
|
||||
}
|
||||
|
||||
// Update state
|
||||
if ok {
|
||||
lim.last = now
|
||||
lim.tokens = tokens
|
||||
lim.lastEvent = r.timeToAct
|
||||
} else {
|
||||
lim.last = last
|
||||
}
|
||||
|
||||
lim.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// advance calculates and returns an updated state for lim resulting from the passage of time.
|
||||
// lim is not changed.
|
||||
func (lim *Limiter) advance(
|
||||
now time.Time,
|
||||
) (newNow time.Time, newLast time.Time, newTokens float64) {
|
||||
last := lim.last
|
||||
if now.Before(last) {
|
||||
last = now
|
||||
}
|
||||
|
||||
// Avoid making delta overflow below when last is very old.
|
||||
maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
|
||||
elapsed := now.Sub(last)
|
||||
if elapsed > maxElapsed {
|
||||
elapsed = maxElapsed
|
||||
}
|
||||
|
||||
// Calculate the new number of tokens, due to time that passed.
|
||||
delta := lim.limit.tokensFromDuration(elapsed)
|
||||
tokens := lim.tokens + delta
|
||||
if burst := float64(lim.burst); tokens > burst {
|
||||
tokens = burst
|
||||
}
|
||||
|
||||
return now, last, tokens
|
||||
}
|
||||
|
||||
// durationFromTokens is a unit conversion function from the number of tokens to the duration
|
||||
// of time it takes to accumulate them at a rate of limit tokens per second.
|
||||
func (limit Limit) durationFromTokens(tokens float64) time.Duration {
|
||||
seconds := tokens / float64(limit)
|
||||
return time.Nanosecond * time.Duration(1e9*seconds)
|
||||
}
|
||||
|
||||
// tokensFromDuration is a unit conversion function from a time duration to the number of tokens
|
||||
// which could be accumulated during that duration at a rate of limit tokens per second.
|
||||
func (limit Limit) tokensFromDuration(d time.Duration) float64 {
|
||||
return d.Seconds() * float64(limit)
|
||||
}
|
||||
229
vendor/github.com/cockroachdb/pebble/internal/testkeys/testkeys.go
generated
vendored
229
vendor/github.com/cockroachdb/pebble/internal/testkeys/testkeys.go
generated
vendored
@@ -20,17 +20,27 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"golang.org/x/exp/constraints"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
const alpha = "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
const suffixDelim = '@'
|
||||
|
||||
var inverseAlphabet = make(map[byte]int64, len(alpha))
|
||||
|
||||
func init() {
|
||||
for i := range alpha {
|
||||
inverseAlphabet[alpha[i]] = int64(i)
|
||||
}
|
||||
}
|
||||
|
||||
// MaxSuffixLen is the maximum length of a suffix generated by this package.
|
||||
var MaxSuffixLen = 1 + len(fmt.Sprintf("%d", math.MaxInt64))
|
||||
var MaxSuffixLen = 1 + len(fmt.Sprintf("%d", int64(math.MaxInt64)))
|
||||
|
||||
// Comparer is the comparer for test keys generated by this package.
|
||||
var Comparer *base.Comparer = &base.Comparer{
|
||||
var Comparer = &base.Comparer{
|
||||
Compare: compare,
|
||||
Equal: func(a, b []byte) bool { return compare(a, b) == 0 },
|
||||
AbbreviatedKey: func(k []byte) uint64 {
|
||||
@@ -136,7 +146,7 @@ func compareTimestamps(a, b []byte) int {
|
||||
// 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
|
||||
Count() int64
|
||||
|
||||
// MaxLen returns the maximum length, in bytes, of a key within this
|
||||
// keyspace. This is only guaranteed to return an upper bound.
|
||||
@@ -144,20 +154,21 @@ type Keyspace interface {
|
||||
|
||||
// Slice returns the sub-keyspace from index i, inclusive, to index j,
|
||||
// exclusive. The receiver is unmodified.
|
||||
Slice(i, j int) Keyspace
|
||||
Slice(i, j int64) 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
|
||||
EveryN(n int64) Keyspace
|
||||
|
||||
key(buf []byte, i int) int
|
||||
// key writes the i-th key to the buffer and returns the length.
|
||||
key(buf []byte, i int64) 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 {
|
||||
func Divvy(ks Keyspace, n int64) []Keyspace {
|
||||
ret := make([]Keyspace, n)
|
||||
for i := 0; i < n; i++ {
|
||||
for i := int64(0); i < n; i++ {
|
||||
ret[i] = ks.Slice(i, ks.Count()).EveryN(n)
|
||||
}
|
||||
return ret
|
||||
@@ -175,7 +186,7 @@ func Alpha(maxLength int) Keyspace {
|
||||
|
||||
// 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 {
|
||||
func KeyAt(k Keyspace, i int64, t int64) []byte {
|
||||
b := make([]byte, k.MaxLen()+MaxSuffixLen)
|
||||
return b[:WriteKeyAt(b, k, i, t)]
|
||||
}
|
||||
@@ -183,20 +194,20 @@ func KeyAt(k Keyspace, i int, t int) []byte {
|
||||
// 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 {
|
||||
func WriteKeyAt(dst []byte, k Keyspace, i int64, t int64) 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 {
|
||||
func Suffix(t int64) []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 {
|
||||
func SuffixLen(t int64) int {
|
||||
// Begin at 1 for the '@' delimiter, 1 for a single digit.
|
||||
n := 2
|
||||
t /= 10
|
||||
@@ -208,42 +219,42 @@ func SuffixLen(t int) int {
|
||||
}
|
||||
|
||||
// ParseSuffix returns the integer representation of the encoded suffix.
|
||||
func ParseSuffix(s []byte) (int, error) {
|
||||
return strconv.Atoi(strings.TrimPrefix(string(s), string(suffixDelim)))
|
||||
func ParseSuffix(s []byte) (int64, error) {
|
||||
return strconv.ParseInt(strings.TrimPrefix(string(s), string(suffixDelim)), 10, 64)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func WriteSuffix(dst []byte, t int64) int {
|
||||
dst[0] = suffixDelim
|
||||
n := 1
|
||||
n += len(strconv.AppendInt(dst[n:n], int64(t), 10))
|
||||
n += len(strconv.AppendInt(dst[n:n], t, 10))
|
||||
return n
|
||||
}
|
||||
|
||||
// Key returns the i-th unsuffixed key within the keyspace.
|
||||
func Key(k Keyspace, i int) []byte {
|
||||
func Key(k Keyspace, i int64) []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 {
|
||||
func WriteKey(dst []byte, k Keyspace, i int64) int {
|
||||
return k.key(dst, i)
|
||||
}
|
||||
|
||||
type alphabet struct {
|
||||
alphabet []byte
|
||||
maxLength int
|
||||
headSkip int
|
||||
tailSkip int
|
||||
increment int
|
||||
headSkip int64
|
||||
tailSkip int64
|
||||
increment int64
|
||||
}
|
||||
|
||||
func (a alphabet) Count() int {
|
||||
func (a alphabet) Count() int64 {
|
||||
// Calculate the total number of keys, ignoring the increment.
|
||||
total := (keyCount(len(a.alphabet), a.maxLength) - a.headSkip - a.tailSkip)
|
||||
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:
|
||||
@@ -263,20 +274,25 @@ func (a alphabet) MaxLen() int {
|
||||
return a.maxLength
|
||||
}
|
||||
|
||||
func (a alphabet) Slice(i, j int) Keyspace {
|
||||
func (a alphabet) Slice(i, j int64) Keyspace {
|
||||
s := a
|
||||
s.headSkip += i
|
||||
s.tailSkip += a.Count() - j
|
||||
return s
|
||||
}
|
||||
|
||||
func (a alphabet) EveryN(n int) Keyspace {
|
||||
func (a alphabet) EveryN(n int64) Keyspace {
|
||||
s := a
|
||||
s.increment *= n
|
||||
return s
|
||||
}
|
||||
|
||||
func keyCount(n, l int) int {
|
||||
func keyCount(n, l int) int64 {
|
||||
if n == 0 {
|
||||
return 0
|
||||
} else if n == 1 {
|
||||
return int64(l)
|
||||
}
|
||||
// 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:
|
||||
@@ -286,10 +302,10 @@ func keyCount(n, l int) int {
|
||||
// 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)
|
||||
return (int64(n) * (int64(math.Pow(float64(n), float64(l))) - 1)) / int64(n-1)
|
||||
}
|
||||
|
||||
func (a alphabet) key(buf []byte, idx int) int {
|
||||
func (a alphabet) key(buf []byte, idx int64) 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.
|
||||
@@ -308,14 +324,14 @@ func (a alphabet) key(buf []byte, idx int) int {
|
||||
keyCount(len(a.alphabet), a.maxLength))
|
||||
}
|
||||
|
||||
func generateAlphabetKey(buf, alphabet []byte, i, keyCount int) int {
|
||||
func generateAlphabetKey(buf, alphabet []byte, i, keyCount int64) 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)
|
||||
keysPerCharacter := keyCount / int64(len(alphabet))
|
||||
|
||||
// Find the character that the key at index i starts with and set it.
|
||||
characterIdx := i / keysPerCharacter
|
||||
@@ -347,3 +363,156 @@ func generateAlphabetKey(buf, alphabet []byte, i, keyCount int) int {
|
||||
i = i - keysPerCharacter*characterIdx - 1
|
||||
return 1 + generateAlphabetKey(buf[1:], alphabet, i, keysPerCharacter-1)
|
||||
}
|
||||
|
||||
// computeAlphabetKeyIndex computes the inverse of generateAlphabetKey,
|
||||
// returning the index of a particular key, given the provided alphabet and max
|
||||
// length of a key.
|
||||
//
|
||||
// len(key) must be ≥ 1.
|
||||
func computeAlphabetKeyIndex(key []byte, alphabet map[byte]int64, n int) int64 {
|
||||
i, ok := alphabet[key[0]]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unrecognized alphabet character %v", key[0]))
|
||||
}
|
||||
// How many keys exist that start with the preceding i characters? Each of
|
||||
// the i characters themselves are a key, plus the count of all the keys
|
||||
// with one less character for each.
|
||||
ret := i + i*keyCount(len(alphabet), n-1)
|
||||
if len(key) > 1 {
|
||||
ret += 1 + computeAlphabetKeyIndex(key[1:], alphabet, n-1)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func abs(a int64) int64 {
|
||||
if a < 0 {
|
||||
return -a
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// RandomSeparator returns a random alphabetic key k such that a < k < b,
|
||||
// pulling randomness from the provided random number generator. If dst is
|
||||
// provided and the generated key fits within dst's capacity, the returned slice
|
||||
// will use dst's memory.
|
||||
//
|
||||
// If a prefix P exists such that Prefix(a) < P < Prefix(b), the generated key
|
||||
// will consist of the prefix P appended with the provided suffix. A zero suffix
|
||||
// generates an unsuffixed key. If no such prefix P exists, RandomSeparator will
|
||||
// try to find a key k with either Prefix(a) or Prefix(b) such that a < k < b,
|
||||
// but the generated key will not use the provided suffix. Note that it's
|
||||
// possible that no separator key exists (eg, a='a@2', b='a@1'), in which case
|
||||
// RandomSeparator returns nil.
|
||||
//
|
||||
// If RandomSeparator generates a new prefix, the generated prefix will have
|
||||
// length at most MAX(maxLength, len(Prefix(a)), len(Prefix(b))).
|
||||
//
|
||||
// RandomSeparator panics if a or b fails to decode.
|
||||
func RandomSeparator(dst, a, b []byte, suffix int64, maxLength int, rng *rand.Rand) []byte {
|
||||
if Comparer.Compare(a, b) >= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine both keys' logical prefixes and suffixes.
|
||||
ai := Comparer.Split(a)
|
||||
bi := Comparer.Split(b)
|
||||
ap := a[:ai]
|
||||
bp := b[:bi]
|
||||
maxLength = max[int](maxLength, max[int](len(ap), len(bp)))
|
||||
var as, bs int64
|
||||
var err error
|
||||
if ai != len(a) {
|
||||
as, err = ParseSuffix(a[ai:])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to parse suffix of %q", a))
|
||||
}
|
||||
}
|
||||
if bi != len(b) {
|
||||
bs, err = ParseSuffix(b[bi:])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to parse suffix of %q", b))
|
||||
}
|
||||
}
|
||||
|
||||
apIdx := computeAlphabetKeyIndex(ap, inverseAlphabet, maxLength)
|
||||
bpIdx := computeAlphabetKeyIndex(bp, inverseAlphabet, maxLength)
|
||||
diff := bpIdx - apIdx
|
||||
generatedIdx := bpIdx
|
||||
if diff > 0 {
|
||||
var add int64 = diff + 1
|
||||
var start int64 = apIdx
|
||||
if as == 1 {
|
||||
// There's no expressible key with prefix a greater than a@1. So,
|
||||
// exclude ap.
|
||||
start = apIdx + 1
|
||||
add = diff
|
||||
}
|
||||
if bs == 0 {
|
||||
// No key with prefix b can sort before b@0. We don't want to pick b.
|
||||
add--
|
||||
}
|
||||
// We're allowing generated id to be in the range [start, start + add - 1].
|
||||
if start > start+add-1 {
|
||||
return nil
|
||||
}
|
||||
// If we can generate a key which is actually in the middle of apIdx
|
||||
// and bpIdx use it so that we don't have to bother about timestamps.
|
||||
generatedIdx = rng.Int63n(add) + start
|
||||
for diff > 1 && generatedIdx == apIdx || generatedIdx == bpIdx {
|
||||
generatedIdx = rng.Int63n(add) + start
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case generatedIdx == apIdx && generatedIdx == bpIdx:
|
||||
if abs(bs-as) <= 1 {
|
||||
// There's no expressible suffix between the two, and there's no
|
||||
// possible separator key.
|
||||
return nil
|
||||
}
|
||||
// The key b is >= key a, but has the same prefix, so b must have the
|
||||
// smaller timestamp, unless a has timestamp of 0.
|
||||
//
|
||||
// NB: The zero suffix (suffix-less) sorts before all other suffixes, so
|
||||
// any suffix we generate will be greater than it.
|
||||
if as == 0 {
|
||||
// bs > as
|
||||
suffix = bs + rng.Int63n(10) + 1
|
||||
} else {
|
||||
// bs < as.
|
||||
// Generate suffix in range [bs + 1, as - 1]
|
||||
suffix = bs + 1 + rng.Int63n(as-bs-1)
|
||||
}
|
||||
case generatedIdx == apIdx:
|
||||
// NB: The zero suffix (suffix-less) sorts before all other suffixes, so
|
||||
// any suffix we generate will be greater than it.
|
||||
if as == 0 && suffix == 0 {
|
||||
suffix++
|
||||
} else if as != 0 && suffix >= as {
|
||||
suffix = rng.Int63n(as)
|
||||
}
|
||||
case generatedIdx == bpIdx:
|
||||
if suffix <= bs {
|
||||
suffix = bs + rng.Int63n(10) + 1
|
||||
}
|
||||
}
|
||||
if sz := maxLength + SuffixLen(suffix); cap(dst) < sz {
|
||||
dst = make([]byte, sz)
|
||||
} else {
|
||||
dst = dst[:cap(dst)]
|
||||
}
|
||||
var w int
|
||||
if suffix == 0 {
|
||||
w = WriteKey(dst, Alpha(maxLength), generatedIdx)
|
||||
} else {
|
||||
w = WriteKeyAt(dst, Alpha(maxLength), generatedIdx, suffix)
|
||||
}
|
||||
return dst[:w]
|
||||
}
|
||||
|
||||
func max[I constraints.Ordered](a, b I) I {
|
||||
if b > a {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
428
vendor/github.com/cockroachdb/pebble/iterator.go
generated
vendored
428
vendor/github.com/cockroachdb/pebble/iterator.go
generated
vendored
@@ -154,6 +154,18 @@ func (s *RangeKeyIteratorStats) Merge(o RangeKeyIteratorStats) {
|
||||
s.SkippedPoints += o.SkippedPoints
|
||||
}
|
||||
|
||||
func (s *RangeKeyIteratorStats) String() string {
|
||||
return redact.StringWithoutMarkers(s)
|
||||
}
|
||||
|
||||
// SafeFormat implements the redact.SafeFormatter interface.
|
||||
func (s *RangeKeyIteratorStats) SafeFormat(p redact.SafePrinter, verb rune) {
|
||||
p.Printf("range keys: %s, contained points: %s (%s skipped)",
|
||||
humanize.Count.Uint64(uint64(s.Count)),
|
||||
humanize.Count.Uint64(uint64(s.ContainedPoints)),
|
||||
humanize.Count.Uint64(uint64(s.SkippedPoints)))
|
||||
}
|
||||
|
||||
// LazyValue is a lazy value. See the long comment in base.LazyValue.
|
||||
type LazyValue = base.LazyValue
|
||||
|
||||
@@ -187,7 +199,9 @@ type Iterator struct {
|
||||
comparer base.Comparer
|
||||
iter internalIterator
|
||||
pointIter internalIterator
|
||||
// Either readState or version is set, but not both.
|
||||
readState *readState
|
||||
version *version
|
||||
// rangeKey holds iteration state specific to iteration over range keys.
|
||||
// The range key field may be nil if the Iterator has never been configured
|
||||
// to iterate over range keys. Its non-nilness cannot be used to determine
|
||||
@@ -445,6 +459,11 @@ const (
|
||||
seekPrefixGELastPositioningOp
|
||||
seekGELastPositioningOp
|
||||
seekLTLastPositioningOp
|
||||
// internalNextOp is a special internal iterator positioning operation used
|
||||
// by CanDeterministicallySingleDelete. It exists for enforcing requirements
|
||||
// around calling CanDeterministicallySingleDelete at most once per external
|
||||
// iterator position.
|
||||
internalNextOp
|
||||
// invalidatedLastPositionOp is similar to unknownLastPositionOp and the
|
||||
// only reason to distinguish this is for the wider set of SeekGE
|
||||
// optimizations we permit for the external iterator Iterator.forwardOnly
|
||||
@@ -548,6 +567,21 @@ func (i *Iterator) findNextEntry(limit []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
// If the user has configured a SkipPoint function, invoke it to see
|
||||
// whether we should skip over the current user key.
|
||||
if i.opts.SkipPoint != nil && key.Kind() != InternalKeyKindRangeKeySet && i.opts.SkipPoint(i.iterKey.UserKey) {
|
||||
// NB: We could call nextUserKey, but in some cases the SkipPoint
|
||||
// predicate function might be cheaper than nextUserKey's key copy
|
||||
// and key comparison. This should be the case for MVCC suffix
|
||||
// comparisons, for example. In the future, we could expand the
|
||||
// SkipPoint interface to give the implementor more control over
|
||||
// whether we skip over just the internal key, the user key, or even
|
||||
// the key prefix.
|
||||
i.stats.ForwardStepCount[InternalIterCall]++
|
||||
i.iterKey, i.iterValue = i.iter.Next()
|
||||
continue
|
||||
}
|
||||
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindRangeKeySet:
|
||||
// Save the current key.
|
||||
@@ -568,7 +602,10 @@ func (i *Iterator) findNextEntry(limit []byte) {
|
||||
i.iterValidityState = IterValid
|
||||
return
|
||||
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
// NB: treating InternalKeyKindSingleDelete as equivalent to DEL is not
|
||||
// only simpler, but is also necessary for correctness due to
|
||||
// InternalKeyKindSSTableInternalObsoleteBit.
|
||||
i.nextUserKey()
|
||||
continue
|
||||
|
||||
@@ -614,6 +651,13 @@ func (i *Iterator) findNextEntry(limit []byte) {
|
||||
}
|
||||
|
||||
func (i *Iterator) nextPointCurrentUserKey() bool {
|
||||
// If the user has configured a SkipPoint function and the current user key
|
||||
// would be skipped by it, there's no need to step forward looking for a
|
||||
// point key. If we were to find one, it should be skipped anyways.
|
||||
if i.opts.SkipPoint != nil && i.opts.SkipPoint(i.key) {
|
||||
return false
|
||||
}
|
||||
|
||||
i.pos = iterPosCurForward
|
||||
|
||||
i.iterKey, i.iterValue = i.iter.Next()
|
||||
@@ -631,7 +675,10 @@ func (i *Iterator) nextPointCurrentUserKey() bool {
|
||||
i.err = base.CorruptionErrorf("pebble: unexpected range key set mid-user key")
|
||||
return false
|
||||
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
// NB: treating InternalKeyKindSingleDelete as equivalent to DEL is not
|
||||
// only simpler, but is also necessary for correctness due to
|
||||
// InternalKeyKindSSTableInternalObsoleteBit.
|
||||
return false
|
||||
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||
@@ -772,39 +819,41 @@ func (i *Iterator) maybeSampleRead() {
|
||||
func (i *Iterator) sampleRead() {
|
||||
var topFile *manifest.FileMetadata
|
||||
topLevel, numOverlappingLevels := numLevels, 0
|
||||
if mi, ok := i.iter.(*mergingIter); ok {
|
||||
if len(mi.levels) > 1 {
|
||||
mi.ForEachLevelIter(func(li *levelIter) bool {
|
||||
l := manifest.LevelToInt(li.level)
|
||||
if f := li.iterFile; f != nil {
|
||||
var containsKey bool
|
||||
if i.pos == iterPosNext || i.pos == iterPosCurForward ||
|
||||
i.pos == iterPosCurForwardPaused {
|
||||
containsKey = i.cmp(f.SmallestPointKey.UserKey, i.key) <= 0
|
||||
} else if i.pos == iterPosPrev || i.pos == iterPosCurReverse ||
|
||||
i.pos == iterPosCurReversePaused {
|
||||
containsKey = i.cmp(f.LargestPointKey.UserKey, i.key) >= 0
|
||||
}
|
||||
// Do nothing if the current key is not contained in f's
|
||||
// bounds. We could seek the LevelIterator at this level
|
||||
// to find the right file, but the performance impacts of
|
||||
// doing that are significant enough to negate the benefits
|
||||
// of read sampling in the first place. See the discussion
|
||||
// at:
|
||||
// https://github.com/cockroachdb/pebble/pull/1041#issuecomment-763226492
|
||||
if containsKey {
|
||||
numOverlappingLevels++
|
||||
if numOverlappingLevels >= 2 {
|
||||
// Terminate the loop early if at least 2 overlapping levels are found.
|
||||
return true
|
||||
}
|
||||
topLevel = l
|
||||
topFile = f
|
||||
}
|
||||
mi := i.merging
|
||||
if mi == nil {
|
||||
return
|
||||
}
|
||||
if len(mi.levels) > 1 {
|
||||
mi.ForEachLevelIter(func(li *levelIter) bool {
|
||||
l := manifest.LevelToInt(li.level)
|
||||
if f := li.iterFile; f != nil {
|
||||
var containsKey bool
|
||||
if i.pos == iterPosNext || i.pos == iterPosCurForward ||
|
||||
i.pos == iterPosCurForwardPaused {
|
||||
containsKey = i.cmp(f.SmallestPointKey.UserKey, i.key) <= 0
|
||||
} else if i.pos == iterPosPrev || i.pos == iterPosCurReverse ||
|
||||
i.pos == iterPosCurReversePaused {
|
||||
containsKey = i.cmp(f.LargestPointKey.UserKey, i.key) >= 0
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
// Do nothing if the current key is not contained in f's
|
||||
// bounds. We could seek the LevelIterator at this level
|
||||
// to find the right file, but the performance impacts of
|
||||
// doing that are significant enough to negate the benefits
|
||||
// of read sampling in the first place. See the discussion
|
||||
// at:
|
||||
// https://github.com/cockroachdb/pebble/pull/1041#issuecomment-763226492
|
||||
if containsKey {
|
||||
numOverlappingLevels++
|
||||
if numOverlappingLevels >= 2 {
|
||||
// Terminate the loop early if at least 2 overlapping levels are found.
|
||||
return true
|
||||
}
|
||||
topLevel = l
|
||||
topFile = f
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
if topFile == nil || topLevel >= numLevels {
|
||||
return
|
||||
@@ -901,6 +950,26 @@ func (i *Iterator) findPrevEntry(limit []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// If the user has configured a SkipPoint function, invoke it to see
|
||||
// whether we should skip over the current user key.
|
||||
if i.opts.SkipPoint != nil && key.Kind() != InternalKeyKindRangeKeySet && i.opts.SkipPoint(key.UserKey) {
|
||||
// NB: We could call prevUserKey, but in some cases the SkipPoint
|
||||
// predicate function might be cheaper than prevUserKey's key copy
|
||||
// and key comparison. This should be the case for MVCC suffix
|
||||
// comparisons, for example. In the future, we could expand the
|
||||
// SkipPoint interface to give the implementor more control over
|
||||
// whether we skip over just the internal key, the user key, or even
|
||||
// the key prefix.
|
||||
i.stats.ReverseStepCount[InternalIterCall]++
|
||||
i.iterKey, i.iterValue = i.iter.Prev()
|
||||
if limit != nil && i.iterKey != nil && i.cmp(limit, i.iterKey.UserKey) > 0 && !i.rangeKeyWithinLimit(limit) {
|
||||
i.iterValidityState = IterAtLimit
|
||||
i.pos = iterPosCurReversePaused
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindRangeKeySet:
|
||||
// Range key start boundary markers are interleaved with the maximum
|
||||
@@ -929,7 +998,7 @@ func (i *Iterator) findPrevEntry(limit []byte) {
|
||||
// return the key even if the MERGE point key is deleted.
|
||||
rangeKeyBoundary = true
|
||||
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
i.value = LazyValue{}
|
||||
i.iterValidityState = IterExhausted
|
||||
valueMerger = nil
|
||||
@@ -938,12 +1007,12 @@ func (i *Iterator) findPrevEntry(limit []byte) {
|
||||
// Compare with the limit. We could optimize by only checking when
|
||||
// we step to the previous user key, but detecting that requires a
|
||||
// comparison too. Note that this position may already passed a
|
||||
// number of versions of this user key, but they are all deleted,
|
||||
// so the fact that a subsequent Prev*() call will not see them is
|
||||
// number of versions of this user key, but they are all deleted, so
|
||||
// the fact that a subsequent Prev*() call will not see them is
|
||||
// harmless. Also note that this is the only place in the loop,
|
||||
// other than the firstLoopIter case above, where we could step
|
||||
// to a different user key and start processing it for returning
|
||||
// to the caller.
|
||||
// other than the firstLoopIter and SkipPoint cases above, where we
|
||||
// could step to a different user key and start processing it for
|
||||
// returning to the caller.
|
||||
if limit != nil && i.iterKey != nil && i.cmp(limit, i.iterKey.UserKey) > 0 && !i.rangeKeyWithinLimit(limit) {
|
||||
i.iterValidityState = IterAtLimit
|
||||
i.pos = iterPosCurReversePaused
|
||||
@@ -1092,9 +1161,13 @@ func (i *Iterator) mergeNext(key InternalKey, valueMerger ValueMerger) {
|
||||
return
|
||||
}
|
||||
switch key.Kind() {
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||
// We've hit a deletion tombstone. Return everything up to this
|
||||
// point.
|
||||
//
|
||||
// NB: treating InternalKeyKindSingleDelete as equivalent to DEL is not
|
||||
// only simpler, but is also necessary for correctness due to
|
||||
// InternalKeyKindSSTableInternalObsoleteBit.
|
||||
return
|
||||
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||
@@ -1750,7 +1823,8 @@ func (i *Iterator) internalNextPrefix(currKeyPrefixLen int) {
|
||||
i.iterKey, i.iterValue = i.iter.NextPrefix(i.prefixOrFullSeekKey)
|
||||
if invariants.Enabled && i.iterKey != nil {
|
||||
if iterKeyPrefixLen := i.split(i.iterKey.UserKey); i.cmp(i.iterKey.UserKey[:iterKeyPrefixLen], i.prefixOrFullSeekKey) < 0 {
|
||||
panic("pebble: iter.NextPrefix did not advance beyond the current prefix")
|
||||
panic(errors.AssertionFailedf("pebble: iter.NextPrefix did not advance beyond the current prefix: now at %q; expected to be geq %q",
|
||||
i.iterKey, i.prefixOrFullSeekKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2119,6 +2193,9 @@ func (i *Iterator) Value() []byte {
|
||||
|
||||
// ValueAndErr returns the value, and any error encountered in extracting the value.
|
||||
// REQUIRES: i.Error()==nil and HasPointAndRange() returns true for hasPoint.
|
||||
//
|
||||
// The caller should not modify the contents of the returned slice, and its
|
||||
// contents may change on the next call to Next.
|
||||
func (i *Iterator) ValueAndErr() ([]byte, error) {
|
||||
val, callerOwned, err := i.value.Value(i.lazyValueBuf)
|
||||
if err != nil {
|
||||
@@ -2149,7 +2226,13 @@ func (i *Iterator) RangeKeys() []RangeKeyData {
|
||||
// Valid returns true if the iterator is positioned at a valid key/value pair
|
||||
// and false otherwise.
|
||||
func (i *Iterator) Valid() bool {
|
||||
return i.iterValidityState == IterValid && !i.requiresReposition
|
||||
valid := i.iterValidityState == IterValid && !i.requiresReposition
|
||||
if invariants.Enabled {
|
||||
if err := i.Error(); valid && err != nil {
|
||||
panic(errors.WithSecondaryError(errors.AssertionFailedf("pebble: iterator is valid with non-nil Error"), err))
|
||||
}
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
// Error returns any accumulated error.
|
||||
@@ -2214,6 +2297,10 @@ func (i *Iterator) Close() error {
|
||||
i.readState = nil
|
||||
}
|
||||
|
||||
if i.version != nil {
|
||||
i.version.Unref()
|
||||
}
|
||||
|
||||
for _, readers := range i.externalReaders {
|
||||
for _, r := range readers {
|
||||
err = firstError(err, r.Close())
|
||||
@@ -2406,7 +2493,8 @@ func (i *Iterator) SetOptions(o *IterOptions) {
|
||||
// If either options specify block property filters for an iterator stack,
|
||||
// reconstruct it.
|
||||
if i.pointIter != nil && (closeBoth || len(o.PointKeyFilters) > 0 || len(i.opts.PointKeyFilters) > 0 ||
|
||||
o.RangeKeyMasking.Filter != nil || i.opts.RangeKeyMasking.Filter != nil) {
|
||||
o.RangeKeyMasking.Filter != nil || i.opts.RangeKeyMasking.Filter != nil || o.SkipPoint != nil ||
|
||||
i.opts.SkipPoint != nil) {
|
||||
i.err = firstError(i.err, i.pointIter.Close())
|
||||
i.pointIter = nil
|
||||
}
|
||||
@@ -2645,11 +2733,22 @@ func (i *Iterator) CloneWithContext(ctx context.Context, opts CloneOptions) (*It
|
||||
}
|
||||
|
||||
readState := i.readState
|
||||
if readState == nil {
|
||||
vers := i.version
|
||||
if readState == nil && vers == nil {
|
||||
return nil, errors.Errorf("cannot Clone a closed Iterator")
|
||||
}
|
||||
// i is already holding a ref, so there is no race with unref here.
|
||||
readState.ref()
|
||||
//
|
||||
// TODO(bilal): If the underlying iterator was created on a snapshot, we could
|
||||
// grab a reference to the current readState instead of reffing the original
|
||||
// readState. This allows us to release references to some zombie sstables
|
||||
// and memtables.
|
||||
if readState != nil {
|
||||
readState.ref()
|
||||
}
|
||||
if vers != nil {
|
||||
vers.Ref()
|
||||
}
|
||||
// Bundle various structures under a single umbrella in order to allocate
|
||||
// them together.
|
||||
buf := iterAllocPool.Get().(*iterAlloc)
|
||||
@@ -2661,6 +2760,7 @@ func (i *Iterator) CloneWithContext(ctx context.Context, opts CloneOptions) (*It
|
||||
merge: i.merge,
|
||||
comparer: i.comparer,
|
||||
readState: readState,
|
||||
version: vers,
|
||||
keyBuf: buf.keyBuf,
|
||||
prefixOrFullSeekKey: buf.prefixOrFullSeekKey,
|
||||
boundsBuf: buf.boundsBuf,
|
||||
@@ -2700,43 +2800,209 @@ func (stats *IteratorStats) String() string {
|
||||
|
||||
// SafeFormat implements the redact.SafeFormatter interface.
|
||||
func (stats *IteratorStats) SafeFormat(s redact.SafePrinter, verb rune) {
|
||||
for i := range stats.ForwardStepCount {
|
||||
switch IteratorStatsKind(i) {
|
||||
case InterfaceCall:
|
||||
s.SafeString("(interface (dir, seek, step): ")
|
||||
case InternalIterCall:
|
||||
s.SafeString(", (internal (dir, seek, step): ")
|
||||
}
|
||||
s.Printf("(fwd, %d, %d), (rev, %d, %d))",
|
||||
redact.Safe(stats.ForwardSeekCount[i]), redact.Safe(stats.ForwardStepCount[i]),
|
||||
redact.Safe(stats.ReverseSeekCount[i]), redact.Safe(stats.ReverseStepCount[i]))
|
||||
}
|
||||
if stats.InternalStats != (InternalIteratorStats{}) {
|
||||
s.SafeString(",\n(internal-stats: ")
|
||||
s.Printf("(block-bytes: (total %s, cached %s, read-time %s)), "+
|
||||
"(points: (count %s, key-bytes %s, value-bytes %s, tombstoned %s))",
|
||||
humanize.IEC.Uint64(stats.InternalStats.BlockBytes),
|
||||
humanize.IEC.Uint64(stats.InternalStats.BlockBytesInCache),
|
||||
humanize.FormattedString(stats.InternalStats.BlockReadDuration.String()),
|
||||
humanize.SI.Uint64(stats.InternalStats.PointCount),
|
||||
humanize.SI.Uint64(stats.InternalStats.KeyBytes),
|
||||
humanize.SI.Uint64(stats.InternalStats.ValueBytes),
|
||||
humanize.SI.Uint64(stats.InternalStats.PointsCoveredByRangeTombstones),
|
||||
if stats.ReverseSeekCount[InterfaceCall] == 0 && stats.ReverseSeekCount[InternalIterCall] == 0 {
|
||||
s.Printf("seeked %s times (%s internal)",
|
||||
humanize.Count.Uint64(uint64(stats.ForwardSeekCount[InterfaceCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ForwardSeekCount[InternalIterCall])),
|
||||
)
|
||||
if stats.InternalStats.SeparatedPointValue.Count != 0 {
|
||||
s.Printf(", (separated: (count %s, bytes %s, fetched %s)))",
|
||||
humanize.SI.Uint64(stats.InternalStats.SeparatedPointValue.Count),
|
||||
humanize.IEC.Uint64(stats.InternalStats.SeparatedPointValue.ValueBytes),
|
||||
humanize.IEC.Uint64(stats.InternalStats.SeparatedPointValue.ValueBytesFetched))
|
||||
} else {
|
||||
s.Printf(")")
|
||||
}
|
||||
} else {
|
||||
s.Printf("seeked %s times (%s fwd/%s rev, internal: %s fwd/%s rev)",
|
||||
humanize.Count.Uint64(uint64(stats.ForwardSeekCount[InterfaceCall]+stats.ReverseSeekCount[InterfaceCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ForwardSeekCount[InterfaceCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ReverseSeekCount[InterfaceCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ForwardSeekCount[InternalIterCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ReverseSeekCount[InternalIterCall])),
|
||||
)
|
||||
}
|
||||
s.SafeString("; ")
|
||||
|
||||
if stats.ReverseStepCount[InterfaceCall] == 0 && stats.ReverseStepCount[InternalIterCall] == 0 {
|
||||
s.Printf("stepped %s times (%s internal)",
|
||||
humanize.Count.Uint64(uint64(stats.ForwardStepCount[InterfaceCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ForwardStepCount[InternalIterCall])),
|
||||
)
|
||||
} else {
|
||||
s.Printf("stepped %s times (%s fwd/%s rev, internal: %s fwd/%s rev)",
|
||||
humanize.Count.Uint64(uint64(stats.ForwardStepCount[InterfaceCall]+stats.ReverseStepCount[InterfaceCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ForwardStepCount[InterfaceCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ReverseStepCount[InterfaceCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ForwardStepCount[InternalIterCall])),
|
||||
humanize.Count.Uint64(uint64(stats.ReverseStepCount[InternalIterCall])),
|
||||
)
|
||||
}
|
||||
|
||||
if stats.InternalStats != (InternalIteratorStats{}) {
|
||||
s.SafeString("; ")
|
||||
stats.InternalStats.SafeFormat(s, verb)
|
||||
}
|
||||
if stats.RangeKeyStats != (RangeKeyIteratorStats{}) {
|
||||
s.SafeString(",\n(range-key-stats: ")
|
||||
s.Printf("(count %d), (contained points: (count %d, skipped %d)))",
|
||||
stats.RangeKeyStats.Count,
|
||||
stats.RangeKeyStats.ContainedPoints,
|
||||
stats.RangeKeyStats.SkippedPoints)
|
||||
s.SafeString(", ")
|
||||
stats.RangeKeyStats.SafeFormat(s, verb)
|
||||
}
|
||||
}
|
||||
|
||||
// CanDeterministicallySingleDelete takes a valid iterator and examines internal
|
||||
// state to determine if a SingleDelete deleting Iterator.Key() would
|
||||
// deterministically delete the key. CanDeterministicallySingleDelete requires
|
||||
// the iterator to be oriented in the forward direction (eg, the last
|
||||
// positioning operation must've been a First, a Seek[Prefix]GE, or a
|
||||
// Next[Prefix][WithLimit]).
|
||||
//
|
||||
// This function does not change the external position of the iterator, and all
|
||||
// positioning methods should behave the same as if it was never called. This
|
||||
// function will only return a meaningful result the first time it's invoked at
|
||||
// an iterator position. This function invalidates the iterator Value's memory,
|
||||
// and the caller must not rely on the memory safety of the previous Iterator
|
||||
// position.
|
||||
//
|
||||
// If CanDeterministicallySingleDelete returns true AND the key at the iterator
|
||||
// position is not modified between the creation of the Iterator and the commit
|
||||
// of a batch containing a SingleDelete over the key, then the caller can be
|
||||
// assured that SingleDelete is equivalent to Delete on the local engine, but it
|
||||
// may not be true on another engine that received the same writes and with
|
||||
// logically equivalent state since this engine may have collapsed multiple SETs
|
||||
// into one.
|
||||
func CanDeterministicallySingleDelete(it *Iterator) (bool, error) {
|
||||
// This function may only be called once per external iterator position. We
|
||||
// can validate this by checking the last positioning operation.
|
||||
if it.lastPositioningOp == internalNextOp {
|
||||
return false, errors.New("pebble: CanDeterministicallySingleDelete called twice")
|
||||
}
|
||||
validity, kind := it.internalNext()
|
||||
var shadowedBySingleDelete bool
|
||||
for validity == internalNextValid {
|
||||
switch kind {
|
||||
case InternalKeyKindDelete, InternalKeyKindDeleteSized:
|
||||
// A DEL or DELSIZED tombstone is okay. An internal key
|
||||
// sequence like SINGLEDEL; SET; DEL; SET can be handled
|
||||
// deterministically. If there are SETs further down, we
|
||||
// don't care about them.
|
||||
return true, nil
|
||||
case InternalKeyKindSingleDelete:
|
||||
// A SingleDelete is okay as long as when that SingleDelete was
|
||||
// written, it was written deterministically (eg, with its own
|
||||
// CanDeterministicallySingleDelete check). Validate that it was
|
||||
// written deterministically. We'll allow one set to appear after
|
||||
// the SingleDelete.
|
||||
shadowedBySingleDelete = true
|
||||
validity, kind = it.internalNext()
|
||||
continue
|
||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete, InternalKeyKindMerge:
|
||||
// If we observed a single delete, it's allowed to delete 1 key.
|
||||
// We'll keep looping to validate that the internal keys beneath the
|
||||
// already-written single delete are copacetic.
|
||||
if shadowedBySingleDelete {
|
||||
shadowedBySingleDelete = false
|
||||
validity, kind = it.internalNext()
|
||||
continue
|
||||
}
|
||||
// We encountered a shadowed SET, SETWITHDEL, MERGE. A SINGLEDEL
|
||||
// that deleted the KV at the original iterator position could
|
||||
// result in this key becoming visible.
|
||||
return false, nil
|
||||
case InternalKeyKindRangeDelete:
|
||||
// RangeDeletes are handled by the merging iterator and should never
|
||||
// be observed by the top-level Iterator.
|
||||
panic(errors.AssertionFailedf("pebble: unexpected range delete"))
|
||||
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||
// Range keys are interleaved at the maximal sequence number and
|
||||
// should never be observed within a user key.
|
||||
panic(errors.AssertionFailedf("pebble: unexpected range key"))
|
||||
default:
|
||||
panic(errors.AssertionFailedf("pebble: unexpected key kind: %s", errors.Safe(kind)))
|
||||
}
|
||||
}
|
||||
if validity == internalNextError {
|
||||
return false, it.Error()
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// internalNextValidity enumerates the potential outcomes of a call to
|
||||
// internalNext.
|
||||
type internalNextValidity int8
|
||||
|
||||
const (
|
||||
// internalNextError is returned by internalNext when an error occurred and
|
||||
// the caller is responsible for checking iter.Error().
|
||||
internalNextError internalNextValidity = iota
|
||||
// internalNextExhausted is returned by internalNext when the next internal
|
||||
// key is an internal key with a different user key than Iterator.Key().
|
||||
internalNextExhausted
|
||||
// internalNextValid is returned by internalNext when the internal next
|
||||
// found a shadowed internal key with a user key equal to Iterator.Key().
|
||||
internalNextValid
|
||||
)
|
||||
|
||||
// internalNext advances internal Iterator state forward to expose the
|
||||
// InternalKeyKind of the next internal key with a user key equal to Key().
|
||||
//
|
||||
// internalNext is a highly specialized operation and is unlikely to be
|
||||
// generally useful. See Iterator.Next for how to reposition the iterator to the
|
||||
// next key. internalNext requires the Iterator to be at a valid position in the
|
||||
// forward direction (the last positioning operation must've been a First, a
|
||||
// Seek[Prefix]GE, or a Next[Prefix][WithLimit] and Valid() must return true).
|
||||
//
|
||||
// internalNext, unlike all other Iterator methods, exposes internal LSM state.
|
||||
// internalNext advances the Iterator's internal iterator to the next shadowed
|
||||
// key with a user key equal to Key(). When a key is overwritten or deleted, its
|
||||
// removal from the LSM occurs lazily as a part of compactions. internalNext
|
||||
// allows the caller to see whether an obsolete internal key exists with the
|
||||
// current Key(), and what it's key kind is. Note that the existence of an
|
||||
// internal key is nondeterministic and dependent on internal LSM state. These
|
||||
// semantics are unlikely to be applicable to almost all use cases.
|
||||
//
|
||||
// If internalNext finds a key that shares the same user key as Key(), it
|
||||
// returns internalNextValid and the internal key's kind. If internalNext
|
||||
// encounters an error, it returns internalNextError and the caller is expected
|
||||
// to call Iterator.Error() to retrieve it. In all other circumstances,
|
||||
// internalNext returns internalNextExhausted, indicating that there are no more
|
||||
// additional internal keys with the user key Key().
|
||||
//
|
||||
// internalNext does not change the external position of the iterator, and a
|
||||
// Next operation should behave the same as if internalNext was never called.
|
||||
// internalNext does invalidate the iterator Value's memory, and the caller must
|
||||
// not rely on the memory safety of the previous Iterator position.
|
||||
func (i *Iterator) internalNext() (internalNextValidity, base.InternalKeyKind) {
|
||||
i.stats.ForwardStepCount[InterfaceCall]++
|
||||
if i.err != nil {
|
||||
return internalNextError, base.InternalKeyKindInvalid
|
||||
} else if i.iterValidityState != IterValid {
|
||||
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||
}
|
||||
i.lastPositioningOp = internalNextOp
|
||||
|
||||
switch i.pos {
|
||||
case iterPosCurForward:
|
||||
i.iterKey, i.iterValue = i.iter.Next()
|
||||
if i.iterKey == nil {
|
||||
// We check i.iter.Error() here and return an internalNextError enum
|
||||
// variant so that the caller does not need to check i.iter.Error()
|
||||
// in the common case that the next internal key has a new user key.
|
||||
if i.err = i.iter.Error(); i.err != nil {
|
||||
return internalNextError, base.InternalKeyKindInvalid
|
||||
}
|
||||
i.pos = iterPosNext
|
||||
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||
} else if i.comparer.Equal(i.iterKey.UserKey, i.key) {
|
||||
return internalNextValid, i.iterKey.Kind()
|
||||
}
|
||||
i.pos = iterPosNext
|
||||
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||
case iterPosCurReverse, iterPosCurReversePaused, iterPosPrev:
|
||||
i.err = errors.New("switching from reverse to forward via internalNext is prohibited")
|
||||
i.iterValidityState = IterExhausted
|
||||
return internalNextError, base.InternalKeyKindInvalid
|
||||
case iterPosNext, iterPosCurForwardPaused:
|
||||
// The previous method already moved onto the next user key. This is
|
||||
// only possible if
|
||||
// - the last positioning method was a call to internalNext, and we
|
||||
// advanced to a new user key.
|
||||
// - the previous non-internalNext iterator operation encountered a
|
||||
// range key or merge, forcing an internal Next that found a new
|
||||
// user key that's not equal to i.Iterator.Key().
|
||||
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
15
vendor/github.com/cockroachdb/pebble/level_checker.go
generated
vendored
15
vendor/github.com/cockroachdb/pebble/level_checker.go
generated
vendored
@@ -165,7 +165,7 @@ func (m *simpleMergingIter) step() bool {
|
||||
if m.valueMerger != nil {
|
||||
// Ongoing series of MERGE records.
|
||||
switch item.key.Kind() {
|
||||
case InternalKeyKindSingleDelete, InternalKeyKindDelete:
|
||||
case InternalKeyKindSingleDelete, InternalKeyKindDelete, InternalKeyKindDeleteSized:
|
||||
var closer io.Closer
|
||||
_, closer, m.err = m.valueMerger.Finish(true /* includesBase */)
|
||||
if m.err == nil && closer != nil {
|
||||
@@ -345,7 +345,7 @@ func iterateAndCheckTombstones(
|
||||
|
||||
type checkConfig struct {
|
||||
logger Logger
|
||||
cmp Compare
|
||||
comparer *Comparer
|
||||
readState *readState
|
||||
newIters tableNewIters
|
||||
seqNum uint64
|
||||
@@ -354,6 +354,9 @@ type checkConfig struct {
|
||||
formatKey base.FormatKey
|
||||
}
|
||||
|
||||
// cmp is shorthand for comparer.Compare.
|
||||
func (c *checkConfig) cmp(a, b []byte) int { return c.comparer.Compare(a, b) }
|
||||
|
||||
func checkRangeTombstones(c *checkConfig) error {
|
||||
var level int
|
||||
var tombstones []tombstoneWithLevel
|
||||
@@ -379,7 +382,7 @@ func checkRangeTombstones(c *checkConfig) error {
|
||||
atomicUnit, _ := expandToAtomicUnit(c.cmp, lf.Slice(), true /* disableIsCompacting */)
|
||||
lower, upper := manifest.KeyRange(c.cmp, atomicUnit.Iter())
|
||||
iterToClose, iter, err := c.newIters(
|
||||
context.Background(), lf.FileMetadata, nil, internalIterOpts{})
|
||||
context.Background(), lf.FileMetadata, &IterOptions{level: manifest.Level(lsmLevel)}, internalIterOpts{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -571,7 +574,7 @@ func (d *DB) CheckLevels(stats *CheckLevelsStats) error {
|
||||
|
||||
checkConfig := &checkConfig{
|
||||
logger: d.opts.Logger,
|
||||
cmp: d.cmp,
|
||||
comparer: d.opts.Comparer,
|
||||
readState: readState,
|
||||
newIters: d.newIters,
|
||||
seqNum: seqNum,
|
||||
@@ -639,7 +642,7 @@ func checkLevelsInternal(c *checkConfig) (err error) {
|
||||
manifestIter := current.L0SublevelFiles[sublevel].Iter()
|
||||
iterOpts := IterOptions{logger: c.logger}
|
||||
li := &levelIter{}
|
||||
li.init(context.Background(), iterOpts, c.cmp, nil /* split */, c.newIters, manifestIter,
|
||||
li.init(context.Background(), iterOpts, c.comparer, c.newIters, manifestIter,
|
||||
manifest.L0Sublevel(sublevel), internalIterOpts{})
|
||||
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
|
||||
li.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
|
||||
@@ -653,7 +656,7 @@ func checkLevelsInternal(c *checkConfig) (err error) {
|
||||
|
||||
iterOpts := IterOptions{logger: c.logger}
|
||||
li := &levelIter{}
|
||||
li.init(context.Background(), iterOpts, c.cmp, nil /* split */, c.newIters,
|
||||
li.init(context.Background(), iterOpts, c.comparer, c.newIters,
|
||||
current.Levels[level].Iter(), manifest.Level(level), internalIterOpts{})
|
||||
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
|
||||
li.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
|
||||
|
||||
62
vendor/github.com/cockroachdb/pebble/level_iter.go
generated
vendored
62
vendor/github.com/cockroachdb/pebble/level_iter.go
generated
vendored
@@ -36,9 +36,8 @@ type tableNewIters func(
|
||||
// tableNewRangeDelIter takes a tableNewIters and returns a TableNewSpanIter
|
||||
// for the rangedel iterator returned by tableNewIters.
|
||||
func tableNewRangeDelIter(ctx context.Context, newIters tableNewIters) keyspan.TableNewSpanIter {
|
||||
return func(file *manifest.FileMetadata, iterOptions *keyspan.SpanIterOptions) (keyspan.FragmentIterator, error) {
|
||||
iter, rangeDelIter, err := newIters(
|
||||
ctx, file, &IterOptions{RangeKeyFilters: iterOptions.RangeKeyFilters}, internalIterOpts{})
|
||||
return func(file *manifest.FileMetadata, iterOptions keyspan.SpanIterOptions) (keyspan.FragmentIterator, error) {
|
||||
iter, rangeDelIter, err := newIters(ctx, file, nil, internalIterOpts{})
|
||||
if iter != nil {
|
||||
_ = iter.Close()
|
||||
}
|
||||
@@ -51,6 +50,7 @@ func tableNewRangeDelIter(ctx context.Context, newIters tableNewIters) keyspan.T
|
||||
|
||||
type internalIterOpts struct {
|
||||
bytesIterated *uint64
|
||||
bufferPool *sstable.BufferPool
|
||||
stats *base.InternalIteratorStats
|
||||
boundLimitedFilter sstable.BoundLimitedBlockPropertyFilter
|
||||
}
|
||||
@@ -82,10 +82,11 @@ type levelIter struct {
|
||||
// short-lived (since they pin sstables), (b) plumbing a context into every
|
||||
// method is very painful, (c) they do not (yet) respect context
|
||||
// cancellation and are only used for tracing.
|
||||
ctx context.Context
|
||||
logger Logger
|
||||
cmp Compare
|
||||
split Split
|
||||
ctx context.Context
|
||||
logger Logger
|
||||
comparer *Comparer
|
||||
cmp Compare
|
||||
split Split
|
||||
// The lower/upper bounds for iteration as specified at creation or the most
|
||||
// recent call to SetBounds.
|
||||
lower []byte
|
||||
@@ -200,6 +201,11 @@ type levelIter struct {
|
||||
// cache when constructing new table iterators.
|
||||
internalOpts internalIterOpts
|
||||
|
||||
// Scratch space for the obsolete keys filter, when there are no other block
|
||||
// property filters specified. See the performance note where
|
||||
// IterOptions.PointKeyFilters is declared.
|
||||
filtersBuf [1]BlockPropertyFilter
|
||||
|
||||
// Disable invariant checks even if they are otherwise enabled. Used by tests
|
||||
// which construct "impossible" situations (e.g. seeking to a key before the
|
||||
// lower bound).
|
||||
@@ -236,24 +242,22 @@ var _ base.InternalIterator = (*levelIter)(nil)
|
||||
// parameter if the caller is never going to call SeekPrefixGE.
|
||||
func newLevelIter(
|
||||
opts IterOptions,
|
||||
cmp Compare,
|
||||
split Split,
|
||||
comparer *Comparer,
|
||||
newIters tableNewIters,
|
||||
files manifest.LevelIterator,
|
||||
level manifest.Level,
|
||||
bytesIterated *uint64,
|
||||
internalOpts internalIterOpts,
|
||||
) *levelIter {
|
||||
l := &levelIter{}
|
||||
l.init(context.Background(), opts, cmp, split, newIters, files, level,
|
||||
internalIterOpts{bytesIterated: bytesIterated})
|
||||
l.init(context.Background(), opts, comparer, newIters, files, level,
|
||||
internalOpts)
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *levelIter) init(
|
||||
ctx context.Context,
|
||||
opts IterOptions,
|
||||
cmp Compare,
|
||||
split Split,
|
||||
comparer *Comparer,
|
||||
newIters tableNewIters,
|
||||
files manifest.LevelIterator,
|
||||
level manifest.Level,
|
||||
@@ -267,10 +271,15 @@ func (l *levelIter) init(
|
||||
l.upper = opts.UpperBound
|
||||
l.tableOpts.TableFilter = opts.TableFilter
|
||||
l.tableOpts.PointKeyFilters = opts.PointKeyFilters
|
||||
if len(opts.PointKeyFilters) == 0 {
|
||||
l.tableOpts.PointKeyFilters = l.filtersBuf[:0:1]
|
||||
}
|
||||
l.tableOpts.UseL6Filters = opts.UseL6Filters
|
||||
l.tableOpts.level = l.level
|
||||
l.cmp = cmp
|
||||
l.split = split
|
||||
l.tableOpts.snapshotForHideObsoletePoints = opts.snapshotForHideObsoletePoints
|
||||
l.comparer = comparer
|
||||
l.cmp = comparer.Compare
|
||||
l.split = comparer.Split
|
||||
l.iterFile = nil
|
||||
l.newIters = newIters
|
||||
l.files = files
|
||||
@@ -788,7 +797,7 @@ func (l *levelIter) SeekPrefixGE(
|
||||
}
|
||||
return l.verify(l.largestBoundary, base.LazyValue{})
|
||||
}
|
||||
// It is possible that we are here because bloom filter matching failed. In
|
||||
// It is possible that we are here because bloom filter matching failed. In
|
||||
// that case it is likely that all keys matching the prefix are wholly
|
||||
// within the current file and cannot be in the subsequent file. In that
|
||||
// case we don't want to go to the next file, since loading and seeking in
|
||||
@@ -796,7 +805,16 @@ func (l *levelIter) SeekPrefixGE(
|
||||
// next file will defeat the optimization for the next SeekPrefixGE that is
|
||||
// called with flags.TrySeekUsingNext(), since for sparse key spaces it is
|
||||
// likely that the next key will also be contained in the current file.
|
||||
if n := l.split(l.iterFile.LargestPointKey.UserKey); l.cmp(prefix, l.iterFile.LargestPointKey.UserKey[:n]) < 0 {
|
||||
var n int
|
||||
if l.split != nil {
|
||||
// If the split function is specified, calculate the prefix length accordingly.
|
||||
n = l.split(l.iterFile.LargestPointKey.UserKey)
|
||||
} else {
|
||||
// If the split function is not specified, the entire key is used as the
|
||||
// prefix. This case can occur when getIter uses SeekPrefixGE.
|
||||
n = len(l.iterFile.LargestPointKey.UserKey)
|
||||
}
|
||||
if l.cmp(prefix, l.iterFile.LargestPointKey.UserKey[:n]) < 0 {
|
||||
return nil, base.LazyValue{}
|
||||
}
|
||||
return l.verify(l.skipEmptyFileForward())
|
||||
@@ -1066,7 +1084,9 @@ func (l *levelIter) skipEmptyFileForward() (*InternalKey, base.LazyValue) {
|
||||
if *l.rangeDelIterPtr != nil && l.filteredIter != nil &&
|
||||
l.filteredIter.MaybeFilteredKeys() {
|
||||
l.largestBoundary = &l.iterFile.Largest
|
||||
l.boundaryContext.isIgnorableBoundaryKey = true
|
||||
if l.boundaryContext != nil {
|
||||
l.boundaryContext.isIgnorableBoundaryKey = true
|
||||
}
|
||||
return l.largestBoundary, base.LazyValue{}
|
||||
}
|
||||
}
|
||||
@@ -1155,7 +1175,9 @@ func (l *levelIter) skipEmptyFileBackward() (*InternalKey, base.LazyValue) {
|
||||
// the next file.
|
||||
if *l.rangeDelIterPtr != nil && l.filteredIter != nil && l.filteredIter.MaybeFilteredKeys() {
|
||||
l.smallestBoundary = &l.iterFile.Smallest
|
||||
l.boundaryContext.isIgnorableBoundaryKey = true
|
||||
if l.boundaryContext != nil {
|
||||
l.boundaryContext.isIgnorableBoundaryKey = true
|
||||
}
|
||||
return l.smallestBoundary, base.LazyValue{}
|
||||
}
|
||||
}
|
||||
|
||||
92
vendor/github.com/cockroachdb/pebble/mem_table.go
generated
vendored
92
vendor/github.com/cockroachdb/pebble/mem_table.go
generated
vendored
@@ -10,12 +10,12 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/arenaskl"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||
"github.com/cockroachdb/pebble/internal/manual"
|
||||
"github.com/cockroachdb/pebble/internal/rangedel"
|
||||
"github.com/cockroachdb/pebble/internal/rangekey"
|
||||
)
|
||||
@@ -85,7 +85,16 @@ type memTable struct {
|
||||
rangeKeys keySpanCache
|
||||
// The current logSeqNum at the time the memtable was created. This is
|
||||
// guaranteed to be less than or equal to any seqnum stored in the memtable.
|
||||
logSeqNum uint64
|
||||
logSeqNum uint64
|
||||
releaseAccountingReservation func()
|
||||
}
|
||||
|
||||
func (m *memTable) free() {
|
||||
if m != nil {
|
||||
m.releaseAccountingReservation()
|
||||
manual.Free(m.arenaBuf)
|
||||
m.arenaBuf = nil
|
||||
}
|
||||
}
|
||||
|
||||
// memTableOptions holds configuration used when creating a memTable. All of
|
||||
@@ -93,9 +102,10 @@ type memTable struct {
|
||||
// which is used by tests.
|
||||
type memTableOptions struct {
|
||||
*Options
|
||||
arenaBuf []byte
|
||||
size int
|
||||
logSeqNum uint64
|
||||
arenaBuf []byte
|
||||
size int
|
||||
logSeqNum uint64
|
||||
releaseAccountingReservation func()
|
||||
}
|
||||
|
||||
func checkMemTable(obj interface{}) {
|
||||
@@ -110,16 +120,22 @@ func checkMemTable(obj interface{}) {
|
||||
// Options.MemTableSize is used instead.
|
||||
func newMemTable(opts memTableOptions) *memTable {
|
||||
opts.Options = opts.Options.EnsureDefaults()
|
||||
if opts.size == 0 {
|
||||
opts.size = opts.MemTableSize
|
||||
}
|
||||
m := new(memTable)
|
||||
m.init(opts)
|
||||
return m
|
||||
}
|
||||
|
||||
m := &memTable{
|
||||
cmp: opts.Comparer.Compare,
|
||||
formatKey: opts.Comparer.FormatKey,
|
||||
equal: opts.Comparer.Equal,
|
||||
arenaBuf: opts.arenaBuf,
|
||||
logSeqNum: opts.logSeqNum,
|
||||
func (m *memTable) init(opts memTableOptions) {
|
||||
if opts.size == 0 {
|
||||
opts.size = int(opts.MemTableSize)
|
||||
}
|
||||
*m = memTable{
|
||||
cmp: opts.Comparer.Compare,
|
||||
formatKey: opts.Comparer.FormatKey,
|
||||
equal: opts.Comparer.Equal,
|
||||
arenaBuf: opts.arenaBuf,
|
||||
logSeqNum: opts.logSeqNum,
|
||||
releaseAccountingReservation: opts.releaseAccountingReservation,
|
||||
}
|
||||
m.writerRefs.Store(1)
|
||||
m.tombstones = keySpanCache{
|
||||
@@ -144,7 +160,6 @@ func newMemTable(opts memTableOptions) *memTable {
|
||||
m.rangeDelSkl.Reset(arena, m.cmp)
|
||||
m.rangeKeySkl.Reset(arena, m.cmp)
|
||||
m.reserved = arena.Size()
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *memTable) writerRef() {
|
||||
@@ -154,7 +169,8 @@ func (m *memTable) writerRef() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memTable) writerUnref() bool {
|
||||
// writerUnref drops a ref on the memtable. Returns true if this was the last ref.
|
||||
func (m *memTable) writerUnref() (wasLastRef bool) {
|
||||
switch v := m.writerRefs.Add(-1); {
|
||||
case v < 0:
|
||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||
@@ -165,6 +181,7 @@ func (m *memTable) writerUnref() bool {
|
||||
}
|
||||
}
|
||||
|
||||
// readyForFlush is part of the flushable interface.
|
||||
func (m *memTable) readyForFlush() bool {
|
||||
return m.writerRefs.Load() == 0
|
||||
}
|
||||
@@ -194,11 +211,13 @@ func (m *memTable) apply(batch *Batch, seqNum uint64) error {
|
||||
var tombstoneCount, rangeKeyCount uint32
|
||||
startSeqNum := seqNum
|
||||
for r := batch.Reader(); ; seqNum++ {
|
||||
kind, ukey, value, ok := r.Next()
|
||||
kind, ukey, value, ok, err := r.Next()
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
var err error
|
||||
ikey := base.MakeInternalKey(ukey, seqNum, kind)
|
||||
switch kind {
|
||||
case InternalKeyKindRangeDelete:
|
||||
@@ -233,17 +252,19 @@ func (m *memTable) apply(batch *Batch, seqNum uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// newIter returns an iterator that is unpositioned (Iterator.Valid() will
|
||||
// return false). The iterator can be positioned via a call to SeekGE,
|
||||
// SeekLT, First or Last.
|
||||
// newIter is part of the flushable interface. It returns an iterator that is
|
||||
// unpositioned (Iterator.Valid() will return false). The iterator can be
|
||||
// positioned via a call to SeekGE, SeekLT, First or Last.
|
||||
func (m *memTable) newIter(o *IterOptions) internalIterator {
|
||||
return m.skl.NewIter(o.GetLowerBound(), o.GetUpperBound())
|
||||
}
|
||||
|
||||
// newFlushIter is part of the flushable interface.
|
||||
func (m *memTable) newFlushIter(o *IterOptions, bytesFlushed *uint64) internalIterator {
|
||||
return m.skl.NewFlushIter(bytesFlushed)
|
||||
}
|
||||
|
||||
// newRangeDelIter is part of the flushable interface.
|
||||
func (m *memTable) newRangeDelIter(*IterOptions) keyspan.FragmentIterator {
|
||||
tombstones := m.tombstones.get()
|
||||
if tombstones == nil {
|
||||
@@ -252,6 +273,7 @@ func (m *memTable) newRangeDelIter(*IterOptions) keyspan.FragmentIterator {
|
||||
return keyspan.NewIter(m.cmp, tombstones)
|
||||
}
|
||||
|
||||
// newRangeKeyIter is part of the flushable interface.
|
||||
func (m *memTable) newRangeKeyIter(*IterOptions) keyspan.FragmentIterator {
|
||||
rangeKeys := m.rangeKeys.get()
|
||||
if rangeKeys == nil {
|
||||
@@ -260,8 +282,9 @@ func (m *memTable) newRangeKeyIter(*IterOptions) keyspan.FragmentIterator {
|
||||
return keyspan.NewIter(m.cmp, rangeKeys)
|
||||
}
|
||||
|
||||
// containsRangeKeys is part of the flushable interface.
|
||||
func (m *memTable) containsRangeKeys() bool {
|
||||
return atomic.LoadUint32(&m.rangeKeys.atomicCount) > 0
|
||||
return m.rangeKeys.count.Load() > 0
|
||||
}
|
||||
|
||||
func (m *memTable) availBytes() uint32 {
|
||||
@@ -275,10 +298,12 @@ func (m *memTable) availBytes() uint32 {
|
||||
return a.Capacity() - m.reserved
|
||||
}
|
||||
|
||||
// inuseBytes is part of the flushable interface.
|
||||
func (m *memTable) inuseBytes() uint64 {
|
||||
return uint64(m.skl.Size() - memTableEmptySize)
|
||||
}
|
||||
|
||||
// totalBytes is part of the flushable interface.
|
||||
func (m *memTable) totalBytes() uint64 {
|
||||
return uint64(m.skl.Arena().Capacity())
|
||||
}
|
||||
@@ -355,8 +380,8 @@ func (f *keySpanFrags) get(
|
||||
// invalidated whenever a key of the same kind is added to a memTable, and
|
||||
// populated when empty when a span iterator of that key kind is created.
|
||||
type keySpanCache struct {
|
||||
atomicCount uint32
|
||||
frags unsafe.Pointer
|
||||
count atomic.Uint32
|
||||
frags atomic.Pointer[keySpanFrags]
|
||||
cmp Compare
|
||||
formatKey base.FormatKey
|
||||
constructSpan constructSpan
|
||||
@@ -366,23 +391,20 @@ type keySpanCache struct {
|
||||
// Invalidate the current set of cached spans, indicating the number of
|
||||
// spans that were added.
|
||||
func (c *keySpanCache) invalidate(count uint32) {
|
||||
newCount := atomic.AddUint32(&c.atomicCount, count)
|
||||
newCount := c.count.Add(count)
|
||||
var frags *keySpanFrags
|
||||
|
||||
for {
|
||||
oldPtr := atomic.LoadPointer(&c.frags)
|
||||
if oldPtr != nil {
|
||||
oldFrags := (*keySpanFrags)(oldPtr)
|
||||
if oldFrags.count >= newCount {
|
||||
// Someone else invalidated the cache before us and their invalidation
|
||||
// subsumes ours.
|
||||
break
|
||||
}
|
||||
oldFrags := c.frags.Load()
|
||||
if oldFrags != nil && oldFrags.count >= newCount {
|
||||
// Someone else invalidated the cache before us and their invalidation
|
||||
// subsumes ours.
|
||||
break
|
||||
}
|
||||
if frags == nil {
|
||||
frags = &keySpanFrags{count: newCount}
|
||||
}
|
||||
if atomic.CompareAndSwapPointer(&c.frags, oldPtr, unsafe.Pointer(frags)) {
|
||||
if c.frags.CompareAndSwap(oldFrags, frags) {
|
||||
// We successfully invalidated the cache.
|
||||
break
|
||||
}
|
||||
@@ -391,7 +413,7 @@ func (c *keySpanCache) invalidate(count uint32) {
|
||||
}
|
||||
|
||||
func (c *keySpanCache) get() []keyspan.Span {
|
||||
frags := (*keySpanFrags)(atomic.LoadPointer(&c.frags))
|
||||
frags := c.frags.Load()
|
||||
if frags == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
10
vendor/github.com/cockroachdb/pebble/merging_iter.go
generated
vendored
10
vendor/github.com/cockroachdb/pebble/merging_iter.go
generated
vendored
@@ -27,6 +27,10 @@ type mergingIterLevel struct {
|
||||
// iterKey and iterValue cache the current key and value iter are pointed at.
|
||||
iterKey *InternalKey
|
||||
iterValue base.LazyValue
|
||||
// levelIter is non-nil if this level's iter is ultimately backed by a
|
||||
// *levelIter. The handle in iter may have wrapped the levelIter with
|
||||
// intermediary internalIterator implementations.
|
||||
levelIter *levelIter
|
||||
|
||||
// levelIterBoundaryContext's fields are set when using levelIter, in order
|
||||
// to surface sstable boundary keys and file-level context. See levelIter
|
||||
@@ -1371,9 +1375,9 @@ func (m *mergingIter) DebugString() string {
|
||||
}
|
||||
|
||||
func (m *mergingIter) ForEachLevelIter(fn func(li *levelIter) bool) {
|
||||
for _, iter := range m.levels {
|
||||
if li, ok := iter.iter.(*levelIter); ok {
|
||||
if done := fn(li); done {
|
||||
for _, ml := range m.levels {
|
||||
if ml.levelIter != nil {
|
||||
if done := fn(ml.levelIter); done {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
408
vendor/github.com/cockroachdb/pebble/metrics.go
generated
vendored
408
vendor/github.com/cockroachdb/pebble/metrics.go
generated
vendored
@@ -6,12 +6,13 @@ package pebble
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/cache"
|
||||
"github.com/cockroachdb/pebble/internal/humanize"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||
"github.com/cockroachdb/pebble/record"
|
||||
"github.com/cockroachdb/pebble/sstable"
|
||||
"github.com/cockroachdb/redact"
|
||||
@@ -28,13 +29,10 @@ type FilterMetrics = sstable.FilterMetrics
|
||||
// comment in base.
|
||||
type ThroughputMetric = base.ThroughputMetric
|
||||
|
||||
func formatCacheMetrics(w redact.SafePrinter, m *CacheMetrics, name redact.SafeString) {
|
||||
w.Printf("%7s %9s %7s %6.1f%% (score == hit-rate)\n",
|
||||
name,
|
||||
humanize.SI.Int64(m.Count),
|
||||
humanize.IEC.Int64(m.Size),
|
||||
redact.Safe(hitRate(m.Hits, m.Misses)))
|
||||
}
|
||||
// SecondaryCacheMetrics holds metrics for the persistent secondary cache
|
||||
// that caches commonly accessed blocks from blob storage on a local
|
||||
// file system.
|
||||
type SecondaryCacheMetrics = sharedcache.Metrics
|
||||
|
||||
// LevelMetrics holds per-level metrics such as the number of files and total
|
||||
// size of the files, and compaction related metrics.
|
||||
@@ -46,9 +44,14 @@ type LevelMetrics struct {
|
||||
Sublevels int32
|
||||
// The total number of files in the level.
|
||||
NumFiles int64
|
||||
// The total number of virtual sstables in the level.
|
||||
NumVirtualFiles uint64
|
||||
// The total size in bytes of the files in the level.
|
||||
Size int64
|
||||
// The level's compaction score.
|
||||
// The total size of the virtual sstables in the level.
|
||||
VirtualSize uint64
|
||||
// The level's compaction score. This is the compensatedScoreRatio in the
|
||||
// candidateLevelInfo.
|
||||
Score float64
|
||||
// The number of incoming bytes from other levels read during
|
||||
// compactions. This excludes bytes moved and bytes ingested. For L0 this is
|
||||
@@ -79,6 +82,18 @@ type LevelMetrics struct {
|
||||
TablesIngested uint64
|
||||
// The number of sstables moved to this level by a "move" compaction.
|
||||
TablesMoved uint64
|
||||
|
||||
MultiLevel struct {
|
||||
// BytesInTop are the total bytes in a multilevel compaction coming from the top level.
|
||||
BytesInTop uint64
|
||||
|
||||
// BytesIn, exclusively for multiLevel compactions.
|
||||
BytesIn uint64
|
||||
|
||||
// BytesRead, exclusively for multilevel compactions.
|
||||
BytesRead uint64
|
||||
}
|
||||
|
||||
// Additional contains misc additional metrics that are not always printed.
|
||||
Additional struct {
|
||||
// The sum of Properties.ValueBlocksSize for all the sstables in this
|
||||
@@ -96,6 +111,8 @@ type LevelMetrics struct {
|
||||
// Add updates the counter metrics for the level.
|
||||
func (m *LevelMetrics) Add(u *LevelMetrics) {
|
||||
m.NumFiles += u.NumFiles
|
||||
m.NumVirtualFiles += u.NumVirtualFiles
|
||||
m.VirtualSize += u.VirtualSize
|
||||
m.Size += u.Size
|
||||
m.BytesIn += u.BytesIn
|
||||
m.BytesIngested += u.BytesIngested
|
||||
@@ -107,6 +124,9 @@ func (m *LevelMetrics) Add(u *LevelMetrics) {
|
||||
m.TablesFlushed += u.TablesFlushed
|
||||
m.TablesIngested += u.TablesIngested
|
||||
m.TablesMoved += u.TablesMoved
|
||||
m.MultiLevel.BytesInTop += u.MultiLevel.BytesInTop
|
||||
m.MultiLevel.BytesRead += u.MultiLevel.BytesRead
|
||||
m.MultiLevel.BytesIn += u.MultiLevel.BytesIn
|
||||
m.Additional.BytesWrittenDataBlocks += u.Additional.BytesWrittenDataBlocks
|
||||
m.Additional.BytesWrittenValueBlocks += u.Additional.BytesWrittenValueBlocks
|
||||
m.Additional.ValueBlocksSize += u.Additional.ValueBlocksSize
|
||||
@@ -121,32 +141,6 @@ func (m *LevelMetrics) WriteAmp() float64 {
|
||||
return float64(m.BytesFlushed+m.BytesCompacted) / float64(m.BytesIn)
|
||||
}
|
||||
|
||||
// format generates a string of the receiver's metrics, formatting it into the
|
||||
// supplied buffer.
|
||||
func (m *LevelMetrics) format(
|
||||
w redact.SafePrinter, score redact.SafeValue, includeValueBlocksSize bool,
|
||||
) {
|
||||
w.Printf("%9d %7s %7s %7s %7s %7s %7s %7s %7s %7s %7s %7d %7.1f",
|
||||
redact.Safe(m.NumFiles),
|
||||
humanize.IEC.Int64(m.Size),
|
||||
score,
|
||||
humanize.IEC.Uint64(m.BytesIn),
|
||||
humanize.IEC.Uint64(m.BytesIngested),
|
||||
humanize.SI.Uint64(m.TablesIngested),
|
||||
humanize.IEC.Uint64(m.BytesMoved),
|
||||
humanize.SI.Uint64(m.TablesMoved),
|
||||
humanize.IEC.Uint64(m.BytesFlushed+m.BytesCompacted),
|
||||
humanize.SI.Uint64(m.TablesFlushed+m.TablesCompacted),
|
||||
humanize.IEC.Uint64(m.BytesRead),
|
||||
redact.Safe(m.Sublevels),
|
||||
redact.Safe(m.WriteAmp()))
|
||||
if includeValueBlocksSize {
|
||||
w.Printf(" %7s\n", humanize.IEC.Uint64(m.Additional.ValueBlocksSize))
|
||||
} else {
|
||||
w.SafeString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics holds metrics for various subsystems of the DB such as the Cache,
|
||||
// Compactions, WAL, and per-Level metrics.
|
||||
//
|
||||
@@ -158,14 +152,15 @@ type Metrics struct {
|
||||
|
||||
Compact struct {
|
||||
// The total number of compactions, and per-compaction type counts.
|
||||
Count int64
|
||||
DefaultCount int64
|
||||
DeleteOnlyCount int64
|
||||
ElisionOnlyCount int64
|
||||
MoveCount int64
|
||||
ReadCount int64
|
||||
RewriteCount int64
|
||||
MultiLevelCount int64
|
||||
Count int64
|
||||
DefaultCount int64
|
||||
DeleteOnlyCount int64
|
||||
ElisionOnlyCount int64
|
||||
MoveCount int64
|
||||
ReadCount int64
|
||||
RewriteCount int64
|
||||
MultiLevelCount int64
|
||||
CounterLevelCount int64
|
||||
// An estimate of the number of bytes that need to be compacted for the LSM
|
||||
// to reach a stable state.
|
||||
EstimatedDebt uint64
|
||||
@@ -184,6 +179,11 @@ type Metrics struct {
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
Ingest struct {
|
||||
// The total number of ingestions
|
||||
Count uint64
|
||||
}
|
||||
|
||||
Flush struct {
|
||||
// The total number of flushes.
|
||||
Count int64
|
||||
@@ -213,7 +213,10 @@ type Metrics struct {
|
||||
// The count of memtables.
|
||||
Count int64
|
||||
// The number of bytes present in zombie memtables which are no longer
|
||||
// referenced by the current DB state but are still in use by an iterator.
|
||||
// referenced by the current DB state. An unbounded number of memtables
|
||||
// may be zombie if they're still in use by an iterator. One additional
|
||||
// memtable may be zombie if it's no longer in use and waiting to be
|
||||
// recycled.
|
||||
ZombieSize uint64
|
||||
// The count of zombie memtables.
|
||||
ZombieCount int64
|
||||
@@ -225,6 +228,9 @@ type Metrics struct {
|
||||
// The approximate count of internal tombstones (DEL, SINGLEDEL and
|
||||
// RANGEDEL key kinds) within the database.
|
||||
TombstoneCount uint64
|
||||
// A cumulative total number of missized DELSIZED keys encountered by
|
||||
// compactions since the database was opened.
|
||||
MissizedTombstonesCount uint64
|
||||
}
|
||||
|
||||
Snapshots struct {
|
||||
@@ -253,6 +259,10 @@ type Metrics struct {
|
||||
ZombieSize uint64
|
||||
// The count of zombie tables.
|
||||
ZombieCount int64
|
||||
// The count of the backing sstables.
|
||||
BackingTableCount uint64
|
||||
// The sum of the sizes of the all of the backing sstables.
|
||||
BackingTableSize uint64
|
||||
}
|
||||
|
||||
TableCache CacheMetrics
|
||||
@@ -286,6 +296,8 @@ type Metrics struct {
|
||||
record.LogWriterMetrics
|
||||
}
|
||||
|
||||
SecondaryCacheMetrics SecondaryCacheMetrics
|
||||
|
||||
private struct {
|
||||
optionsFileSize uint64
|
||||
manifestFileSize uint64
|
||||
@@ -299,6 +311,13 @@ var (
|
||||
prometheus.LinearBuckets(0.0, float64(time.Microsecond*100), 50),
|
||||
prometheus.ExponentialBucketsRange(float64(time.Millisecond*5), float64(10*time.Second), 50)...,
|
||||
)
|
||||
|
||||
// SecondaryCacheIOBuckets exported to enable exporting from package pebble to
|
||||
// enable exporting metrics with below buckets in CRDB.
|
||||
SecondaryCacheIOBuckets = sharedcache.IOBuckets
|
||||
// SecondaryCacheChannelWriteBuckets exported to enable exporting from package
|
||||
// pebble to enable exporting metrics with below buckets in CRDB.
|
||||
SecondaryCacheChannelWriteBuckets = sharedcache.ChannelWriteBuckets
|
||||
)
|
||||
|
||||
// DiskSpaceUsage returns the total disk space used by the database in bytes,
|
||||
@@ -318,12 +337,25 @@ func (m *Metrics) DiskSpaceUsage() uint64 {
|
||||
return usageBytes
|
||||
}
|
||||
|
||||
func (m *Metrics) levelSizes() [numLevels]int64 {
|
||||
var sizes [numLevels]int64
|
||||
for i := 0; i < len(sizes); i++ {
|
||||
sizes[i] = m.Levels[i].Size
|
||||
// NumVirtual is the number of virtual sstables in the latest version
|
||||
// summed over every level in the lsm.
|
||||
func (m *Metrics) NumVirtual() uint64 {
|
||||
var n uint64
|
||||
for _, level := range m.Levels {
|
||||
n += level.NumVirtualFiles
|
||||
}
|
||||
return sizes
|
||||
return n
|
||||
}
|
||||
|
||||
// VirtualSize is the sum of the sizes of the virtual sstables in the
|
||||
// latest version. BackingTableSize - VirtualSize gives an estimate for
|
||||
// the space amplification caused by not compacting virtual sstables.
|
||||
func (m *Metrics) VirtualSize() uint64 {
|
||||
var size uint64
|
||||
for _, level := range m.Levels {
|
||||
size += level.VirtualSize
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// ReadAmp returns the current read amplification of the database.
|
||||
@@ -354,59 +386,34 @@ func (m *Metrics) Total() LevelMetrics {
|
||||
return total
|
||||
}
|
||||
|
||||
const notApplicable = redact.SafeString("-")
|
||||
|
||||
func (m *Metrics) formatWAL(w redact.SafePrinter) {
|
||||
var writeAmp float64
|
||||
if m.WAL.BytesIn > 0 {
|
||||
writeAmp = float64(m.WAL.BytesWritten) / float64(m.WAL.BytesIn)
|
||||
}
|
||||
w.Printf(" WAL %9d %7s %7s %7s %7s %7s %7s %7s %7s %7s %7s %7s %7.1f\n",
|
||||
redact.Safe(m.WAL.Files),
|
||||
humanize.Uint64(m.WAL.Size),
|
||||
notApplicable,
|
||||
humanize.Uint64(m.WAL.BytesIn),
|
||||
notApplicable,
|
||||
notApplicable,
|
||||
notApplicable,
|
||||
notApplicable,
|
||||
humanize.Uint64(m.WAL.BytesWritten),
|
||||
notApplicable,
|
||||
notApplicable,
|
||||
notApplicable,
|
||||
redact.Safe(writeAmp))
|
||||
}
|
||||
|
||||
// String pretty-prints the metrics, showing a line for the WAL, a line per-level, and
|
||||
// a total:
|
||||
// String pretty-prints the metrics as below:
|
||||
//
|
||||
// __level_____count____size___score______in__ingest(sz_cnt)____move(sz_cnt)___write(sz_cnt)____read___r-amp___w-amp
|
||||
// WAL 1 28 B - 17 B - - - - 56 B - - - 3.3
|
||||
// 0 1 770 B 0.25 28 B 0 B 0 0 B 0 770 B 1 0 B 1 27.5
|
||||
// 1 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
||||
// 2 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
||||
// 3 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
||||
// 4 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
||||
// 5 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
||||
// 6 0 0 B - 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
||||
// total 1 770 B - 56 B 0 B 0 0 B 0 826 B 1 0 B 1 14.8
|
||||
// flush 1 0 B 0 0 (ingest = ingested-as-flushable, move = tables-ingested)
|
||||
// compact 0 0 B 0 B 0 (size == estimated-debt, score = in-progress-bytes, in = num-in-progress)
|
||||
// ctype 0 0 0 0 0 0 0 (default, delete, elision, move, read, rewrite, multi-level)
|
||||
// memtbl 1 256 K
|
||||
// zmemtbl 1 256 K
|
||||
// ztbl 0 0 B
|
||||
// bcache 4 697 B 0.0% (score == hit-rate)
|
||||
// tcache 1 696 B 0.0% (score == hit-rate)
|
||||
// snaps 0 - 0 (score == earliest seq num)
|
||||
// titers 1
|
||||
// filter - - 0.0% (score == utility)
|
||||
//
|
||||
// The WAL "in" metric is the size of the batches written to the WAL. The WAL
|
||||
// "write" metric is the size of the physical data written to the WAL which
|
||||
// includes record fragment overhead. Write amplification is computed as
|
||||
// bytes-written / bytes-in, except for the total row where bytes-in is
|
||||
// replaced with WAL-bytes-written + bytes-ingested.
|
||||
// | | | | ingested | moved | written | | amp
|
||||
// level | tables size val-bl vtables | score | in | tables size | tables size | tables size | read | r w
|
||||
// ------+-----------------------------+-------+-------+--------------+--------------+--------------+-------+---------
|
||||
// 0 | 101 102B 0B 0 | 103.0 | 104B | 112 104B | 113 106B | 221 217B | 107B | 1 2.1
|
||||
// 1 | 201 202B 0B 0 | 203.0 | 204B | 212 204B | 213 206B | 421 417B | 207B | 2 2.0
|
||||
// 2 | 301 302B 0B 0 | 303.0 | 304B | 312 304B | 313 306B | 621 617B | 307B | 3 2.0
|
||||
// 3 | 401 402B 0B 0 | 403.0 | 404B | 412 404B | 413 406B | 821 817B | 407B | 4 2.0
|
||||
// 4 | 501 502B 0B 0 | 503.0 | 504B | 512 504B | 513 506B | 1.0K 1017B | 507B | 5 2.0
|
||||
// 5 | 601 602B 0B 0 | 603.0 | 604B | 612 604B | 613 606B | 1.2K 1.2KB | 607B | 6 2.0
|
||||
// 6 | 701 702B 0B 0 | - | 704B | 712 704B | 713 706B | 1.4K 1.4KB | 707B | 7 2.0
|
||||
// total | 2.8K 2.7KB 0B 0 | - | 2.8KB | 2.9K 2.8KB | 2.9K 2.8KB | 5.7K 8.4KB | 2.8KB | 28 3.0
|
||||
// -------------------------------------------------------------------------------------------------------------------
|
||||
// WAL: 22 files (24B) in: 25B written: 26B (4% overhead)
|
||||
// Flushes: 8
|
||||
// Compactions: 5 estimated debt: 6B in progress: 2 (7B)
|
||||
// default: 27 delete: 28 elision: 29 move: 30 read: 31 rewrite: 32 multi-level: 33
|
||||
// MemTables: 12 (11B) zombie: 14 (13B)
|
||||
// Zombie tables: 16 (15B)
|
||||
// Backing tables: 0 (0B)
|
||||
// Block cache: 2 entries (1B) hit rate: 42.9%
|
||||
// Table cache: 18 entries (17B) hit rate: 48.7%
|
||||
// Secondary cache: 40 entries (40B) hit rate: 49.9%
|
||||
// Snapshots: 4 earliest seq num: 1024
|
||||
// Table iters: 21
|
||||
// Filter utility: 47.4%
|
||||
// Ingestions: 27 as flushable: 36 (34B in 35 tables)
|
||||
func (m *Metrics) String() string {
|
||||
return redact.StringWithoutMarkers(m)
|
||||
}
|
||||
@@ -427,29 +434,84 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
// width specifiers. When the issue is fixed, we can convert these to
|
||||
// RedactableStrings. https://github.com/cockroachdb/redact/issues/17
|
||||
|
||||
haveValueBlocks := false
|
||||
var valueBlocksHeading redact.SafeString
|
||||
for level := 0; level < numLevels; level++ {
|
||||
if m.Levels[level].Additional.ValueBlocksSize > 0 {
|
||||
haveValueBlocks = true
|
||||
valueBlocksHeading = "__val-bl"
|
||||
break
|
||||
multiExists := m.Compact.MultiLevelCount > 0
|
||||
appendIfMulti := func(line redact.SafeString) {
|
||||
if multiExists {
|
||||
w.SafeString(line)
|
||||
}
|
||||
}
|
||||
newline := func() {
|
||||
w.SafeString("\n")
|
||||
}
|
||||
|
||||
w.SafeString(" | | | | ingested | moved | written | | amp")
|
||||
appendIfMulti(" | multilevel")
|
||||
newline()
|
||||
w.SafeString("level | tables size val-bl vtables | score | in | tables size | tables size | tables size | read | r w")
|
||||
appendIfMulti(" | top in read")
|
||||
newline()
|
||||
w.SafeString("------+-----------------------------+-------+-------+--------------+--------------+--------------+-------+---------")
|
||||
appendIfMulti("-+------------------")
|
||||
newline()
|
||||
|
||||
// formatRow prints out a row of the table.
|
||||
formatRow := func(m *LevelMetrics, score float64) {
|
||||
scoreStr := "-"
|
||||
if !math.IsNaN(score) {
|
||||
// Try to keep the string no longer than 5 characters.
|
||||
switch {
|
||||
case score < 99.995:
|
||||
scoreStr = fmt.Sprintf("%.2f", score)
|
||||
case score < 999.95:
|
||||
scoreStr = fmt.Sprintf("%.1f", score)
|
||||
default:
|
||||
scoreStr = fmt.Sprintf("%.0f", score)
|
||||
}
|
||||
}
|
||||
var wampStr string
|
||||
if wamp := m.WriteAmp(); wamp > 99.5 {
|
||||
wampStr = fmt.Sprintf("%.0f", wamp)
|
||||
} else {
|
||||
wampStr = fmt.Sprintf("%.1f", wamp)
|
||||
}
|
||||
|
||||
w.Printf("| %5s %6s %6s %7s | %5s | %5s | %5s %6s | %5s %6s | %5s %6s | %5s | %3d %4s",
|
||||
humanize.Count.Int64(m.NumFiles),
|
||||
humanize.Bytes.Int64(m.Size),
|
||||
humanize.Bytes.Uint64(m.Additional.ValueBlocksSize),
|
||||
humanize.Count.Uint64(m.NumVirtualFiles),
|
||||
redact.Safe(scoreStr),
|
||||
humanize.Bytes.Uint64(m.BytesIn),
|
||||
humanize.Count.Uint64(m.TablesIngested),
|
||||
humanize.Bytes.Uint64(m.BytesIngested),
|
||||
humanize.Count.Uint64(m.TablesMoved),
|
||||
humanize.Bytes.Uint64(m.BytesMoved),
|
||||
humanize.Count.Uint64(m.TablesFlushed+m.TablesCompacted),
|
||||
humanize.Bytes.Uint64(m.BytesFlushed+m.BytesCompacted),
|
||||
humanize.Bytes.Uint64(m.BytesRead),
|
||||
redact.Safe(m.Sublevels),
|
||||
redact.Safe(wampStr))
|
||||
|
||||
if multiExists {
|
||||
w.Printf(" | %5s %5s %5s",
|
||||
humanize.Bytes.Uint64(m.MultiLevel.BytesInTop),
|
||||
humanize.Bytes.Uint64(m.MultiLevel.BytesIn),
|
||||
humanize.Bytes.Uint64(m.MultiLevel.BytesRead))
|
||||
}
|
||||
newline()
|
||||
}
|
||||
|
||||
var total LevelMetrics
|
||||
w.Printf("__level_____count____size___score______in__ingest(sz_cnt)"+
|
||||
"____move(sz_cnt)___write(sz_cnt)____read___r-amp___w-amp%s\n", valueBlocksHeading)
|
||||
m.formatWAL(w)
|
||||
for level := 0; level < numLevels; level++ {
|
||||
l := &m.Levels[level]
|
||||
w.Printf("%7d ", redact.Safe(level))
|
||||
w.Printf("%5d ", redact.Safe(level))
|
||||
|
||||
// Format the score.
|
||||
var score redact.SafeValue = notApplicable
|
||||
score := math.NaN()
|
||||
if level < numLevels-1 {
|
||||
score = redact.Safe(fmt.Sprintf("%0.2f", l.Score))
|
||||
score = l.Score
|
||||
}
|
||||
l.format(w, score, haveValueBlocks)
|
||||
formatRow(l, score)
|
||||
total.Add(l)
|
||||
total.Sublevels += l.Sublevels
|
||||
}
|
||||
@@ -459,57 +521,105 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||
// the bytes written to the log and bytes written externally and then
|
||||
// ingested.
|
||||
total.BytesFlushed += total.BytesIn
|
||||
w.SafeString(" total ")
|
||||
total.format(w, notApplicable, haveValueBlocks)
|
||||
w.SafeString("total ")
|
||||
formatRow(&total, math.NaN())
|
||||
|
||||
w.Printf(" flush %9d %31s %7d %7d %s\n",
|
||||
redact.Safe(m.Flush.Count),
|
||||
humanize.IEC.Uint64(m.Flush.AsIngestBytes),
|
||||
redact.Safe(m.Flush.AsIngestTableCount),
|
||||
redact.Safe(m.Flush.AsIngestCount),
|
||||
redact.SafeString(`(ingest = tables-ingested, move = ingested-as-flushable)`))
|
||||
w.Printf("compact %9d %7s %7s %7d %s %s\n",
|
||||
w.SafeString("-------------------------------------------------------------------------------------------------------------------")
|
||||
appendIfMulti("--------------------")
|
||||
newline()
|
||||
w.Printf("WAL: %d files (%s) in: %s written: %s (%.0f%% overhead)\n",
|
||||
redact.Safe(m.WAL.Files),
|
||||
humanize.Bytes.Uint64(m.WAL.Size),
|
||||
humanize.Bytes.Uint64(m.WAL.BytesIn),
|
||||
humanize.Bytes.Uint64(m.WAL.BytesWritten),
|
||||
redact.Safe(percent(int64(m.WAL.BytesWritten)-int64(m.WAL.BytesIn), int64(m.WAL.BytesIn))))
|
||||
|
||||
w.Printf("Flushes: %d\n", redact.Safe(m.Flush.Count))
|
||||
|
||||
w.Printf("Compactions: %d estimated debt: %s in progress: %d (%s)\n",
|
||||
redact.Safe(m.Compact.Count),
|
||||
humanize.IEC.Uint64(m.Compact.EstimatedDebt),
|
||||
humanize.IEC.Int64(m.Compact.InProgressBytes),
|
||||
humanize.Bytes.Uint64(m.Compact.EstimatedDebt),
|
||||
redact.Safe(m.Compact.NumInProgress),
|
||||
redact.SafeString(strings.Repeat(" ", 24)),
|
||||
redact.SafeString(`(size == estimated-debt, score = in-progress-bytes, in = num-in-progress)`))
|
||||
w.Printf(" ctype %9d %7d %7d %7d %7d %7d %7d %s\n",
|
||||
humanize.Bytes.Int64(m.Compact.InProgressBytes))
|
||||
|
||||
w.Printf(" default: %d delete: %d elision: %d move: %d read: %d rewrite: %d multi-level: %d\n",
|
||||
redact.Safe(m.Compact.DefaultCount),
|
||||
redact.Safe(m.Compact.DeleteOnlyCount),
|
||||
redact.Safe(m.Compact.ElisionOnlyCount),
|
||||
redact.Safe(m.Compact.MoveCount),
|
||||
redact.Safe(m.Compact.ReadCount),
|
||||
redact.Safe(m.Compact.RewriteCount),
|
||||
redact.Safe(m.Compact.MultiLevelCount),
|
||||
redact.SafeString(`(default, delete, elision, move, read, rewrite, multi-level)`))
|
||||
w.Printf(" memtbl %9d %7s\n",
|
||||
redact.Safe(m.Compact.MultiLevelCount))
|
||||
|
||||
w.Printf("MemTables: %d (%s) zombie: %d (%s)\n",
|
||||
redact.Safe(m.MemTable.Count),
|
||||
humanize.IEC.Uint64(m.MemTable.Size))
|
||||
w.Printf("zmemtbl %9d %7s\n",
|
||||
humanize.Bytes.Uint64(m.MemTable.Size),
|
||||
redact.Safe(m.MemTable.ZombieCount),
|
||||
humanize.IEC.Uint64(m.MemTable.ZombieSize))
|
||||
w.Printf(" ztbl %9d %7s\n",
|
||||
humanize.Bytes.Uint64(m.MemTable.ZombieSize))
|
||||
|
||||
w.Printf("Zombie tables: %d (%s)\n",
|
||||
redact.Safe(m.Table.ZombieCount),
|
||||
humanize.IEC.Uint64(m.Table.ZombieSize))
|
||||
formatCacheMetrics(w, &m.BlockCache, "bcache")
|
||||
formatCacheMetrics(w, &m.TableCache, "tcache")
|
||||
w.Printf(" snaps %9d %7s %7d (score == earliest seq num)\n",
|
||||
humanize.Bytes.Uint64(m.Table.ZombieSize))
|
||||
|
||||
w.Printf("Backing tables: %d (%s)\n",
|
||||
redact.Safe(m.Table.BackingTableCount),
|
||||
humanize.Bytes.Uint64(m.Table.BackingTableSize))
|
||||
w.Printf("Virtual tables: %d (%s)\n",
|
||||
redact.Safe(m.NumVirtual()),
|
||||
humanize.Bytes.Uint64(m.VirtualSize()))
|
||||
|
||||
formatCacheMetrics := func(m *CacheMetrics, name redact.SafeString) {
|
||||
w.Printf("%s: %s entries (%s) hit rate: %.1f%%\n",
|
||||
name,
|
||||
humanize.Count.Int64(m.Count),
|
||||
humanize.Bytes.Int64(m.Size),
|
||||
redact.Safe(hitRate(m.Hits, m.Misses)))
|
||||
}
|
||||
formatCacheMetrics(&m.BlockCache, "Block cache")
|
||||
formatCacheMetrics(&m.TableCache, "Table cache")
|
||||
|
||||
formatSharedCacheMetrics := func(w redact.SafePrinter, m *SecondaryCacheMetrics, name redact.SafeString) {
|
||||
w.Printf("%s: %s entries (%s) hit rate: %.1f%%\n",
|
||||
name,
|
||||
humanize.Count.Int64(m.Count),
|
||||
humanize.Bytes.Int64(m.Size),
|
||||
redact.Safe(hitRate(m.ReadsWithFullHit, m.ReadsWithPartialHit+m.ReadsWithNoHit)))
|
||||
}
|
||||
formatSharedCacheMetrics(w, &m.SecondaryCacheMetrics, "Secondary cache")
|
||||
|
||||
w.Printf("Snapshots: %d earliest seq num: %d\n",
|
||||
redact.Safe(m.Snapshots.Count),
|
||||
notApplicable,
|
||||
redact.Safe(m.Snapshots.EarliestSeqNum))
|
||||
w.Printf(" titers %9d\n", redact.Safe(m.TableIters))
|
||||
w.Printf(" filter %9s %7s %6.1f%% (score == utility)\n",
|
||||
notApplicable,
|
||||
notApplicable,
|
||||
redact.Safe(hitRate(m.Filter.Hits, m.Filter.Misses)))
|
||||
|
||||
w.Printf("Table iters: %d\n", redact.Safe(m.TableIters))
|
||||
w.Printf("Filter utility: %.1f%%\n", redact.Safe(hitRate(m.Filter.Hits, m.Filter.Misses)))
|
||||
w.Printf("Ingestions: %d as flushable: %d (%s in %d tables)\n",
|
||||
redact.Safe(m.Ingest.Count),
|
||||
redact.Safe(m.Flush.AsIngestCount),
|
||||
humanize.Bytes.Uint64(m.Flush.AsIngestBytes),
|
||||
redact.Safe(m.Flush.AsIngestTableCount))
|
||||
}
|
||||
|
||||
func hitRate(hits, misses int64) float64 {
|
||||
sum := hits + misses
|
||||
if sum == 0 {
|
||||
return percent(hits, hits+misses)
|
||||
}
|
||||
|
||||
func percent(numerator, denominator int64) float64 {
|
||||
if denominator == 0 {
|
||||
return 0
|
||||
}
|
||||
return 100 * float64(hits) / float64(sum)
|
||||
return 100 * float64(numerator) / float64(denominator)
|
||||
}
|
||||
|
||||
// StringForTests is identical to m.String() on 64-bit platforms. It is used to
|
||||
// provide a platform-independent result for tests.
|
||||
func (m *Metrics) StringForTests() string {
|
||||
mCopy := *m
|
||||
if math.MaxInt == math.MaxInt32 {
|
||||
// This is the difference in Sizeof(sstable.Reader{})) between 64 and 32 bit
|
||||
// platforms.
|
||||
const tableCacheSizeAdjustment = 212
|
||||
mCopy.TableCache.Size += mCopy.TableCache.Count * tableCacheSizeAdjustment
|
||||
}
|
||||
return redact.StringWithoutMarkers(&mCopy)
|
||||
}
|
||||
|
||||
4
vendor/github.com/cockroachdb/pebble/objstorage/noop_readahead.go
generated
vendored
4
vendor/github.com/cockroachdb/pebble/objstorage/noop_readahead.go
generated
vendored
@@ -27,8 +27,8 @@ func (h *NoopReadHandle) ReadAt(ctx context.Context, p []byte, off int64) error
|
||||
// Close is part of the ReadHandle interface.
|
||||
func (*NoopReadHandle) Close() error { return nil }
|
||||
|
||||
// MaxReadahead is part of the ReadHandle interface.
|
||||
func (*NoopReadHandle) MaxReadahead() {}
|
||||
// SetupForCompaction is part of the ReadHandle interface.
|
||||
func (*NoopReadHandle) SetupForCompaction() {}
|
||||
|
||||
// RecordCacheHit is part of the ReadHandle interface.
|
||||
func (*NoopReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {}
|
||||
|
||||
122
vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go
generated
vendored
122
vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go
generated
vendored
@@ -8,7 +8,10 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
)
|
||||
|
||||
@@ -16,6 +19,9 @@ import (
|
||||
type Readable interface {
|
||||
// ReadAt reads len(p) bytes into p starting at offset off.
|
||||
//
|
||||
// Does not return partial results; if off + len(p) is past the end of the
|
||||
// object, an error is returned.
|
||||
//
|
||||
// Clients of ReadAt can execute parallel ReadAt calls on the
|
||||
// same Readable.
|
||||
ReadAt(ctx context.Context, p []byte, off int64) error
|
||||
@@ -39,14 +45,18 @@ type Readable interface {
|
||||
type ReadHandle interface {
|
||||
// ReadAt reads len(p) bytes into p starting at offset off.
|
||||
//
|
||||
// Does not return partial results; if off + len(p) is past the end of the
|
||||
// object, an error is returned.
|
||||
//
|
||||
// Parallel ReadAt calls on the same ReadHandle are not allowed.
|
||||
ReadAt(ctx context.Context, p []byte, off int64) error
|
||||
|
||||
Close() error
|
||||
|
||||
// MaxReadahead configures the implementation to expect large sequential
|
||||
// reads. Used to skip any initial read-ahead ramp-up.
|
||||
MaxReadahead()
|
||||
// SetupForCompaction informs the implementation that the read handle will
|
||||
// be used to read data blocks for a compaction. The implementation can expect
|
||||
// sequential reads, and can decide to not retain data in any caches.
|
||||
SetupForCompaction()
|
||||
|
||||
// RecordCacheHit informs the implementation that we were able to retrieve a
|
||||
// block from cache. This is useful for example when the implementation is
|
||||
@@ -80,21 +90,71 @@ type ObjectMetadata struct {
|
||||
DiskFileNum base.DiskFileNum
|
||||
FileType base.FileType
|
||||
|
||||
// The fields below are only set if the object is on shared storage.
|
||||
Shared struct {
|
||||
// The fields below are only set if the object is on remote storage.
|
||||
Remote struct {
|
||||
// CreatorID identifies the DB instance that originally created the object.
|
||||
//
|
||||
// Only used when CustomObjectName is not set.
|
||||
CreatorID CreatorID
|
||||
// CreatorFileNum is the identifier for the object within the context of the
|
||||
// DB instance that originally created the object.
|
||||
//
|
||||
// Only used when CustomObjectName is not set.
|
||||
CreatorFileNum base.DiskFileNum
|
||||
|
||||
// CustomObjectName (if it is set) overrides the object name that is normally
|
||||
// derived from the CreatorID and CreatorFileNum.
|
||||
CustomObjectName string
|
||||
// CleanupMethod indicates the method for cleaning up unused shared objects.
|
||||
CleanupMethod SharedCleanupMethod
|
||||
// Locator identifies the remote.Storage implementation for this object.
|
||||
Locator remote.Locator
|
||||
// Storage is the remote.Storage object corresponding to the Locator. Used
|
||||
// to avoid lookups in hot paths.
|
||||
Storage remote.Storage
|
||||
}
|
||||
}
|
||||
|
||||
// IsShared returns true if the object is on shared storage.
|
||||
// IsRemote returns true if the object is on remote storage.
|
||||
func (meta *ObjectMetadata) IsRemote() bool {
|
||||
return meta.IsShared() || meta.IsExternal()
|
||||
}
|
||||
|
||||
// IsExternal returns true if the object is on remote storage but is not owned
|
||||
// by any Pebble instances in the cluster.
|
||||
func (meta *ObjectMetadata) IsExternal() bool {
|
||||
return meta.Remote.CustomObjectName != ""
|
||||
}
|
||||
|
||||
// IsShared returns true if the object is on remote storage and is owned by a
|
||||
// Pebble instance in the cluster (potentially shared between multiple
|
||||
// instances).
|
||||
func (meta *ObjectMetadata) IsShared() bool {
|
||||
return meta.Shared.CreatorID.IsSet()
|
||||
return meta.Remote.CreatorID.IsSet()
|
||||
}
|
||||
|
||||
// AssertValid checks that the metadata is sane.
|
||||
func (meta *ObjectMetadata) AssertValid() {
|
||||
if !meta.IsRemote() {
|
||||
// Verify all Remote fields are empty.
|
||||
if meta.Remote != (ObjectMetadata{}).Remote {
|
||||
panic(errors.AssertionFailedf("meta.Remote not empty: %#v", meta.Remote))
|
||||
}
|
||||
} else {
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
if meta.Remote.CreatorID == 0 {
|
||||
panic(errors.AssertionFailedf("CreatorID not set"))
|
||||
}
|
||||
if meta.Remote.CreatorFileNum == base.FileNum(0).DiskFileNum() {
|
||||
panic(errors.AssertionFailedf("CreatorFileNum not set"))
|
||||
}
|
||||
}
|
||||
if meta.Remote.CleanupMethod != SharedNoCleanup && meta.Remote.CleanupMethod != SharedRefTracking {
|
||||
panic(errors.AssertionFailedf("invalid CleanupMethod %d", meta.Remote.CleanupMethod))
|
||||
}
|
||||
if meta.Remote.Storage == nil {
|
||||
panic(errors.AssertionFailedf("Storage not set"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreatorID identifies the DB instance that originally created a shared object.
|
||||
@@ -115,7 +175,7 @@ const (
|
||||
// keep track of references via reference marker objects.
|
||||
SharedRefTracking SharedCleanupMethod = iota
|
||||
|
||||
// SharedNoCleanup is used for shared objects that are managed externally; the
|
||||
// SharedNoCleanup is used for remote objects that are managed externally; the
|
||||
// objstorage provider never deletes such objects.
|
||||
SharedNoCleanup
|
||||
)
|
||||
@@ -148,7 +208,7 @@ type CreateOptions struct {
|
||||
// created by the provider, or existing objects the Provider was informed about
|
||||
// via AddObjects.
|
||||
//
|
||||
// Objects are currently backed by a vfs.File or a shared.Storage object.
|
||||
// Objects are currently backed by a vfs.File or a remote.Storage object.
|
||||
type Provider interface {
|
||||
// OpenForReading opens an existing object.
|
||||
OpenForReading(
|
||||
@@ -203,50 +263,62 @@ type Provider interface {
|
||||
List() []ObjectMetadata
|
||||
|
||||
// SetCreatorID sets the CreatorID which is needed in order to use shared
|
||||
// objects. Shared object usage is disabled until this method is called the
|
||||
// objects. Remote object usage is disabled until this method is called the
|
||||
// first time. Once set, the Creator ID is persisted and cannot change.
|
||||
//
|
||||
// Cannot be called if shared storage is not configured for the provider.
|
||||
SetCreatorID(creatorID CreatorID) error
|
||||
|
||||
// SharedObjectBacking encodes the shared object metadata.
|
||||
SharedObjectBacking(meta *ObjectMetadata) (SharedObjectBackingHandle, error)
|
||||
// IsSharedForeign returns whether this object is owned by a different node.
|
||||
IsSharedForeign(meta ObjectMetadata) bool
|
||||
|
||||
// AttachSharedObjects registers existing shared objects with this provider.
|
||||
AttachSharedObjects(objs []SharedObjectToAttach) ([]ObjectMetadata, error)
|
||||
// RemoteObjectBacking encodes the remote object metadata for the given object.
|
||||
RemoteObjectBacking(meta *ObjectMetadata) (RemoteObjectBackingHandle, error)
|
||||
|
||||
// CreateExternalObjectBacking creates a backing for an existing object with a
|
||||
// custom object name. The object is considered to be managed outside of
|
||||
// Pebble and will never be removed by Pebble.
|
||||
CreateExternalObjectBacking(locator remote.Locator, objName string) (RemoteObjectBacking, error)
|
||||
|
||||
// AttachRemoteObjects registers existing remote objects with this provider.
|
||||
AttachRemoteObjects(objs []RemoteObjectToAttach) ([]ObjectMetadata, error)
|
||||
|
||||
Close() error
|
||||
|
||||
// IsNotExistError indicates whether the error is known to report that a file or
|
||||
// directory does not exist.
|
||||
IsNotExistError(err error) bool
|
||||
|
||||
// Metrics returns metrics about objstorage. Currently, it only returns metrics
|
||||
// about the shared cache.
|
||||
Metrics() sharedcache.Metrics
|
||||
}
|
||||
|
||||
// SharedObjectBacking encodes the metadata necessary to incorporate a shared
|
||||
// RemoteObjectBacking encodes the metadata necessary to incorporate a shared
|
||||
// object into a different Pebble instance. The encoding is specific to a given
|
||||
// Provider implementation.
|
||||
type SharedObjectBacking []byte
|
||||
type RemoteObjectBacking []byte
|
||||
|
||||
// SharedObjectBackingHandle is a container for a SharedObjectBacking which
|
||||
// RemoteObjectBackingHandle is a container for a RemoteObjectBacking which
|
||||
// ensures that the backing stays valid. A backing can otherwise become invalid
|
||||
// if this provider unrefs the shared object. The SharedObjectBackingHandle
|
||||
// if this provider unrefs the shared object. The RemoteObjectBackingHandle
|
||||
// delays any unref until Close.
|
||||
type SharedObjectBackingHandle interface {
|
||||
type RemoteObjectBackingHandle interface {
|
||||
// Get returns the backing. The backing is only guaranteed to be valid until
|
||||
// Close is called (or until the Provider is closed). If Close was already
|
||||
// called, returns an error.
|
||||
Get() (SharedObjectBacking, error)
|
||||
Get() (RemoteObjectBacking, error)
|
||||
Close()
|
||||
}
|
||||
|
||||
// SharedObjectToAttach contains the arguments needed to attach an existing shared object.
|
||||
type SharedObjectToAttach struct {
|
||||
// RemoteObjectToAttach contains the arguments needed to attach an existing remote object.
|
||||
type RemoteObjectToAttach struct {
|
||||
// FileNum is the file number that will be used to refer to this object (in
|
||||
// the context of this instance).
|
||||
FileNum base.DiskFileNum
|
||||
FileType base.FileType
|
||||
// Backing contains the metadata for the shared object backing (normally
|
||||
// Backing contains the metadata for the remote object backing (normally
|
||||
// generated from a different instance, but using the same Provider
|
||||
// implementation).
|
||||
Backing SharedObjectBacking
|
||||
Backing RemoteObjectBacking
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ const (
|
||||
// RecordCacheHitOp happens when a read is satisfied from the block cache. See
|
||||
// objstorage.ReadHandle.RecordCacheHit().
|
||||
RecordCacheHitOp
|
||||
// MaxReadaheadOp is a "meta operation" that configures a read handle for
|
||||
// large sequential reads. See objstorage.ReadHandle.MaxReadahead().
|
||||
MaxReadaheadOp
|
||||
// SetupForCompactionOp is a "meta operation" that configures a read handle
|
||||
// for large sequential reads. See objstorage.ReadHandle.SetupForCompaction().
|
||||
SetupForCompactionOp
|
||||
)
|
||||
|
||||
// Reason indicates the higher-level context of the operation.
|
||||
|
||||
@@ -209,14 +209,14 @@ func (rh *readHandle) Close() error {
|
||||
return rh.rh.Close()
|
||||
}
|
||||
|
||||
// MaxReadahead is part of the objstorage.ReadHandle interface.
|
||||
func (rh *readHandle) MaxReadahead() {
|
||||
// SetupForCompaction is part of the objstorage.ReadHandle interface.
|
||||
func (rh *readHandle) SetupForCompaction() {
|
||||
rh.g.add(context.Background(), Event{
|
||||
Op: MaxReadaheadOp,
|
||||
Op: SetupForCompactionOp,
|
||||
FileNum: rh.fileNum,
|
||||
HandleID: rh.handleID,
|
||||
})
|
||||
rh.rh.MaxReadahead()
|
||||
rh.rh.SetupForCompaction()
|
||||
}
|
||||
|
||||
// RecordCacheHit is part of the objstorage.ReadHandle interface.
|
||||
|
||||
198
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/provider.go
generated
vendored
198
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/provider.go
generated
vendored
@@ -17,8 +17,9 @@ import (
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/shared"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
)
|
||||
|
||||
@@ -30,18 +31,20 @@ type provider struct {
|
||||
|
||||
tracer *objiotracing.Tracer
|
||||
|
||||
shared sharedSubsystem
|
||||
remote remoteSubsystem
|
||||
|
||||
mu struct {
|
||||
sync.RWMutex
|
||||
|
||||
shared struct {
|
||||
// catalogBatch accumulates shared object creations and deletions until
|
||||
remote struct {
|
||||
// catalogBatch accumulates remote object creations and deletions until
|
||||
// Sync is called.
|
||||
catalogBatch sharedobjcat.Batch
|
||||
catalogBatch remoteobjcat.Batch
|
||||
|
||||
storageObjects map[remote.Locator]remote.Storage
|
||||
}
|
||||
|
||||
// localObjectsChanged is set if non-shared objects were created or deleted
|
||||
// localObjectsChanged is set if non-remote objects were created or deleted
|
||||
// but Sync was not yet called.
|
||||
localObjectsChanged bool
|
||||
|
||||
@@ -89,14 +92,92 @@ type Settings struct {
|
||||
// out a large chunk of dirty filesystem buffers.
|
||||
BytesPerSync int
|
||||
|
||||
// Fields here are set only if the provider is to support shared objects
|
||||
// Local contains fields that are only relevant for files stored on the local
|
||||
// filesystem.
|
||||
Local struct {
|
||||
// TODO(radu): move FSCleaner, NoSyncOnClose, BytesPerSync here.
|
||||
|
||||
// ReadaheadConfigFn is a function used to retrieve the current readahead
|
||||
// mode. This function is run whenever a local object is open for reading.
|
||||
// If it is nil, DefaultReadaheadConfig is used.
|
||||
ReadaheadConfigFn func() ReadaheadConfig
|
||||
}
|
||||
|
||||
// Fields here are set only if the provider is to support remote objects
|
||||
// (experimental).
|
||||
Shared struct {
|
||||
Storage shared.Storage
|
||||
Remote struct {
|
||||
StorageFactory remote.StorageFactory
|
||||
|
||||
// If CreateOnShared is non-zero, sstables are created on remote storage using
|
||||
// the CreateOnSharedLocator (when the PreferSharedStorage create option is
|
||||
// true).
|
||||
CreateOnShared remote.CreateOnSharedStrategy
|
||||
CreateOnSharedLocator remote.Locator
|
||||
|
||||
// CacheSizeBytes is the size of the on-disk block cache for objects
|
||||
// on remote storage. If it is 0, no cache is used.
|
||||
CacheSizeBytes int64
|
||||
|
||||
// CacheBlockSize is the block size of the cache; if 0, the default of 32KB is used.
|
||||
CacheBlockSize int
|
||||
|
||||
// ShardingBlockSize is the size of a shard block. The cache is split into contiguous
|
||||
// ShardingBlockSize units. The units are distributed across multiple independent shards
|
||||
// of the cache, via a hash(offset) modulo num shards operation. The cache replacement
|
||||
// policies operate at the level of shard, not whole cache. This is done to reduce lock
|
||||
// contention.
|
||||
//
|
||||
// If ShardingBlockSize is 0, the default of 1 MB is used.
|
||||
ShardingBlockSize int64
|
||||
|
||||
// The number of independent shards the cache leverages. Each shard is the same size,
|
||||
// and a hash of filenum & offset map a read to a certain shard. If set to 0,
|
||||
// 2*runtime.GOMAXPROCS is used as the shard count.
|
||||
CacheShardCount int
|
||||
|
||||
// TODO(radu): allow the cache to live on another FS/location (e.g. to use
|
||||
// instance-local SSD).
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultSettings initializes default settings (with no shared storage),
|
||||
// ReadaheadConfig controls the use of read-ahead.
|
||||
type ReadaheadConfig struct {
|
||||
// Informed is the type of read-ahead for operations that are known to read a
|
||||
// large consecutive chunk of a file.
|
||||
Informed ReadaheadMode
|
||||
|
||||
// Speculative is the type of read-ahead used automatically, when consecutive
|
||||
// reads are detected.
|
||||
Speculative ReadaheadMode
|
||||
}
|
||||
|
||||
// DefaultReadaheadConfig is the readahead config used when ReadaheadConfigFn is
|
||||
// not specified.
|
||||
var DefaultReadaheadConfig = ReadaheadConfig{
|
||||
Informed: FadviseSequential,
|
||||
Speculative: FadviseSequential,
|
||||
}
|
||||
|
||||
// ReadaheadMode indicates the type of read-ahead to use, either for informed
|
||||
// read-ahead (e.g. compactions) or speculative read-ahead.
|
||||
type ReadaheadMode uint8
|
||||
|
||||
const (
|
||||
// NoReadahead disables readahead altogether.
|
||||
NoReadahead ReadaheadMode = iota
|
||||
|
||||
// SysReadahead enables the use of SYS_READAHEAD call to prefetch data.
|
||||
// The prefetch window grows dynamically as consecutive writes are detected.
|
||||
SysReadahead
|
||||
|
||||
// FadviseSequential enables to use of FADV_SEQUENTIAL. For informed
|
||||
// read-ahead, FADV_SEQUENTIAL is used from the beginning. For speculative
|
||||
// read-ahead SYS_READAHEAD is first used until the window reaches the maximum
|
||||
// size, then we siwtch to FADV_SEQUENTIAL.
|
||||
FadviseSequential
|
||||
)
|
||||
|
||||
// DefaultSettings initializes default settings (with no remote storage),
|
||||
// suitable for tests and tools.
|
||||
func DefaultSettings(fs vfs.FS, dirName string) Settings {
|
||||
return Settings{
|
||||
@@ -111,7 +192,13 @@ func DefaultSettings(fs vfs.FS, dirName string) Settings {
|
||||
|
||||
// Open creates the provider.
|
||||
func Open(settings Settings) (objstorage.Provider, error) {
|
||||
return open(settings)
|
||||
// Note: we can't just `return open(settings)` because in an error case we
|
||||
// would return (*provider)(nil) which is not objstorage.Provider(nil).
|
||||
p, err := open(settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func open(settings Settings) (p *provider, _ error) {
|
||||
@@ -142,8 +229,8 @@ func open(settings Settings) (p *provider, _ error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize shared subsystem (if configured) and add shared objects.
|
||||
if err := p.sharedInit(); err != nil {
|
||||
// Initialize remote subsystem (if configured) and add remote objects.
|
||||
if err := p.remoteInit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -152,9 +239,9 @@ func open(settings Settings) (p *provider, _ error) {
|
||||
|
||||
// Close is part of the objstorage.Provider interface.
|
||||
func (p *provider) Close() error {
|
||||
var err error
|
||||
err := p.sharedClose()
|
||||
if p.fsDir != nil {
|
||||
err = p.fsDir.Close()
|
||||
err = firstError(err, p.fsDir.Close())
|
||||
p.fsDir = nil
|
||||
}
|
||||
if objiotracing.Enabled {
|
||||
@@ -182,10 +269,14 @@ func (p *provider) OpenForReading(
|
||||
}
|
||||
|
||||
var r objstorage.Readable
|
||||
if !meta.IsShared() {
|
||||
if !meta.IsRemote() {
|
||||
r, err = p.vfsOpenForReading(ctx, fileType, fileNum, opts)
|
||||
} else {
|
||||
r, err = p.sharedOpenForReading(ctx, meta, opts)
|
||||
r, err = p.remoteOpenForReading(ctx, meta, opts)
|
||||
if err != nil && p.isNotExistError(meta, err) {
|
||||
// Wrap the error so that IsNotExistError functions properly.
|
||||
err = errors.Mark(err, os.ErrNotExist)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -206,8 +297,8 @@ func (p *provider) Create(
|
||||
fileNum base.DiskFileNum,
|
||||
opts objstorage.CreateOptions,
|
||||
) (w objstorage.Writable, meta objstorage.ObjectMetadata, err error) {
|
||||
if opts.PreferSharedStorage && p.st.Shared.Storage != nil {
|
||||
w, meta, err = p.sharedCreate(ctx, fileType, fileNum, opts)
|
||||
if opts.PreferSharedStorage && p.st.Remote.CreateOnShared != remote.CreateOnSharedNone {
|
||||
w, meta, err = p.sharedCreate(ctx, fileType, fileNum, p.st.Remote.CreateOnSharedLocator, opts)
|
||||
} else {
|
||||
w, meta, err = p.vfsCreate(ctx, fileType, fileNum)
|
||||
}
|
||||
@@ -224,8 +315,8 @@ func (p *provider) Create(
|
||||
|
||||
// Remove removes an object.
|
||||
//
|
||||
// Note that if the object is shared, the object is only (conceptually) removed
|
||||
// from this provider. If other providers have references on the shared object,
|
||||
// Note that if the object is remote, the object is only (conceptually) removed
|
||||
// from this provider. If other providers have references on the remote object,
|
||||
// it will not be removed.
|
||||
//
|
||||
// The object is not guaranteed to be durably removed until Sync is called.
|
||||
@@ -235,11 +326,15 @@ func (p *provider) Remove(fileType base.FileType, fileNum base.DiskFileNum) erro
|
||||
return err
|
||||
}
|
||||
|
||||
if !meta.IsShared() {
|
||||
if !meta.IsRemote() {
|
||||
err = p.vfsRemove(fileType, fileNum)
|
||||
} else {
|
||||
// TODO(radu): implement shared object removal (i.e. deref).
|
||||
// TODO(radu): implement remote object removal (i.e. deref).
|
||||
err = p.sharedUnref(meta)
|
||||
if err != nil && p.isNotExistError(meta, err) {
|
||||
// Wrap the error so that IsNotExistError functions properly.
|
||||
err = errors.Mark(err, os.ErrNotExist)
|
||||
}
|
||||
}
|
||||
if err != nil && !p.IsNotExistError(err) {
|
||||
// We want to be able to retry a Remove, so we keep the object in our list.
|
||||
@@ -252,15 +347,18 @@ func (p *provider) Remove(fileType base.FileType, fileNum base.DiskFileNum) erro
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *provider) isNotExistError(meta objstorage.ObjectMetadata, err error) bool {
|
||||
if meta.Remote.Storage != nil {
|
||||
return meta.Remote.Storage.IsNotExistError(err)
|
||||
}
|
||||
return oserror.IsNotExist(err)
|
||||
}
|
||||
|
||||
// IsNotExistError is part of the objstorage.Provider interface.
|
||||
func (p *provider) IsNotExistError(err error) bool {
|
||||
if oserror.IsNotExist(err) {
|
||||
return true
|
||||
}
|
||||
if p.sharedStorage() != nil && p.sharedStorage().IsNotExistError(err) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
// We use errors.Mark(err, os.ErrNotExist) for not-exist errors coming from
|
||||
// remote.Storage.
|
||||
return oserror.IsNotExist(err)
|
||||
}
|
||||
|
||||
// Sync flushes the metadata from creation or removal of objects since the last Sync.
|
||||
@@ -288,7 +386,7 @@ func (p *provider) LinkOrCopyFromLocal(
|
||||
dstFileNum base.DiskFileNum,
|
||||
opts objstorage.CreateOptions,
|
||||
) (objstorage.ObjectMetadata, error) {
|
||||
shared := opts.PreferSharedStorage && p.st.Shared.Storage != nil
|
||||
shared := opts.PreferSharedStorage && p.st.Remote.CreateOnShared != remote.CreateOnSharedNone
|
||||
if !shared && srcFS == p.st.FS {
|
||||
// Wrap the normal filesystem with one which wraps newly created files with
|
||||
// vfs.NewSyncingFile.
|
||||
@@ -368,18 +466,18 @@ func (p *provider) Lookup(
|
||||
|
||||
// Path is part of the objstorage.Provider interface.
|
||||
func (p *provider) Path(meta objstorage.ObjectMetadata) string {
|
||||
if !meta.IsShared() {
|
||||
if !meta.IsRemote() {
|
||||
return p.vfsPath(meta.FileType, meta.DiskFileNum)
|
||||
}
|
||||
return p.sharedPath(meta)
|
||||
return p.remotePath(meta)
|
||||
}
|
||||
|
||||
// Size returns the size of the object.
|
||||
func (p *provider) Size(meta objstorage.ObjectMetadata) (int64, error) {
|
||||
if !meta.IsShared() {
|
||||
if !meta.IsRemote() {
|
||||
return p.vfsSize(meta.FileType, meta.DiskFileNum)
|
||||
}
|
||||
return p.sharedSize(meta)
|
||||
return p.remoteSize(meta)
|
||||
}
|
||||
|
||||
// List is part of the objstorage.Provider interface.
|
||||
@@ -396,17 +494,29 @@ func (p *provider) List() []objstorage.ObjectMetadata {
|
||||
return res
|
||||
}
|
||||
|
||||
// Metrics is part of the objstorage.Provider interface.
|
||||
func (p *provider) Metrics() sharedcache.Metrics {
|
||||
if p.remote.cache != nil {
|
||||
return p.remote.cache.Metrics()
|
||||
}
|
||||
return sharedcache.Metrics{}
|
||||
}
|
||||
|
||||
func (p *provider) addMetadata(meta objstorage.ObjectMetadata) {
|
||||
if invariants.Enabled {
|
||||
meta.AssertValid()
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.mu.knownObjects[meta.DiskFileNum] = meta
|
||||
if meta.IsShared() {
|
||||
p.mu.shared.catalogBatch.AddObject(sharedobjcat.SharedObjectMetadata{
|
||||
if meta.IsRemote() {
|
||||
p.mu.remote.catalogBatch.AddObject(remoteobjcat.RemoteObjectMetadata{
|
||||
FileNum: meta.DiskFileNum,
|
||||
FileType: meta.FileType,
|
||||
CreatorID: meta.Shared.CreatorID,
|
||||
CreatorFileNum: meta.Shared.CreatorFileNum,
|
||||
CleanupMethod: meta.Shared.CleanupMethod,
|
||||
CreatorID: meta.Remote.CreatorID,
|
||||
CreatorFileNum: meta.Remote.CreatorFileNum,
|
||||
Locator: meta.Remote.Locator,
|
||||
CleanupMethod: meta.Remote.CleanupMethod,
|
||||
})
|
||||
} else {
|
||||
p.mu.localObjectsChanged = true
|
||||
@@ -422,14 +532,14 @@ func (p *provider) removeMetadata(fileNum base.DiskFileNum) {
|
||||
return
|
||||
}
|
||||
delete(p.mu.knownObjects, fileNum)
|
||||
if meta.IsShared() {
|
||||
p.mu.shared.catalogBatch.DeleteObject(fileNum)
|
||||
if meta.IsRemote() {
|
||||
p.mu.remote.catalogBatch.DeleteObject(fileNum)
|
||||
} else {
|
||||
p.mu.localObjectsChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
// protectObject prevents the unreferencing of a shared object until
|
||||
// protectObject prevents the unreferencing of a remote object until
|
||||
// unprotectObject is called.
|
||||
func (p *provider) protectObject(fileNum base.DiskFileNum) {
|
||||
p.mu.Lock()
|
||||
|
||||
52
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/readahead.go
generated
vendored
52
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/readahead.go
generated
vendored
@@ -4,6 +4,8 @@
|
||||
|
||||
package objstorageprovider
|
||||
|
||||
import "github.com/cockroachdb/pebble/internal/invariants"
|
||||
|
||||
const (
|
||||
// Constants for dynamic readahead of data blocks. Note that the size values
|
||||
// make sense as some multiple of the default block size; and they should
|
||||
@@ -11,15 +13,15 @@ const (
|
||||
minFileReadsForReadahead = 2
|
||||
// TODO(bilal): Have the initial size value be a factor of the block size,
|
||||
// as opposed to a hardcoded value.
|
||||
initialReadaheadSize = 64 << 10 /* 64KB */
|
||||
maxReadaheadSize = 256 << 10 /* 256KB */
|
||||
initialReadaheadSize = 64 << 10 /* 64KB */
|
||||
)
|
||||
|
||||
// readaheadState contains state variables related to readahead. Updated on
|
||||
// file reads.
|
||||
type readaheadState struct {
|
||||
// Number of sequential reads.
|
||||
numReads int64
|
||||
numReads int64
|
||||
maxReadaheadSize int64
|
||||
// Size issued to the next call to Prefetch. Starts at or above
|
||||
// initialReadaheadSize and grows exponentially until maxReadaheadSize.
|
||||
size int64
|
||||
@@ -32,22 +34,23 @@ type readaheadState struct {
|
||||
limit int64
|
||||
}
|
||||
|
||||
func makeReadaheadState() readaheadState {
|
||||
func makeReadaheadState(maxReadaheadSize int64) readaheadState {
|
||||
return readaheadState{
|
||||
size: initialReadaheadSize,
|
||||
size: initialReadaheadSize,
|
||||
maxReadaheadSize: maxReadaheadSize,
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *readaheadState) recordCacheHit(offset, blockLength int64) {
|
||||
currentReadEnd := offset + blockLength
|
||||
if rs.numReads >= minFileReadsForReadahead {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+rs.maxReadaheadSize {
|
||||
// This is a read that would have resulted in a readahead, had it
|
||||
// not been a cache hit.
|
||||
rs.limit = currentReadEnd
|
||||
return
|
||||
}
|
||||
if currentReadEnd < rs.limit-rs.prevSize || offset > rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd < rs.limit-rs.prevSize || offset > rs.limit+rs.maxReadaheadSize {
|
||||
// We read too far away from rs.limit to benefit from readahead in
|
||||
// any scenario. Reset all variables.
|
||||
rs.numReads = 1
|
||||
@@ -61,7 +64,7 @@ func (rs *readaheadState) recordCacheHit(offset, blockLength int64) {
|
||||
// readahead.
|
||||
return
|
||||
}
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+rs.maxReadaheadSize {
|
||||
// Blocks are being read sequentially and would benefit from readahead
|
||||
// down the line.
|
||||
rs.numReads++
|
||||
@@ -80,6 +83,9 @@ func (rs *readaheadState) recordCacheHit(offset, blockLength int64) {
|
||||
// Returns a size value (greater than 0) that should be prefetched if readahead
|
||||
// would be beneficial.
|
||||
func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
if invariants.Enabled && rs.maxReadaheadSize == 0 {
|
||||
panic("readaheadState not initialized")
|
||||
}
|
||||
currentReadEnd := offset + blockLength
|
||||
if rs.numReads >= minFileReadsForReadahead {
|
||||
// The minimum threshold of sequential reads to justify reading ahead
|
||||
@@ -89,28 +95,28 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
// as well as the interval where a read would benefit from read ahead:
|
||||
// [rs.limit, rs.limit + rs.size]
|
||||
// We increase the latter interval to
|
||||
// [rs.limit, rs.limit + maxReadaheadSize] to account for cases where
|
||||
// [rs.limit, rs.limit + rs.maxReadaheadSize] to account for cases where
|
||||
// readahead may not be beneficial with a small readahead size, but over
|
||||
// time the readahead size would increase exponentially to make it
|
||||
// beneficial.
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+rs.maxReadaheadSize {
|
||||
// We are doing a read in the interval ahead of
|
||||
// the last readahead range. In the diagrams below, ++++ is the last
|
||||
// readahead range, ==== is the range represented by
|
||||
// [rs.limit, rs.limit + maxReadaheadSize], and ---- is the range
|
||||
// [rs.limit, rs.limit + rs.maxReadaheadSize], and ---- is the range
|
||||
// being read.
|
||||
//
|
||||
// rs.limit rs.limit + maxReadaheadSize
|
||||
// rs.limit rs.limit + rs.maxReadaheadSize
|
||||
// ++++++++++|===========================|
|
||||
//
|
||||
// |-------------|
|
||||
// offset currentReadEnd
|
||||
//
|
||||
// This case is also possible, as are all cases with an overlap
|
||||
// between [rs.limit, rs.limit + maxReadaheadSize] and [offset,
|
||||
// between [rs.limit, rs.limit + rs.maxReadaheadSize] and [offset,
|
||||
// currentReadEnd]:
|
||||
//
|
||||
// rs.limit rs.limit + maxReadaheadSize
|
||||
// rs.limit rs.limit + rs.maxReadaheadSize
|
||||
// ++++++++++|===========================|
|
||||
//
|
||||
// |-------------|
|
||||
@@ -122,19 +128,19 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
rs.prevSize = rs.size
|
||||
// Increase rs.size for the next read.
|
||||
rs.size *= 2
|
||||
if rs.size > maxReadaheadSize {
|
||||
rs.size = maxReadaheadSize
|
||||
if rs.size > rs.maxReadaheadSize {
|
||||
rs.size = rs.maxReadaheadSize
|
||||
}
|
||||
return rs.prevSize
|
||||
}
|
||||
if currentReadEnd < rs.limit-rs.prevSize || offset > rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd < rs.limit-rs.prevSize || offset > rs.limit+rs.maxReadaheadSize {
|
||||
// The above conditional has rs.limit > rs.prevSize to confirm that
|
||||
// rs.limit - rs.prevSize would not underflow.
|
||||
// We read too far away from rs.limit to benefit from readahead in
|
||||
// any scenario. Reset all variables.
|
||||
// The case where we read too far ahead:
|
||||
//
|
||||
// (rs.limit - rs.prevSize) (rs.limit) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit - rs.prevSize) (rs.limit) (rs.limit + rs.maxReadaheadSize)
|
||||
// |+++++++++++++|=============|
|
||||
//
|
||||
// |-------------|
|
||||
@@ -142,7 +148,7 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
//
|
||||
// Or too far behind:
|
||||
//
|
||||
// (rs.limit - rs.prevSize) (rs.limit) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit - rs.prevSize) (rs.limit) (rs.limit + rs.maxReadaheadSize)
|
||||
// |+++++++++++++|=============|
|
||||
//
|
||||
// |-------------|
|
||||
@@ -160,7 +166,7 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
// readahead, but there's no reason to issue a readahead call at the
|
||||
// moment.
|
||||
//
|
||||
// (rs.limit - rs.prevSize) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit - rs.prevSize) (rs.limit + rs.maxReadaheadSize)
|
||||
// |+++++++++++++|===============|
|
||||
// (rs.limit)
|
||||
//
|
||||
@@ -170,11 +176,11 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
rs.numReads++
|
||||
return 0
|
||||
}
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+maxReadaheadSize {
|
||||
if currentReadEnd >= rs.limit && offset <= rs.limit+rs.maxReadaheadSize {
|
||||
// Blocks are being read sequentially and would benefit from readahead
|
||||
// down the line.
|
||||
//
|
||||
// (rs.limit) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit) (rs.limit + rs.maxReadaheadSize)
|
||||
// |=============|
|
||||
//
|
||||
// |-------|
|
||||
@@ -186,7 +192,7 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
||||
// We read too far ahead of the last read, or before it. This indicates
|
||||
// a random read, where readahead is not desirable. Reset all variables.
|
||||
//
|
||||
// (rs.limit - maxReadaheadSize) (rs.limit) (rs.limit + maxReadaheadSize)
|
||||
// (rs.limit - rs.maxReadaheadSize) (rs.limit) (rs.limit + rs.maxReadaheadSize)
|
||||
// |+++++++++++++|=============|
|
||||
//
|
||||
// |-------|
|
||||
|
||||
376
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote.go
generated
vendored
Normal file
376
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote.go
generated
vendored
Normal file
@@ -0,0 +1,376 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package objstorageprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
|
||||
// remoteSubsystem contains the provider fields related to remote storage.
|
||||
// All fields remain unset if remote storage is not configured.
|
||||
type remoteSubsystem struct {
|
||||
catalog *remoteobjcat.Catalog
|
||||
// catalogSyncMutex is used to correctly serialize two sharedSync operations.
|
||||
// It must be acquired before the provider mutex.
|
||||
catalogSyncMutex sync.Mutex
|
||||
|
||||
cache *sharedcache.Cache
|
||||
|
||||
// shared contains the fields relevant to shared objects, i.e. objects that
|
||||
// are created by Pebble and potentially shared between Pebble instances.
|
||||
shared struct {
|
||||
// initialized guards access to the creatorID field.
|
||||
initialized atomic.Bool
|
||||
creatorID objstorage.CreatorID
|
||||
initOnce sync.Once
|
||||
|
||||
// checkRefsOnOpen controls whether we check the ref marker file when opening
|
||||
// an object. Normally this is true when invariants are enabled (but the provider
|
||||
// test tweaks this field).
|
||||
checkRefsOnOpen bool
|
||||
}
|
||||
}
|
||||
|
||||
// remoteInit initializes the remote object subsystem (if configured) and finds
|
||||
// any remote objects.
|
||||
func (p *provider) remoteInit() error {
|
||||
if p.st.Remote.StorageFactory == nil {
|
||||
return nil
|
||||
}
|
||||
catalog, contents, err := remoteobjcat.Open(p.st.FS, p.st.FSDirName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not open remote object catalog")
|
||||
}
|
||||
p.remote.catalog = catalog
|
||||
p.remote.shared.checkRefsOnOpen = invariants.Enabled
|
||||
|
||||
// The creator ID may or may not be initialized yet.
|
||||
if contents.CreatorID.IsSet() {
|
||||
p.remote.initShared(contents.CreatorID)
|
||||
p.st.Logger.Infof("remote storage configured; creatorID = %s", contents.CreatorID)
|
||||
} else {
|
||||
p.st.Logger.Infof("remote storage configured; no creatorID yet")
|
||||
}
|
||||
|
||||
if p.st.Remote.CacheSizeBytes > 0 {
|
||||
const defaultBlockSize = 32 * 1024
|
||||
blockSize := p.st.Remote.CacheBlockSize
|
||||
if blockSize == 0 {
|
||||
blockSize = defaultBlockSize
|
||||
}
|
||||
|
||||
const defaultShardingBlockSize = 1024 * 1024
|
||||
shardingBlockSize := p.st.Remote.ShardingBlockSize
|
||||
if shardingBlockSize == 0 {
|
||||
shardingBlockSize = defaultShardingBlockSize
|
||||
}
|
||||
|
||||
numShards := p.st.Remote.CacheShardCount
|
||||
if numShards == 0 {
|
||||
numShards = 2 * runtime.GOMAXPROCS(0)
|
||||
}
|
||||
|
||||
p.remote.cache, err = sharedcache.Open(
|
||||
p.st.FS, p.st.Logger, p.st.FSDirName, blockSize, shardingBlockSize, p.st.Remote.CacheSizeBytes, numShards)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not open remote object cache")
|
||||
}
|
||||
}
|
||||
|
||||
for _, meta := range contents.Objects {
|
||||
o := objstorage.ObjectMetadata{
|
||||
DiskFileNum: meta.FileNum,
|
||||
FileType: meta.FileType,
|
||||
}
|
||||
o.Remote.CreatorID = meta.CreatorID
|
||||
o.Remote.CreatorFileNum = meta.CreatorFileNum
|
||||
o.Remote.CleanupMethod = meta.CleanupMethod
|
||||
o.Remote.Locator = meta.Locator
|
||||
o.Remote.CustomObjectName = meta.CustomObjectName
|
||||
o.Remote.Storage, err = p.ensureStorageLocked(o.Remote.Locator)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating remote.Storage object for locator '%s'", o.Remote.Locator)
|
||||
}
|
||||
if invariants.Enabled {
|
||||
o.AssertValid()
|
||||
}
|
||||
p.mu.knownObjects[o.DiskFileNum] = o
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// initShared initializes the creator ID, allowing use of shared objects.
|
||||
func (ss *remoteSubsystem) initShared(creatorID objstorage.CreatorID) {
|
||||
ss.shared.initOnce.Do(func() {
|
||||
ss.shared.creatorID = creatorID
|
||||
ss.shared.initialized.Store(true)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *provider) sharedClose() error {
|
||||
if p.st.Remote.StorageFactory == nil {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
if p.remote.cache != nil {
|
||||
err = p.remote.cache.Close()
|
||||
p.remote.cache = nil
|
||||
}
|
||||
if p.remote.catalog != nil {
|
||||
err = firstError(err, p.remote.catalog.Close())
|
||||
p.remote.catalog = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetCreatorID is part of the objstorage.Provider interface.
|
||||
func (p *provider) SetCreatorID(creatorID objstorage.CreatorID) error {
|
||||
if p.st.Remote.StorageFactory == nil {
|
||||
return errors.AssertionFailedf("attempt to set CreatorID but remote storage not enabled")
|
||||
}
|
||||
// Note: this call is a cheap no-op if the creator ID was already set. This
|
||||
// call also checks if we are trying to change the ID.
|
||||
if err := p.remote.catalog.SetCreatorID(creatorID); err != nil {
|
||||
return err
|
||||
}
|
||||
if !p.remote.shared.initialized.Load() {
|
||||
p.st.Logger.Infof("remote storage creatorID set to %s", creatorID)
|
||||
p.remote.initShared(creatorID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSharedForeign is part of the objstorage.Provider interface.
|
||||
func (p *provider) IsSharedForeign(meta objstorage.ObjectMetadata) bool {
|
||||
if !p.remote.shared.initialized.Load() {
|
||||
return false
|
||||
}
|
||||
return meta.IsShared() && (meta.Remote.CreatorID != p.remote.shared.creatorID)
|
||||
}
|
||||
|
||||
func (p *provider) remoteCheckInitialized() error {
|
||||
if p.st.Remote.StorageFactory == nil {
|
||||
return errors.Errorf("remote object support not configured")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedCheckInitialized() error {
|
||||
if err := p.remoteCheckInitialized(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !p.remote.shared.initialized.Load() {
|
||||
return errors.Errorf("remote object support not available: remote creator ID not yet set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedSync() error {
|
||||
// Serialize parallel sync operations. Note that ApplyBatch is already
|
||||
// serialized internally, but we want to make sure they get called with
|
||||
// batches in the right order.
|
||||
p.remote.catalogSyncMutex.Lock()
|
||||
defer p.remote.catalogSyncMutex.Unlock()
|
||||
|
||||
batch := func() remoteobjcat.Batch {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
res := p.mu.remote.catalogBatch.Copy()
|
||||
p.mu.remote.catalogBatch.Reset()
|
||||
return res
|
||||
}()
|
||||
|
||||
if batch.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := p.remote.catalog.ApplyBatch(batch); err != nil {
|
||||
// Put back the batch (for the next Sync), appending any operations that
|
||||
// happened in the meantime.
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
batch.Append(p.mu.remote.catalogBatch)
|
||||
p.mu.remote.catalogBatch = batch
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) remotePath(meta objstorage.ObjectMetadata) string {
|
||||
if meta.Remote.Locator != "" {
|
||||
return fmt.Sprintf("remote-%s://%s", meta.Remote.Locator, remoteObjectName(meta))
|
||||
}
|
||||
return "remote://" + remoteObjectName(meta)
|
||||
}
|
||||
|
||||
// sharedCreateRef creates a reference marker object.
|
||||
func (p *provider) sharedCreateRef(meta objstorage.ObjectMetadata) error {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return err
|
||||
}
|
||||
if meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||
return nil
|
||||
}
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
writer, err := meta.Remote.Storage.CreateObject(refName)
|
||||
if err == nil {
|
||||
// The object is empty, just close the writer.
|
||||
err = writer.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating marker object %q", refName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedCreate(
|
||||
_ context.Context,
|
||||
fileType base.FileType,
|
||||
fileNum base.DiskFileNum,
|
||||
locator remote.Locator,
|
||||
opts objstorage.CreateOptions,
|
||||
) (objstorage.Writable, objstorage.ObjectMetadata, error) {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, err
|
||||
}
|
||||
storage, err := p.ensureStorage(locator)
|
||||
if err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, err
|
||||
}
|
||||
meta := objstorage.ObjectMetadata{
|
||||
DiskFileNum: fileNum,
|
||||
FileType: fileType,
|
||||
}
|
||||
meta.Remote.CreatorID = p.remote.shared.creatorID
|
||||
meta.Remote.CreatorFileNum = fileNum
|
||||
meta.Remote.CleanupMethod = opts.SharedCleanupMethod
|
||||
meta.Remote.Locator = locator
|
||||
meta.Remote.Storage = storage
|
||||
|
||||
objName := remoteObjectName(meta)
|
||||
writer, err := storage.CreateObject(objName)
|
||||
if err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, errors.Wrapf(err, "creating object %q", objName)
|
||||
}
|
||||
return &sharedWritable{
|
||||
p: p,
|
||||
meta: meta,
|
||||
storageWriter: writer,
|
||||
}, meta, nil
|
||||
}
|
||||
|
||||
func (p *provider) remoteOpenForReading(
|
||||
ctx context.Context, meta objstorage.ObjectMetadata, opts objstorage.OpenOptions,
|
||||
) (objstorage.Readable, error) {
|
||||
if err := p.remoteCheckInitialized(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Verify we have a reference on this object; for performance reasons, we only
|
||||
// do this in testing scenarios.
|
||||
if p.remote.shared.checkRefsOnOpen && meta.Remote.CleanupMethod == objstorage.SharedRefTracking {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
if _, err := meta.Remote.Storage.Size(refName); err != nil {
|
||||
if meta.Remote.Storage.IsNotExistError(err) {
|
||||
if opts.MustExist {
|
||||
p.st.Logger.Fatalf("marker object %q does not exist", refName)
|
||||
// TODO(radu): maybe list references for the object.
|
||||
}
|
||||
return nil, errors.Errorf("marker object %q does not exist", refName)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "checking marker object %q", refName)
|
||||
}
|
||||
}
|
||||
objName := remoteObjectName(meta)
|
||||
reader, size, err := meta.Remote.Storage.ReadObject(ctx, objName)
|
||||
if err != nil {
|
||||
if opts.MustExist && meta.Remote.Storage.IsNotExistError(err) {
|
||||
p.st.Logger.Fatalf("object %q does not exist", objName)
|
||||
// TODO(radu): maybe list references for the object.
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return p.newRemoteReadable(reader, size, meta.DiskFileNum), nil
|
||||
}
|
||||
|
||||
func (p *provider) remoteSize(meta objstorage.ObjectMetadata) (int64, error) {
|
||||
if err := p.remoteCheckInitialized(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
objName := remoteObjectName(meta)
|
||||
return meta.Remote.Storage.Size(objName)
|
||||
}
|
||||
|
||||
// sharedUnref implements object "removal" with the remote backend. The ref
|
||||
// marker object is removed and the backing object is removed only if there are
|
||||
// no other ref markers.
|
||||
func (p *provider) sharedUnref(meta objstorage.ObjectMetadata) error {
|
||||
if meta.Remote.CleanupMethod == objstorage.SharedNoCleanup {
|
||||
// Never delete objects in this mode.
|
||||
return nil
|
||||
}
|
||||
if p.isProtected(meta.DiskFileNum) {
|
||||
// TODO(radu): we need a mechanism to unref the object when it becomes
|
||||
// unprotected.
|
||||
return nil
|
||||
}
|
||||
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
// Tolerate a not-exists error.
|
||||
if err := meta.Remote.Storage.Delete(refName); err != nil && !meta.Remote.Storage.IsNotExistError(err) {
|
||||
return err
|
||||
}
|
||||
otherRefs, err := meta.Remote.Storage.List(sharedObjectRefPrefix(meta), "" /* delimiter */)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(otherRefs) == 0 {
|
||||
objName := remoteObjectName(meta)
|
||||
if err := meta.Remote.Storage.Delete(objName); err != nil && !meta.Remote.Storage.IsNotExistError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureStorageLocked populates the remote.Storage object for the given
|
||||
// locator, if necessary. p.mu must be held.
|
||||
func (p *provider) ensureStorageLocked(locator remote.Locator) (remote.Storage, error) {
|
||||
if p.mu.remote.storageObjects == nil {
|
||||
p.mu.remote.storageObjects = make(map[remote.Locator]remote.Storage)
|
||||
}
|
||||
if res, ok := p.mu.remote.storageObjects[locator]; ok {
|
||||
return res, nil
|
||||
}
|
||||
res, err := p.st.Remote.StorageFactory.CreateStorage(locator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.mu.remote.storageObjects[locator] = res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ensureStorage populates the remote.Storage object for the given locator, if necessary.
|
||||
func (p *provider) ensureStorage(locator remote.Locator) (remote.Storage, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.ensureStorageLocked(locator)
|
||||
}
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,6 +26,12 @@ const (
|
||||
// allows the "target" provider to check that the "source" provider kept its
|
||||
// reference on the object alive.
|
||||
tagRefCheckID = 4
|
||||
// tagLocator encodes the remote.Locator; if absent the locator is "". It is
|
||||
// followed by the locator string length and the locator string.
|
||||
tagLocator = 5
|
||||
// tagLocator encodes a custom object name (if present). It is followed by the
|
||||
// custom name string length and the string.
|
||||
tagCustomObjectName = 6
|
||||
|
||||
// Any new tags that don't have the tagNotSafeToIgnoreMask bit set must be
|
||||
// followed by the length of the data (so they can be skipped).
|
||||
@@ -34,83 +41,103 @@ const (
|
||||
tagNotSafeToIgnoreMask = 64
|
||||
)
|
||||
|
||||
func (p *provider) encodeSharedObjectBacking(
|
||||
func (p *provider) encodeRemoteObjectBacking(
|
||||
meta *objstorage.ObjectMetadata,
|
||||
) (objstorage.SharedObjectBacking, error) {
|
||||
if !meta.IsShared() {
|
||||
return nil, errors.AssertionFailedf("object %s not on shared storage", meta.DiskFileNum)
|
||||
) (objstorage.RemoteObjectBacking, error) {
|
||||
if !meta.IsRemote() {
|
||||
return nil, errors.AssertionFailedf("object %s not on remote storage", meta.DiskFileNum)
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, binary.MaxVarintLen64*4)
|
||||
buf = binary.AppendUvarint(buf, tagCreatorID)
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Shared.CreatorID))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Remote.CreatorID))
|
||||
// TODO(radu): encode file type as well?
|
||||
buf = binary.AppendUvarint(buf, tagCreatorFileNum)
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Shared.CreatorFileNum.FileNum()))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Remote.CreatorFileNum.FileNum()))
|
||||
buf = binary.AppendUvarint(buf, tagCleanupMethod)
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Shared.CleanupMethod))
|
||||
if meta.Shared.CleanupMethod == objstorage.SharedRefTracking {
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.Remote.CleanupMethod))
|
||||
if meta.Remote.CleanupMethod == objstorage.SharedRefTracking {
|
||||
buf = binary.AppendUvarint(buf, tagRefCheckID)
|
||||
buf = binary.AppendUvarint(buf, uint64(p.shared.creatorID))
|
||||
buf = binary.AppendUvarint(buf, uint64(p.remote.shared.creatorID))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.DiskFileNum.FileNum()))
|
||||
}
|
||||
if meta.Remote.Locator != "" {
|
||||
buf = binary.AppendUvarint(buf, tagLocator)
|
||||
buf = encodeString(buf, string(meta.Remote.Locator))
|
||||
}
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
buf = binary.AppendUvarint(buf, tagCustomObjectName)
|
||||
buf = encodeString(buf, meta.Remote.CustomObjectName)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
type sharedObjectBackingHandle struct {
|
||||
backing objstorage.SharedObjectBacking
|
||||
type remoteObjectBackingHandle struct {
|
||||
backing objstorage.RemoteObjectBacking
|
||||
fileNum base.DiskFileNum
|
||||
p *provider
|
||||
}
|
||||
|
||||
func (s *sharedObjectBackingHandle) Get() (objstorage.SharedObjectBacking, error) {
|
||||
func (s *remoteObjectBackingHandle) Get() (objstorage.RemoteObjectBacking, error) {
|
||||
if s.backing == nil {
|
||||
return nil, errors.Errorf("SharedObjectBackingHandle.Get() called after Close()")
|
||||
return nil, errors.Errorf("RemoteObjectBackingHandle.Get() called after Close()")
|
||||
}
|
||||
return s.backing, nil
|
||||
}
|
||||
|
||||
func (s *sharedObjectBackingHandle) Close() {
|
||||
func (s *remoteObjectBackingHandle) Close() {
|
||||
if s.backing != nil {
|
||||
s.backing = nil
|
||||
s.p.unprotectObject(s.fileNum)
|
||||
}
|
||||
}
|
||||
|
||||
var _ objstorage.SharedObjectBackingHandle = (*sharedObjectBackingHandle)(nil)
|
||||
var _ objstorage.RemoteObjectBackingHandle = (*remoteObjectBackingHandle)(nil)
|
||||
|
||||
// SharedObjectBacking is part of the objstorage.Provider interface.
|
||||
func (p *provider) SharedObjectBacking(
|
||||
// RemoteObjectBacking is part of the objstorage.Provider interface.
|
||||
func (p *provider) RemoteObjectBacking(
|
||||
meta *objstorage.ObjectMetadata,
|
||||
) (objstorage.SharedObjectBackingHandle, error) {
|
||||
backing, err := p.encodeSharedObjectBacking(meta)
|
||||
) (objstorage.RemoteObjectBackingHandle, error) {
|
||||
backing, err := p.encodeRemoteObjectBacking(meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.protectObject(meta.DiskFileNum)
|
||||
return &sharedObjectBackingHandle{
|
||||
return &remoteObjectBackingHandle{
|
||||
backing: backing,
|
||||
fileNum: meta.DiskFileNum,
|
||||
p: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateExternalObjectBacking is part of the objstorage.Provider interface.
|
||||
func (p *provider) CreateExternalObjectBacking(
|
||||
locator remote.Locator, objName string,
|
||||
) (objstorage.RemoteObjectBacking, error) {
|
||||
var meta objstorage.ObjectMetadata
|
||||
meta.Remote.Locator = locator
|
||||
meta.Remote.CustomObjectName = objName
|
||||
meta.Remote.CleanupMethod = objstorage.SharedNoCleanup
|
||||
return p.encodeRemoteObjectBacking(&meta)
|
||||
}
|
||||
|
||||
type decodedBacking struct {
|
||||
meta objstorage.ObjectMetadata
|
||||
// refToCheck is set only when meta.Shared.CleanupMethod is RefTracking
|
||||
// refToCheck is set only when meta.Remote.CleanupMethod is RefTracking
|
||||
refToCheck struct {
|
||||
creatorID objstorage.CreatorID
|
||||
fileNum base.DiskFileNum
|
||||
}
|
||||
}
|
||||
|
||||
// decodeSharedObjectBacking decodes the shared object metadata.
|
||||
// Returns the object metadata and (optionally) the creator ID of the provider
|
||||
// that encoded the backing whose ref marker needs to be checked.
|
||||
func decodeSharedObjectBacking(
|
||||
fileType base.FileType, fileNum base.DiskFileNum, buf objstorage.SharedObjectBacking,
|
||||
// decodeRemoteObjectBacking decodes the remote object metadata.
|
||||
//
|
||||
// Note that the meta.Remote.Storage field is not set.
|
||||
func decodeRemoteObjectBacking(
|
||||
fileType base.FileType, fileNum base.DiskFileNum, buf objstorage.RemoteObjectBacking,
|
||||
) (decodedBacking, error) {
|
||||
var creatorID, creatorFileNum, cleanupMethod, refCheckCreatorID, refCheckFileNum uint64
|
||||
var locator, customObjName string
|
||||
br := bytes.NewReader(buf)
|
||||
for {
|
||||
tag, err := binary.ReadUvarint(br)
|
||||
@@ -136,6 +163,12 @@ func decodeSharedObjectBacking(
|
||||
refCheckFileNum, err = binary.ReadUvarint(br)
|
||||
}
|
||||
|
||||
case tagLocator:
|
||||
locator, err = decodeString(br)
|
||||
|
||||
case tagCustomObjectName:
|
||||
customObjName, err = decodeString(br)
|
||||
|
||||
default:
|
||||
// Ignore unknown tags, unless they're not safe to ignore.
|
||||
if tag&tagNotSafeToIgnoreMask != 0 {
|
||||
@@ -151,37 +184,65 @@ func decodeSharedObjectBacking(
|
||||
return decodedBacking{}, err
|
||||
}
|
||||
}
|
||||
if creatorID == 0 {
|
||||
return decodedBacking{}, errors.Newf("shared object backing missing creator ID")
|
||||
}
|
||||
if creatorFileNum == 0 {
|
||||
return decodedBacking{}, errors.Newf("shared object backing missing creator file num")
|
||||
if customObjName == "" {
|
||||
if creatorID == 0 {
|
||||
return decodedBacking{}, errors.Newf("remote object backing missing creator ID")
|
||||
}
|
||||
if creatorFileNum == 0 {
|
||||
return decodedBacking{}, errors.Newf("remote object backing missing creator file num")
|
||||
}
|
||||
}
|
||||
var res decodedBacking
|
||||
res.meta.DiskFileNum = fileNum
|
||||
res.meta.FileType = fileType
|
||||
res.meta.Shared.CreatorID = objstorage.CreatorID(creatorID)
|
||||
res.meta.Shared.CreatorFileNum = base.FileNum(creatorFileNum).DiskFileNum()
|
||||
res.meta.Shared.CleanupMethod = objstorage.SharedCleanupMethod(cleanupMethod)
|
||||
|
||||
if res.meta.Shared.CleanupMethod == objstorage.SharedRefTracking {
|
||||
res.meta.Remote.CreatorID = objstorage.CreatorID(creatorID)
|
||||
res.meta.Remote.CreatorFileNum = base.FileNum(creatorFileNum).DiskFileNum()
|
||||
res.meta.Remote.CleanupMethod = objstorage.SharedCleanupMethod(cleanupMethod)
|
||||
if res.meta.Remote.CleanupMethod == objstorage.SharedRefTracking {
|
||||
if refCheckCreatorID == 0 || refCheckFileNum == 0 {
|
||||
return decodedBacking{}, errors.Newf("shared object backing missing ref to check")
|
||||
return decodedBacking{}, errors.Newf("remote object backing missing ref to check")
|
||||
}
|
||||
res.refToCheck.creatorID = objstorage.CreatorID(refCheckCreatorID)
|
||||
res.refToCheck.fileNum = base.FileNum(refCheckFileNum).DiskFileNum()
|
||||
}
|
||||
res.meta.Remote.Locator = remote.Locator(locator)
|
||||
res.meta.Remote.CustomObjectName = customObjName
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// AttachSharedObjects is part of the objstorage.Provider interface.
|
||||
func (p *provider) AttachSharedObjects(
|
||||
objs []objstorage.SharedObjectToAttach,
|
||||
func encodeString(buf []byte, s string) []byte {
|
||||
buf = binary.AppendUvarint(buf, uint64(len(s)))
|
||||
buf = append(buf, []byte(s)...)
|
||||
return buf
|
||||
}
|
||||
|
||||
func decodeString(br io.ByteReader) (string, error) {
|
||||
length, err := binary.ReadUvarint(br)
|
||||
if err != nil || length == 0 {
|
||||
return "", err
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
for i := range buf {
|
||||
buf[i], err = br.ReadByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// AttachRemoteObjects is part of the objstorage.Provider interface.
|
||||
func (p *provider) AttachRemoteObjects(
|
||||
objs []objstorage.RemoteObjectToAttach,
|
||||
) ([]objstorage.ObjectMetadata, error) {
|
||||
decoded := make([]decodedBacking, len(objs))
|
||||
for i, o := range objs {
|
||||
var err error
|
||||
decoded[i], err = decodeSharedObjectBacking(o.FileType, o.FileNum, o.Backing)
|
||||
decoded[i], err = decodeRemoteObjectBacking(o.FileType, o.FileNum, o.Backing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decoded[i].meta.Remote.Storage, err = p.ensureStorage(decoded[i].meta.Remote.Locator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -190,7 +251,7 @@ func (p *provider) AttachSharedObjects(
|
||||
// Create the reference marker objects.
|
||||
// TODO(radu): parallelize this.
|
||||
for _, d := range decoded {
|
||||
if d.meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
|
||||
if d.meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||
continue
|
||||
}
|
||||
if err := p.sharedCreateRef(d.meta); err != nil {
|
||||
@@ -199,10 +260,10 @@ func (p *provider) AttachSharedObjects(
|
||||
}
|
||||
// Check the "origin"'s reference.
|
||||
refName := sharedObjectRefName(d.meta, d.refToCheck.creatorID, d.refToCheck.fileNum)
|
||||
if _, err := p.sharedStorage().Size(refName); err != nil {
|
||||
if _, err := d.meta.Remote.Storage.Size(refName); err != nil {
|
||||
_ = p.sharedUnref(d.meta)
|
||||
// TODO(radu): clean up references previously created in this loop.
|
||||
if p.sharedStorage().IsNotExistError(err) {
|
||||
if d.meta.Remote.Storage.IsNotExistError(err) {
|
||||
return nil, errors.Errorf("origin marker object %q does not exist;"+
|
||||
" object probably removed from the provider which created the backing", refName)
|
||||
}
|
||||
@@ -214,11 +275,13 @@ func (p *provider) AttachSharedObjects(
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
for _, d := range decoded {
|
||||
p.mu.shared.catalogBatch.AddObject(sharedobjcat.SharedObjectMetadata{
|
||||
p.mu.remote.catalogBatch.AddObject(remoteobjcat.RemoteObjectMetadata{
|
||||
FileNum: d.meta.DiskFileNum,
|
||||
FileType: d.meta.FileType,
|
||||
CreatorID: d.meta.Shared.CreatorID,
|
||||
CreatorFileNum: d.meta.Shared.CreatorFileNum,
|
||||
CreatorID: d.meta.Remote.CreatorID,
|
||||
CreatorFileNum: d.meta.Remote.CreatorFileNum,
|
||||
CleanupMethod: d.meta.Remote.CleanupMethod,
|
||||
Locator: d.meta.Remote.Locator,
|
||||
})
|
||||
}
|
||||
}()
|
||||
@@ -11,16 +11,19 @@ import (
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
)
|
||||
|
||||
// sharedObjectName returns the name of an object on shared storage.
|
||||
// remoteObjectName returns the name of an object on remote storage.
|
||||
//
|
||||
// For sstables, the format is: <hash>-<creator-id>-<file-num>.sst
|
||||
// For example: 1a3f-2-000001.sst
|
||||
func sharedObjectName(meta objstorage.ObjectMetadata) string {
|
||||
func remoteObjectName(meta objstorage.ObjectMetadata) string {
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
return meta.Remote.CustomObjectName
|
||||
}
|
||||
switch meta.FileType {
|
||||
case base.FileTypeTable:
|
||||
return fmt.Sprintf(
|
||||
"%04x-%d-%06d.sst",
|
||||
objHash(meta), meta.Shared.CreatorID, meta.Shared.CreatorFileNum.FileNum(),
|
||||
objHash(meta), meta.Remote.CreatorID, meta.Remote.CreatorFileNum.FileNum(),
|
||||
)
|
||||
}
|
||||
panic("unknown FileType")
|
||||
@@ -34,25 +37,33 @@ func sharedObjectName(meta objstorage.ObjectMetadata) string {
|
||||
func sharedObjectRefName(
|
||||
meta objstorage.ObjectMetadata, refCreatorID objstorage.CreatorID, refFileNum base.DiskFileNum,
|
||||
) string {
|
||||
if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
|
||||
if meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||
panic("ref object used when ref tracking disabled")
|
||||
}
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
return fmt.Sprintf(
|
||||
"%s.ref.%d.%06d", meta.Remote.CustomObjectName, refCreatorID, refFileNum.FileNum(),
|
||||
)
|
||||
}
|
||||
switch meta.FileType {
|
||||
case base.FileTypeTable:
|
||||
return fmt.Sprintf(
|
||||
"%04x-%d-%06d.sst.ref.%d.%06d",
|
||||
objHash(meta), meta.Shared.CreatorID, meta.Shared.CreatorFileNum.FileNum(), refCreatorID, refFileNum.FileNum(),
|
||||
objHash(meta), meta.Remote.CreatorID, meta.Remote.CreatorFileNum.FileNum(), refCreatorID, refFileNum.FileNum(),
|
||||
)
|
||||
}
|
||||
panic("unknown FileType")
|
||||
}
|
||||
|
||||
func sharedObjectRefPrefix(meta objstorage.ObjectMetadata) string {
|
||||
if meta.Remote.CustomObjectName != "" {
|
||||
return meta.Remote.CustomObjectName + ".ref."
|
||||
}
|
||||
switch meta.FileType {
|
||||
case base.FileTypeTable:
|
||||
return fmt.Sprintf(
|
||||
"%04x-%d-%06d.sst.ref.",
|
||||
objHash(meta), meta.Shared.CreatorID, meta.Shared.CreatorFileNum.FileNum(),
|
||||
objHash(meta), meta.Remote.CreatorID, meta.Remote.CreatorFileNum.FileNum(),
|
||||
)
|
||||
}
|
||||
panic("unknown FileType")
|
||||
@@ -64,10 +75,10 @@ func sharedObjectRefPrefix(meta objstorage.ObjectMetadata) string {
|
||||
//
|
||||
// For example: 1a3f-2-000001.sst.ref.5.000008
|
||||
func (p *provider) sharedObjectRefName(meta objstorage.ObjectMetadata) string {
|
||||
if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
|
||||
if meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||
panic("ref object used when ref tracking disabled")
|
||||
}
|
||||
return sharedObjectRefName(meta, p.shared.creatorID, meta.DiskFileNum)
|
||||
return sharedObjectRefName(meta, p.remote.shared.creatorID, meta.DiskFileNum)
|
||||
}
|
||||
|
||||
// objHash returns a 16-bit hash value derived from the creator ID and creator
|
||||
@@ -76,5 +87,5 @@ func (p *provider) sharedObjectRefName(meta objstorage.ObjectMetadata) string {
|
||||
func objHash(meta objstorage.ObjectMetadata) uint16 {
|
||||
const prime1 = 7459
|
||||
const prime2 = 17539
|
||||
return uint16(uint64(meta.Shared.CreatorID)*prime1 + uint64(meta.Shared.CreatorFileNum.FileNum())*prime2)
|
||||
return uint16(uint64(meta.Remote.CreatorID)*prime1 + uint64(meta.Remote.CreatorFileNum.FileNum())*prime2)
|
||||
}
|
||||
162
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote_readable.go
generated
vendored
Normal file
162
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote_readable.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package objstorageprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
|
||||
const remoteMaxReadaheadSize = 1024 * 1024 /* 1MB */
|
||||
|
||||
// remoteReadable is a very simple implementation of Readable on top of the
|
||||
// ReadCloser returned by remote.Storage.CreateObject.
|
||||
type remoteReadable struct {
|
||||
objReader remote.ObjectReader
|
||||
size int64
|
||||
fileNum base.DiskFileNum
|
||||
provider *provider
|
||||
}
|
||||
|
||||
var _ objstorage.Readable = (*remoteReadable)(nil)
|
||||
|
||||
func (p *provider) newRemoteReadable(
|
||||
objReader remote.ObjectReader, size int64, fileNum base.DiskFileNum,
|
||||
) *remoteReadable {
|
||||
return &remoteReadable{
|
||||
objReader: objReader,
|
||||
size: size,
|
||||
fileNum: fileNum,
|
||||
provider: p,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadAt is part of the objstorage.Readable interface.
|
||||
func (r *remoteReadable) ReadAt(ctx context.Context, p []byte, offset int64) error {
|
||||
return r.readInternal(ctx, p, offset, false /* forCompaction */)
|
||||
}
|
||||
|
||||
// readInternal performs a read for the object, using the cache when
|
||||
// appropriate.
|
||||
func (r *remoteReadable) readInternal(
|
||||
ctx context.Context, p []byte, offset int64, forCompaction bool,
|
||||
) error {
|
||||
if cache := r.provider.remote.cache; cache != nil {
|
||||
flags := sharedcache.ReadFlags{
|
||||
// Don't add data to the cache if this read is for a compaction.
|
||||
ReadOnly: forCompaction,
|
||||
}
|
||||
return r.provider.remote.cache.ReadAt(ctx, r.fileNum, p, offset, r.objReader, r.size, flags)
|
||||
}
|
||||
return r.objReader.ReadAt(ctx, p, offset)
|
||||
}
|
||||
|
||||
func (r *remoteReadable) Close() error {
|
||||
defer func() { r.objReader = nil }()
|
||||
return r.objReader.Close()
|
||||
}
|
||||
|
||||
func (r *remoteReadable) Size() int64 {
|
||||
return r.size
|
||||
}
|
||||
|
||||
func (r *remoteReadable) NewReadHandle(_ context.Context) objstorage.ReadHandle {
|
||||
// TODO(radu): use a pool.
|
||||
rh := &remoteReadHandle{readable: r}
|
||||
rh.readahead.state = makeReadaheadState(remoteMaxReadaheadSize)
|
||||
return rh
|
||||
}
|
||||
|
||||
type remoteReadHandle struct {
|
||||
readable *remoteReadable
|
||||
readahead struct {
|
||||
state readaheadState
|
||||
data []byte
|
||||
offset int64
|
||||
}
|
||||
forCompaction bool
|
||||
}
|
||||
|
||||
var _ objstorage.ReadHandle = (*remoteReadHandle)(nil)
|
||||
|
||||
// ReadAt is part of the objstorage.ReadHandle interface.
|
||||
func (r *remoteReadHandle) ReadAt(ctx context.Context, p []byte, offset int64) error {
|
||||
readaheadSize := r.maybeReadahead(offset, len(p))
|
||||
|
||||
// Check if we already have the data from a previous read-ahead.
|
||||
if rhSize := int64(len(r.readahead.data)); rhSize > 0 {
|
||||
if r.readahead.offset <= offset && r.readahead.offset+rhSize > offset {
|
||||
n := copy(p, r.readahead.data[offset-r.readahead.offset:])
|
||||
if n == len(p) {
|
||||
// All data was available.
|
||||
return nil
|
||||
}
|
||||
// Use the data that we had and do a shorter read.
|
||||
offset += int64(n)
|
||||
p = p[n:]
|
||||
readaheadSize -= n
|
||||
}
|
||||
}
|
||||
|
||||
if readaheadSize > len(p) {
|
||||
// Don't try to read past EOF.
|
||||
if offset+int64(readaheadSize) > r.readable.size {
|
||||
readaheadSize = int(r.readable.size - offset)
|
||||
if readaheadSize <= 0 {
|
||||
// This shouldn't happen in practice (Pebble should never try to read
|
||||
// past EOF).
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
r.readahead.offset = offset
|
||||
// TODO(radu): we need to somehow account for this memory.
|
||||
if cap(r.readahead.data) >= readaheadSize {
|
||||
r.readahead.data = r.readahead.data[:readaheadSize]
|
||||
} else {
|
||||
r.readahead.data = make([]byte, readaheadSize)
|
||||
}
|
||||
|
||||
if err := r.readable.readInternal(ctx, r.readahead.data, offset, r.forCompaction); err != nil {
|
||||
// Make sure we don't treat the data as valid next time.
|
||||
r.readahead.data = r.readahead.data[:0]
|
||||
return err
|
||||
}
|
||||
copy(p, r.readahead.data)
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.readable.readInternal(ctx, p, offset, r.forCompaction)
|
||||
}
|
||||
|
||||
func (r *remoteReadHandle) maybeReadahead(offset int64, len int) int {
|
||||
if r.forCompaction {
|
||||
return remoteMaxReadaheadSize
|
||||
}
|
||||
return int(r.readahead.state.maybeReadahead(offset, int64(len)))
|
||||
}
|
||||
|
||||
// Close is part of the objstorage.ReadHandle interface.
|
||||
func (r *remoteReadHandle) Close() error {
|
||||
r.readable = nil
|
||||
r.readahead.data = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetupForCompaction is part of the objstorage.ReadHandle interface.
|
||||
func (r *remoteReadHandle) SetupForCompaction() {
|
||||
r.forCompaction = true
|
||||
}
|
||||
|
||||
// RecordCacheHit is part of the objstorage.ReadHandle interface.
|
||||
func (r *remoteReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {
|
||||
if !r.forCompaction {
|
||||
r.readahead.state.recordCacheHit(offset, size)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sharedobjcat
|
||||
package remoteobjcat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -13,15 +13,16 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
"github.com/cockroachdb/pebble/record"
|
||||
"github.com/cockroachdb/pebble/vfs"
|
||||
"github.com/cockroachdb/pebble/vfs/atomicfs"
|
||||
)
|
||||
|
||||
// Catalog is used to manage the on-disk shared object catalog.
|
||||
// Catalog is used to manage the on-disk remote object catalog.
|
||||
//
|
||||
// The catalog file is a log of records, where each record is an encoded
|
||||
// versionEdit.
|
||||
// VersionEdit.
|
||||
type Catalog struct {
|
||||
fs vfs.FS
|
||||
dirname string
|
||||
@@ -29,7 +30,7 @@ type Catalog struct {
|
||||
sync.Mutex
|
||||
|
||||
creatorID objstorage.CreatorID
|
||||
objects map[base.DiskFileNum]SharedObjectMetadata
|
||||
objects map[base.DiskFileNum]RemoteObjectMetadata
|
||||
|
||||
marker *atomicfs.Marker
|
||||
|
||||
@@ -44,8 +45,8 @@ type Catalog struct {
|
||||
}
|
||||
}
|
||||
|
||||
// SharedObjectMetadata encapsulates the data stored in the catalog file for each object.
|
||||
type SharedObjectMetadata struct {
|
||||
// RemoteObjectMetadata encapsulates the data stored in the catalog file for each object.
|
||||
type RemoteObjectMetadata struct {
|
||||
// FileNum is the identifier for the object within the context of a single DB
|
||||
// instance.
|
||||
FileNum base.DiskFileNum
|
||||
@@ -56,24 +57,29 @@ type SharedObjectMetadata struct {
|
||||
// CreatorFileNum is the identifier for the object within the context of the
|
||||
// DB instance that originally created the object.
|
||||
CreatorFileNum base.DiskFileNum
|
||||
|
||||
// CleanupMethod indicates the method for cleaning up unused shared objects.
|
||||
CleanupMethod objstorage.SharedCleanupMethod
|
||||
// Locator identifies a remote.Storage implementation.
|
||||
Locator remote.Locator
|
||||
// CustomObjectName (if it is set) overrides the object name that is normally
|
||||
// derived from the CreatorID and CreatorFileNum.
|
||||
CustomObjectName string
|
||||
}
|
||||
|
||||
const (
|
||||
catalogFilenameBase = "SHARED-CATALOG"
|
||||
catalogMarkerName = "shared-catalog"
|
||||
catalogFilenameBase = "REMOTE-OBJ-CATALOG"
|
||||
catalogMarkerName = "remote-obj-catalog"
|
||||
|
||||
// We create a new file when the size exceeds 1MB (and some other conditions
|
||||
// hold; see record.RotationHelper).
|
||||
rotateFileSize = 1024 * 1024 // 1MB
|
||||
)
|
||||
|
||||
// CatalogContents contains the shared objects in the catalog.
|
||||
// CatalogContents contains the remote objects in the catalog.
|
||||
type CatalogContents struct {
|
||||
// CreatorID, if it is set.
|
||||
CreatorID objstorage.CreatorID
|
||||
Objects []SharedObjectMetadata
|
||||
Objects []RemoteObjectMetadata
|
||||
}
|
||||
|
||||
// Open creates a Catalog and loads any existing catalog file, returning the
|
||||
@@ -83,7 +89,7 @@ func Open(fs vfs.FS, dirname string) (*Catalog, CatalogContents, error) {
|
||||
fs: fs,
|
||||
dirname: dirname,
|
||||
}
|
||||
c.mu.objects = make(map[base.DiskFileNum]SharedObjectMetadata)
|
||||
c.mu.objects = make(map[base.DiskFileNum]RemoteObjectMetadata)
|
||||
|
||||
var err error
|
||||
c.mu.marker, c.mu.catalogFilename, err = atomicfs.LocateMarker(fs, dirname, catalogMarkerName)
|
||||
@@ -102,7 +108,7 @@ func Open(fs vfs.FS, dirname string) (*Catalog, CatalogContents, error) {
|
||||
}
|
||||
res := CatalogContents{
|
||||
CreatorID: c.mu.creatorID,
|
||||
Objects: make([]SharedObjectMetadata, 0, len(c.mu.objects)),
|
||||
Objects: make([]RemoteObjectMetadata, 0, len(c.mu.objects)),
|
||||
}
|
||||
for _, meta := range c.mu.objects {
|
||||
res.Objects = append(res.Objects, meta)
|
||||
@@ -130,9 +136,9 @@ func (c *Catalog) SetCreatorID(id objstorage.CreatorID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
ve := versionEdit{CreatorID: id}
|
||||
ve := VersionEdit{CreatorID: id}
|
||||
if err := c.writeToCatalogFileLocked(&ve); err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not write to shared object catalog: %v", err)
|
||||
return errors.Wrapf(err, "pebble: could not write to remote object catalog: %v", err)
|
||||
}
|
||||
c.mu.creatorID = id
|
||||
return nil
|
||||
@@ -159,14 +165,14 @@ func (c *Catalog) closeCatalogFile() error {
|
||||
|
||||
// Batch is used to perform multiple object additions/deletions at once.
|
||||
type Batch struct {
|
||||
ve versionEdit
|
||||
ve VersionEdit
|
||||
}
|
||||
|
||||
// AddObject adds a new object to the batch.
|
||||
//
|
||||
// The given FileNum must be new - it must not match that of any object that was
|
||||
// ever in the catalog.
|
||||
func (b *Batch) AddObject(meta SharedObjectMetadata) {
|
||||
func (b *Batch) AddObject(meta RemoteObjectMetadata) {
|
||||
b.ve.NewObjects = append(b.ve.NewObjects, meta)
|
||||
}
|
||||
|
||||
@@ -190,7 +196,7 @@ func (b *Batch) IsEmpty() bool {
|
||||
func (b *Batch) Copy() Batch {
|
||||
var res Batch
|
||||
if len(b.ve.NewObjects) > 0 {
|
||||
res.ve.NewObjects = make([]SharedObjectMetadata, len(b.ve.NewObjects))
|
||||
res.ve.NewObjects = make([]RemoteObjectMetadata, len(b.ve.NewObjects))
|
||||
copy(res.ve.NewObjects, b.ve.NewObjects)
|
||||
}
|
||||
if len(b.ve.DeletedObjects) > 0 {
|
||||
@@ -212,29 +218,40 @@ func (c *Catalog) ApplyBatch(b Batch) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
for _, n := range b.ve.DeletedObjects {
|
||||
if _, exists := c.mu.objects[n]; !exists {
|
||||
return errors.AssertionFailedf("deleting non-existent object %s", n)
|
||||
// Sanity checks.
|
||||
toAdd := make(map[base.DiskFileNum]struct{}, len(b.ve.NewObjects))
|
||||
exists := func(n base.DiskFileNum) bool {
|
||||
_, ok := c.mu.objects[n]
|
||||
if !ok {
|
||||
_, ok = toAdd[n]
|
||||
}
|
||||
return ok
|
||||
}
|
||||
for _, meta := range b.ve.NewObjects {
|
||||
if _, exists := c.mu.objects[meta.FileNum]; exists {
|
||||
if exists(meta.FileNum) {
|
||||
return errors.AssertionFailedf("adding existing object %s", meta.FileNum)
|
||||
}
|
||||
toAdd[meta.FileNum] = struct{}{}
|
||||
}
|
||||
for _, n := range b.ve.DeletedObjects {
|
||||
if !exists(n) {
|
||||
return errors.AssertionFailedf("deleting non-existent object %s", n)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.writeToCatalogFileLocked(&b.ve); err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not write to shared object catalog: %v", err)
|
||||
return errors.Wrapf(err, "pebble: could not write to remote object catalog: %v", err)
|
||||
}
|
||||
|
||||
// Apply the batch to our current state.
|
||||
for _, n := range b.ve.DeletedObjects {
|
||||
delete(c.mu.objects, n)
|
||||
}
|
||||
// Add new objects before deleting any objects. This allows for cases where
|
||||
// the same batch adds and deletes an object.
|
||||
for _, meta := range b.ve.NewObjects {
|
||||
c.mu.objects[meta.FileNum] = meta
|
||||
}
|
||||
b.Reset()
|
||||
for _, n := range b.ve.DeletedObjects {
|
||||
delete(c.mu.objects, n)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -243,7 +260,7 @@ func (c *Catalog) loadFromCatalogFile(filename string) error {
|
||||
f, err := c.fs.Open(catalogPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "pebble: could not open shared object catalog file %q for DB %q",
|
||||
err, "pebble: could not open remote object catalog file %q for DB %q",
|
||||
errors.Safe(filename), c.dirname,
|
||||
)
|
||||
}
|
||||
@@ -255,32 +272,26 @@ func (c *Catalog) loadFromCatalogFile(filename string) error {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: error when loading shared object catalog file %q",
|
||||
return errors.Wrapf(err, "pebble: error when loading remote object catalog file %q",
|
||||
errors.Safe(filename))
|
||||
}
|
||||
var ve versionEdit
|
||||
err = ve.Decode(r)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: error when loading shared object catalog file %q",
|
||||
var ve VersionEdit
|
||||
if err := ve.Decode(r); err != nil {
|
||||
return errors.Wrapf(err, "pebble: error when loading remote object catalog file %q",
|
||||
errors.Safe(filename))
|
||||
}
|
||||
// Apply the version edit to the current state.
|
||||
if ve.CreatorID.IsSet() {
|
||||
c.mu.creatorID = ve.CreatorID
|
||||
}
|
||||
for _, fileNum := range ve.DeletedObjects {
|
||||
delete(c.mu.objects, fileNum)
|
||||
}
|
||||
for _, meta := range ve.NewObjects {
|
||||
c.mu.objects[meta.FileNum] = meta
|
||||
if err := ve.Apply(&c.mu.creatorID, c.mu.objects); err != nil {
|
||||
return errors.Wrapf(err, "pebble: error when loading remote object catalog file %q",
|
||||
errors.Safe(filename))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeToCatalogFileLocked writes a versionEdit to the catalog file.
|
||||
// writeToCatalogFileLocked writes a VersionEdit to the catalog file.
|
||||
// Creates a new file if this is the first write.
|
||||
func (c *Catalog) writeToCatalogFileLocked(ve *versionEdit) error {
|
||||
func (c *Catalog) writeToCatalogFileLocked(ve *VersionEdit) error {
|
||||
c.mu.rotationHelper.AddRecord(int64(len(ve.NewObjects) + len(ve.DeletedObjects)))
|
||||
snapshotSize := int64(len(c.mu.objects))
|
||||
|
||||
@@ -323,10 +334,10 @@ func (c *Catalog) createNewCatalogFileLocked() (outErr error) {
|
||||
}
|
||||
recWriter := record.NewWriter(file)
|
||||
err = func() error {
|
||||
// Create a versionEdit that gets us from an empty catalog to the current state.
|
||||
var ve versionEdit
|
||||
// Create a VersionEdit that gets us from an empty catalog to the current state.
|
||||
var ve VersionEdit
|
||||
ve.CreatorID = c.mu.creatorID
|
||||
ve.NewObjects = make([]SharedObjectMetadata, 0, len(c.mu.objects))
|
||||
ve.NewObjects = make([]RemoteObjectMetadata, 0, len(c.mu.objects))
|
||||
for _, meta := range c.mu.objects {
|
||||
ve.NewObjects = append(ve.NewObjects, meta)
|
||||
}
|
||||
@@ -361,7 +372,7 @@ func (c *Catalog) createNewCatalogFileLocked() (outErr error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeRecord(ve *versionEdit, file vfs.File, recWriter *record.Writer) error {
|
||||
func writeRecord(ve *VersionEdit, file vfs.File, recWriter *record.Writer) error {
|
||||
w, err := recWriter.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -2,7 +2,7 @@
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package sharedobjcat
|
||||
package remoteobjcat
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -11,28 +11,37 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||
)
|
||||
|
||||
// versionEdit is a modification to the shared object state which can be encoded
|
||||
// VersionEdit is a modification to the remote object state which can be encoded
|
||||
// into a record.
|
||||
//
|
||||
// TODO(radu): consider adding creation and deletion time for debugging purposes.
|
||||
type versionEdit struct {
|
||||
NewObjects []SharedObjectMetadata
|
||||
type VersionEdit struct {
|
||||
NewObjects []RemoteObjectMetadata
|
||||
DeletedObjects []base.DiskFileNum
|
||||
CreatorID objstorage.CreatorID
|
||||
}
|
||||
|
||||
const (
|
||||
// tagNewObject is followed by the FileNum, creator ID, creator FileNum, and
|
||||
// cleanup method.
|
||||
// tagNewObject is followed by the FileNum, creator ID, creator FileNum,
|
||||
// cleanup method, optional new object tags, and ending with a 0 byte.
|
||||
tagNewObject = 1
|
||||
// tagDeletedObject is followed by the FileNum.
|
||||
tagDeletedObject = 2
|
||||
// tagCreatorID is followed by the Creator ID for this store. This ID can
|
||||
// never change.
|
||||
tagCreatorID = 3
|
||||
// tagNewObjectLocator is an optional tag inside the tagNewObject payload. It
|
||||
// is followed by the encoded length of the locator string and the string.
|
||||
tagNewObjectLocator = 4
|
||||
// tagNewObjectCustomName is an optional tag inside the tagNewObject payload.
|
||||
// It is followed by the encoded length of the custom object name string
|
||||
// followed by the string.
|
||||
tagNewObjectCustomName = 5
|
||||
)
|
||||
|
||||
// Object type values. We don't want to encode FileType directly because it is
|
||||
@@ -61,8 +70,8 @@ func fileTypeToObjType(fileType base.FileType) (uint64, error) {
|
||||
}
|
||||
|
||||
// Encode encodes an edit to the specified writer.
|
||||
func (v *versionEdit) Encode(w io.Writer) error {
|
||||
buf := make([]byte, 0, binary.MaxVarintLen64*(len(v.NewObjects)*4+len(v.DeletedObjects)*2+2))
|
||||
func (v *VersionEdit) Encode(w io.Writer) error {
|
||||
buf := make([]byte, 0, binary.MaxVarintLen64*(len(v.NewObjects)*10+len(v.DeletedObjects)*2+2))
|
||||
for _, meta := range v.NewObjects {
|
||||
objType, err := fileTypeToObjType(meta.FileType)
|
||||
if err != nil {
|
||||
@@ -74,6 +83,16 @@ func (v *versionEdit) Encode(w io.Writer) error {
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.CreatorID))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.CreatorFileNum.FileNum()))
|
||||
buf = binary.AppendUvarint(buf, uint64(meta.CleanupMethod))
|
||||
if meta.Locator != "" {
|
||||
buf = binary.AppendUvarint(buf, uint64(tagNewObjectLocator))
|
||||
buf = encodeString(buf, string(meta.Locator))
|
||||
}
|
||||
if meta.CustomObjectName != "" {
|
||||
buf = binary.AppendUvarint(buf, uint64(tagNewObjectCustomName))
|
||||
buf = encodeString(buf, meta.CustomObjectName)
|
||||
}
|
||||
// Append 0 as the terminator for optional new object tags.
|
||||
buf = binary.AppendUvarint(buf, 0)
|
||||
}
|
||||
|
||||
for _, dfn := range v.DeletedObjects {
|
||||
@@ -89,7 +108,7 @@ func (v *versionEdit) Encode(w io.Writer) error {
|
||||
}
|
||||
|
||||
// Decode decodes an edit from the specified reader.
|
||||
func (v *versionEdit) Decode(r io.Reader) error {
|
||||
func (v *VersionEdit) Decode(r io.Reader) error {
|
||||
br, ok := r.(io.ByteReader)
|
||||
if !ok {
|
||||
br = bufio.NewReader(r)
|
||||
@@ -107,6 +126,7 @@ func (v *versionEdit) Decode(r io.Reader) error {
|
||||
switch tag {
|
||||
case tagNewObject:
|
||||
var fileNum, creatorID, creatorFileNum, cleanupMethod uint64
|
||||
var locator, customName string
|
||||
var fileType base.FileType
|
||||
fileNum, err = binary.ReadUvarint(br)
|
||||
if err == nil {
|
||||
@@ -125,13 +145,34 @@ func (v *versionEdit) Decode(r io.Reader) error {
|
||||
if err == nil {
|
||||
cleanupMethod, err = binary.ReadUvarint(br)
|
||||
}
|
||||
for err == nil {
|
||||
var optionalTag uint64
|
||||
optionalTag, err = binary.ReadUvarint(br)
|
||||
if err != nil || optionalTag == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
switch optionalTag {
|
||||
case tagNewObjectLocator:
|
||||
locator, err = decodeString(br)
|
||||
|
||||
case tagNewObjectCustomName:
|
||||
customName, err = decodeString(br)
|
||||
|
||||
default:
|
||||
err = errors.Newf("unknown newObject tag %d", optionalTag)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
v.NewObjects = append(v.NewObjects, SharedObjectMetadata{
|
||||
FileNum: base.FileNum(fileNum).DiskFileNum(),
|
||||
FileType: fileType,
|
||||
CreatorID: objstorage.CreatorID(creatorID),
|
||||
CreatorFileNum: base.FileNum(creatorFileNum).DiskFileNum(),
|
||||
CleanupMethod: objstorage.SharedCleanupMethod(cleanupMethod),
|
||||
v.NewObjects = append(v.NewObjects, RemoteObjectMetadata{
|
||||
FileNum: base.FileNum(fileNum).DiskFileNum(),
|
||||
FileType: fileType,
|
||||
CreatorID: objstorage.CreatorID(creatorID),
|
||||
CreatorFileNum: base.FileNum(creatorFileNum).DiskFileNum(),
|
||||
CleanupMethod: objstorage.SharedCleanupMethod(cleanupMethod),
|
||||
Locator: remote.Locator(locator),
|
||||
CustomObjectName: customName,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -150,8 +191,9 @@ func (v *versionEdit) Decode(r io.Reader) error {
|
||||
}
|
||||
|
||||
default:
|
||||
// Ignore unknown tags.
|
||||
err = errors.Newf("unknown tag %d", tag)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return errCorruptCatalog
|
||||
@@ -162,4 +204,51 @@ func (v *versionEdit) Decode(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errCorruptCatalog = base.CorruptionErrorf("pebble: corrupt shared object catalog")
|
||||
func encodeString(buf []byte, s string) []byte {
|
||||
buf = binary.AppendUvarint(buf, uint64(len(s)))
|
||||
buf = append(buf, []byte(s)...)
|
||||
return buf
|
||||
}
|
||||
|
||||
func decodeString(br io.ByteReader) (string, error) {
|
||||
length, err := binary.ReadUvarint(br)
|
||||
if err != nil || length == 0 {
|
||||
return "", err
|
||||
}
|
||||
buf := make([]byte, length)
|
||||
for i := range buf {
|
||||
buf[i], err = br.ReadByte()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
var errCorruptCatalog = base.CorruptionErrorf("pebble: corrupt remote object catalog")
|
||||
|
||||
// Apply the version edit to a creator ID and a map of objects.
|
||||
func (v *VersionEdit) Apply(
|
||||
creatorID *objstorage.CreatorID, objects map[base.DiskFileNum]RemoteObjectMetadata,
|
||||
) error {
|
||||
if v.CreatorID.IsSet() {
|
||||
*creatorID = v.CreatorID
|
||||
}
|
||||
for _, meta := range v.NewObjects {
|
||||
if invariants.Enabled {
|
||||
if _, exists := objects[meta.FileNum]; exists {
|
||||
return errors.AssertionFailedf("version edit adds existing object %s", meta.FileNum)
|
||||
}
|
||||
}
|
||||
objects[meta.FileNum] = meta
|
||||
}
|
||||
for _, fileNum := range v.DeletedObjects {
|
||||
if invariants.Enabled {
|
||||
if _, exists := objects[fileNum]; !exists {
|
||||
return errors.AssertionFailedf("version edit deletes non-existent object %s", fileNum)
|
||||
}
|
||||
}
|
||||
delete(objects, fileNum)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
255
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared.go
generated
vendored
255
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared.go
generated
vendored
@@ -1,255 +0,0 @@
|
||||
// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
|
||||
// of this source code is governed by a BSD-style license that can be found in
|
||||
// the LICENSE file.
|
||||
|
||||
package objstorageprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/invariants"
|
||||
"github.com/cockroachdb/pebble/objstorage"
|
||||
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedobjcat"
|
||||
"github.com/cockroachdb/pebble/objstorage/shared"
|
||||
)
|
||||
|
||||
// sharedSubsystem contains the provider fields related to shared storage.
|
||||
// All fields remain unset if shared storage is not configured.
|
||||
type sharedSubsystem struct {
|
||||
catalog *sharedobjcat.Catalog
|
||||
|
||||
// checkRefsOnOpen controls whether we check the ref marker file when opening
|
||||
// an object. Normally this is true when invariants are enabled (but the provider
|
||||
// test tweaks this field).
|
||||
checkRefsOnOpen bool
|
||||
|
||||
// initialized guards access to the creatorID field.
|
||||
initialized atomic.Bool
|
||||
creatorID objstorage.CreatorID
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
func (ss *sharedSubsystem) init(creatorID objstorage.CreatorID) {
|
||||
ss.initOnce.Do(func() {
|
||||
ss.creatorID = creatorID
|
||||
ss.initialized.Store(true)
|
||||
})
|
||||
}
|
||||
|
||||
func (p *provider) sharedStorage() shared.Storage {
|
||||
return p.st.Shared.Storage
|
||||
}
|
||||
|
||||
// sharedInit initializes the shared object subsystem (if configured) and finds
|
||||
// any shared objects.
|
||||
func (p *provider) sharedInit() error {
|
||||
if p.sharedStorage() == nil {
|
||||
return nil
|
||||
}
|
||||
catalog, contents, err := sharedobjcat.Open(p.st.FS, p.st.FSDirName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "pebble: could not open shared object catalog")
|
||||
}
|
||||
p.shared.catalog = catalog
|
||||
p.shared.checkRefsOnOpen = invariants.Enabled
|
||||
|
||||
// The creator ID may or may not be initialized yet.
|
||||
if contents.CreatorID.IsSet() {
|
||||
p.shared.init(contents.CreatorID)
|
||||
p.st.Logger.Infof("shared storage configured; creatorID = %s", contents.CreatorID)
|
||||
} else {
|
||||
p.st.Logger.Infof("shared storage configured; no creatorID yet")
|
||||
}
|
||||
|
||||
for _, meta := range contents.Objects {
|
||||
o := objstorage.ObjectMetadata{
|
||||
DiskFileNum: meta.FileNum,
|
||||
FileType: meta.FileType,
|
||||
}
|
||||
o.Shared.CreatorID = meta.CreatorID
|
||||
o.Shared.CreatorFileNum = meta.CreatorFileNum
|
||||
o.Shared.CleanupMethod = meta.CleanupMethod
|
||||
p.mu.knownObjects[o.DiskFileNum] = o
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetCreatorID is part of the objstorage.Provider interface.
|
||||
func (p *provider) SetCreatorID(creatorID objstorage.CreatorID) error {
|
||||
if p.sharedStorage() == nil {
|
||||
return errors.AssertionFailedf("attempt to set CreatorID but shared storage not enabled")
|
||||
}
|
||||
// Note: this call is a cheap no-op if the creator ID was already set. This
|
||||
// call also checks if we are trying to change the ID.
|
||||
if err := p.shared.catalog.SetCreatorID(creatorID); err != nil {
|
||||
return err
|
||||
}
|
||||
if !p.shared.initialized.Load() {
|
||||
p.st.Logger.Infof("shared storage creatorID set to %s", creatorID)
|
||||
p.shared.init(creatorID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedCheckInitialized() error {
|
||||
if p.sharedStorage() == nil {
|
||||
return errors.Errorf("shared object support not configured")
|
||||
}
|
||||
if !p.shared.initialized.Load() {
|
||||
return errors.Errorf("shared object support not available: shared creator ID not yet set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedSync() error {
|
||||
batch := func() sharedobjcat.Batch {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
res := p.mu.shared.catalogBatch.Copy()
|
||||
p.mu.shared.catalogBatch.Reset()
|
||||
return res
|
||||
}()
|
||||
|
||||
if batch.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.shared.catalog.ApplyBatch(batch)
|
||||
if err != nil {
|
||||
// We have to put back the batch (for the next Sync).
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
batch.Append(p.mu.shared.catalogBatch)
|
||||
p.mu.shared.catalogBatch = batch
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedPath(meta objstorage.ObjectMetadata) string {
|
||||
return "shared://" + sharedObjectName(meta)
|
||||
}
|
||||
|
||||
// sharedCreateRef creates a reference marker object.
|
||||
func (p *provider) sharedCreateRef(meta objstorage.ObjectMetadata) error {
|
||||
if meta.Shared.CleanupMethod != objstorage.SharedRefTracking {
|
||||
return nil
|
||||
}
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
writer, err := p.sharedStorage().CreateObject(refName)
|
||||
if err == nil {
|
||||
// The object is empty, just close the writer.
|
||||
err = writer.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating marker object %q", refName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedCreate(
|
||||
_ context.Context,
|
||||
fileType base.FileType,
|
||||
fileNum base.DiskFileNum,
|
||||
opts objstorage.CreateOptions,
|
||||
) (objstorage.Writable, objstorage.ObjectMetadata, error) {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, err
|
||||
}
|
||||
meta := objstorage.ObjectMetadata{
|
||||
DiskFileNum: fileNum,
|
||||
FileType: fileType,
|
||||
}
|
||||
meta.Shared.CreatorID = p.shared.creatorID
|
||||
meta.Shared.CreatorFileNum = fileNum
|
||||
meta.Shared.CleanupMethod = opts.SharedCleanupMethod
|
||||
|
||||
objName := sharedObjectName(meta)
|
||||
writer, err := p.sharedStorage().CreateObject(objName)
|
||||
if err != nil {
|
||||
return nil, objstorage.ObjectMetadata{}, errors.Wrapf(err, "creating object %q", objName)
|
||||
}
|
||||
return &sharedWritable{
|
||||
p: p,
|
||||
meta: meta,
|
||||
storageWriter: writer,
|
||||
}, meta, nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedOpenForReading(
|
||||
ctx context.Context, meta objstorage.ObjectMetadata, opts objstorage.OpenOptions,
|
||||
) (objstorage.Readable, error) {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Verify we have a reference on this object; for performance reasons, we only
|
||||
// do this in testing scenarios.
|
||||
if p.shared.checkRefsOnOpen && meta.Shared.CleanupMethod == objstorage.SharedRefTracking {
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
if _, err := p.sharedStorage().Size(refName); err != nil {
|
||||
if p.sharedStorage().IsNotExistError(err) {
|
||||
if opts.MustExist {
|
||||
p.st.Logger.Fatalf("marker object %q does not exist", refName)
|
||||
// TODO(radu): maybe list references for the object.
|
||||
}
|
||||
return nil, errors.Errorf("marker object %q does not exist", refName)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "checking marker object %q", refName)
|
||||
}
|
||||
}
|
||||
objName := sharedObjectName(meta)
|
||||
reader, size, err := p.sharedStorage().ReadObject(ctx, objName)
|
||||
if err != nil {
|
||||
if opts.MustExist && p.sharedStorage().IsNotExistError(err) {
|
||||
p.st.Logger.Fatalf("object %q does not exist", objName)
|
||||
// TODO(radu): maybe list references for the object.
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return newSharedReadable(reader, size), nil
|
||||
}
|
||||
|
||||
func (p *provider) sharedSize(meta objstorage.ObjectMetadata) (int64, error) {
|
||||
if err := p.sharedCheckInitialized(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
objName := sharedObjectName(meta)
|
||||
return p.sharedStorage().Size(objName)
|
||||
}
|
||||
|
||||
// sharedUnref implements object "removal" with the shared backend. The ref
|
||||
// marker object is removed and the backing object is removed only if there are
|
||||
// no other ref markers.
|
||||
func (p *provider) sharedUnref(meta objstorage.ObjectMetadata) error {
|
||||
if meta.Shared.CleanupMethod == objstorage.SharedNoCleanup {
|
||||
// Never delete objects in this mode.
|
||||
return nil
|
||||
}
|
||||
if p.isProtected(meta.DiskFileNum) {
|
||||
// TODO(radu): we need a mechanism to unref the object when it becomes
|
||||
// unprotected.
|
||||
return nil
|
||||
}
|
||||
|
||||
refName := p.sharedObjectRefName(meta)
|
||||
// Tolerate a not-exists error.
|
||||
if err := p.sharedStorage().Delete(refName); err != nil && !p.sharedStorage().IsNotExistError(err) {
|
||||
return err
|
||||
}
|
||||
otherRefs, err := p.sharedStorage().List(sharedObjectRefPrefix(meta), "" /* delimiter */)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(otherRefs) == 0 {
|
||||
objName := sharedObjectName(meta)
|
||||
if err := p.sharedStorage().Delete(objName); err != nil && !p.sharedStorage().IsNotExistError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user