[WIP] lint a bit, update deps
This commit is contained in:
67
.golangci.yml
Normal file
67
.golangci.yml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
run:
|
||||||
|
tests: false
|
||||||
|
skip-dirs:
|
||||||
|
- data
|
||||||
|
- vendor
|
||||||
|
modules-download-mode: readonly
|
||||||
|
allow-parallel-runners: true
|
||||||
|
go: '1.21'
|
||||||
|
output:
|
||||||
|
sort-results: true
|
||||||
|
linters-settings:
|
||||||
|
gosec:
|
||||||
|
excludes:
|
||||||
|
# Оно не используется для повышения безопасности или каких-то таких секъюрных вещей, оно используется для выборки
|
||||||
|
# произвольного элемента из массива, так что ИДИ НАХУЙ с этой проверкой и не еби мозги липовой безопасностью.
|
||||||
|
- G404 # Insecure random number source (rand)
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- errcheck
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- unused
|
||||||
|
- asasalint
|
||||||
|
- asciicheck
|
||||||
|
- bodyclose
|
||||||
|
- containedctx
|
||||||
|
- contextcheck
|
||||||
|
- decorder
|
||||||
|
- durationcheck
|
||||||
|
- errchkjson
|
||||||
|
- errorlint
|
||||||
|
- exhaustive
|
||||||
|
- exportloopref
|
||||||
|
- gci
|
||||||
|
- ginkgolinter
|
||||||
|
- gocheckcompilerdirectives
|
||||||
|
- gocritic
|
||||||
|
- godot
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- gosec
|
||||||
|
- gosmopolitan
|
||||||
|
- grouper
|
||||||
|
- importas
|
||||||
|
- ireturn
|
||||||
|
- makezero
|
||||||
|
- mirror
|
||||||
|
- nakedret
|
||||||
|
- nilerr
|
||||||
|
- nilnil
|
||||||
|
- nlreturn
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- nonamedreturns
|
||||||
|
- predeclared
|
||||||
|
- promlinter
|
||||||
|
- reassign
|
||||||
|
- revive
|
||||||
|
- rowserrcheck
|
||||||
|
- stylecheck
|
||||||
|
- unconvert
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
- wsl
|
||||||
21
config.go
21
config.go
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -21,7 +20,7 @@ func parseConfig(path string) (myConfig, error) {
|
|||||||
|
|
||||||
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата.
|
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата.
|
||||||
if fileInfo.Size() > 65535 {
|
if fileInfo.Size() > 65535 {
|
||||||
err := errors.New(fmt.Sprintf("Config file %s is too long for config, skipping", path))
|
err := fmt.Errorf("Config file %s is too long for config, skipping", path)
|
||||||
|
|
||||||
return myConfig{}, err
|
return myConfig{}, err
|
||||||
}
|
}
|
||||||
@@ -36,8 +35,11 @@ func parseConfig(path string) (myConfig, error) {
|
|||||||
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
||||||
var sampleConfig myConfig
|
var (
|
||||||
var tmp map[string]interface{}
|
sampleConfig myConfig
|
||||||
|
tmp map[string]interface{}
|
||||||
|
)
|
||||||
|
|
||||||
err = hjson.Unmarshal(buf, &tmp)
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
// Не удалось распарсить.
|
// Не удалось распарсить.
|
||||||
@@ -59,6 +61,7 @@ func parseConfig(path string) (myConfig, error) {
|
|||||||
// Валидируем значения из конфига.
|
// Валидируем значения из конфига.
|
||||||
if sampleConfig.Redis.Server == "" {
|
if sampleConfig.Redis.Server == "" {
|
||||||
sampleConfig.Redis.Server = "localhost"
|
sampleConfig.Redis.Server = "localhost"
|
||||||
|
|
||||||
log.Infof("Redis server is not defined in config, using localhost")
|
log.Infof("Redis server is not defined in config, using localhost")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,19 +70,19 @@ func parseConfig(path string) (myConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sampleConfig.Redis.Channel == "" {
|
if sampleConfig.Redis.Channel == "" {
|
||||||
err := errors.New(fmt.Sprintf("Channel field in config file %s must be set", path))
|
err := fmt.Errorf("Channel field in config file %s must be set", path)
|
||||||
|
|
||||||
return myConfig{}, err
|
return myConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if sampleConfig.Redis.MyChannel == "" {
|
if sampleConfig.Redis.MyChannel == "" {
|
||||||
err := errors.New(fmt.Sprintf("My_channel field in config file %s must be set", path))
|
err := fmt.Errorf("My_channel field in config file %s must be set", path)
|
||||||
|
|
||||||
return myConfig{}, err
|
return myConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if sampleConfig.Telegram.Token == "" {
|
if sampleConfig.Telegram.Token == "" {
|
||||||
return myConfig{}, errors.New(fmt.Sprintf("telegram.token field in config file %s must be set", path))
|
return myConfig{}, fmt.Errorf("telegram.token field in config file %s must be set", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sampleConfig.Loglevel == "" {
|
if sampleConfig.Loglevel == "" {
|
||||||
@@ -89,7 +92,7 @@ func parseConfig(path string) (myConfig, error) {
|
|||||||
// sampleConfig.Log = "" if not set
|
// sampleConfig.Log = "" if not set
|
||||||
|
|
||||||
if sampleConfig.Csign == "" {
|
if sampleConfig.Csign == "" {
|
||||||
err := errors.New(fmt.Sprintf("Csign field in config file %s must be set", path))
|
err := fmt.Errorf("Csign field in config file %s must be set", path)
|
||||||
|
|
||||||
return myConfig{}, err
|
return myConfig{}, err
|
||||||
}
|
}
|
||||||
@@ -99,7 +102,7 @@ func parseConfig(path string) (myConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sampleConfig.DataDir == "" {
|
if sampleConfig.DataDir == "" {
|
||||||
return myConfig{}, errors.New(fmt.Sprintf("Data_dir field in config file %s must be set", path))
|
return myConfig{}, fmt.Errorf("Data_dir field in config file %s must be set", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sampleConfig, err
|
return sampleConfig, err
|
||||||
|
|||||||
10
globals.go
10
globals.go
@@ -9,13 +9,13 @@ import (
|
|||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config - это у нас глобальная штука :)
|
// Config - это у нас глобальная штука.
|
||||||
var config myConfig
|
var config myConfig
|
||||||
|
|
||||||
// To break circular message forwarding we must set some sane default, it can be overridden via config
|
// To break circular message forwarding we must set some sane default, it can be overridden via config.
|
||||||
var forwardMax int64 = 5
|
var forwardMax int64 = 5
|
||||||
|
|
||||||
// Объектики клиента-редиски
|
// Объектики клиента-редиски.
|
||||||
var redisClient *redis.Client
|
var redisClient *redis.Client
|
||||||
var subscriber *redis.PubSub
|
var subscriber *redis.PubSub
|
||||||
|
|
||||||
@@ -25,10 +25,10 @@ var ctx = context.Background()
|
|||||||
// Ставится в true, если мы получили сигнал на выключение
|
// Ставится в true, если мы получили сигнал на выключение
|
||||||
var shutdown = false
|
var shutdown = false
|
||||||
|
|
||||||
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов
|
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов.
|
||||||
var sigChan = make(chan os.Signal, 1)
|
var sigChan = make(chan os.Signal, 1)
|
||||||
|
|
||||||
// Мапка с открытыми дескрипторами баз с настройками
|
// Мапка с открытыми дескрипторами баз с настройками.
|
||||||
var settingsDB = make(map[string]*pebble.DB)
|
var settingsDB = make(map[string]*pebble.DB)
|
||||||
|
|
||||||
var tg echotron.API
|
var tg echotron.API
|
||||||
|
|||||||
17
go.mod
17
go.mod
@@ -3,9 +3,9 @@ module aleesa-telegram-go
|
|||||||
go 1.19
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/NicoNex/echotron/v3 v3.28.0
|
github.com/NicoNex/echotron/v3 v3.29.0
|
||||||
github.com/cockroachdb/errors v1.11.1
|
github.com/cockroachdb/errors v1.11.1
|
||||||
github.com/cockroachdb/pebble v1.0.0
|
github.com/cockroachdb/pebble v1.1.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/go-redis/redis/v8 v8.11.5
|
github.com/go-redis/redis/v8 v8.11.5
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible
|
github.com/hjson/hjson-go v3.3.0+incompatible
|
||||||
@@ -18,21 +18,22 @@ require (
|
|||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
||||||
github.com/cockroachdb/redact v1.1.5 // indirect
|
github.com/cockroachdb/redact v1.1.5 // indirect
|
||||||
|
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/getsentry/sentry-go v0.26.0 // indirect
|
github.com/getsentry/sentry-go v0.27.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.7 // indirect
|
||||||
github.com/kr/pretty v0.3.1 // indirect
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_golang v1.18.0 // indirect
|
github.com/prometheus/client_golang v1.18.0 // indirect
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.6.0 // indirect
|
||||||
github.com/prometheus/common v0.46.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
38
go.sum
38
go.sum
@@ -1,20 +1,22 @@
|
|||||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||||
github.com/NicoNex/echotron/v3 v3.28.0 h1:A5uGWlD1iQYJe41XYxmb/sAQ8ZoDSJgbB2+Ix9PDimY=
|
github.com/NicoNex/echotron/v3 v3.29.0 h1:STzeYNauF+h82vTL9QIqIBEvhke/bsxgYKQMCmJKnLM=
|
||||||
github.com/NicoNex/echotron/v3 v3.28.0/go.mod h1:LpP5IyHw0y+DZUZMBgXEDAF9O8feXrQu7w7nlJzzoZI=
|
github.com/NicoNex/echotron/v3 v3.29.0/go.mod h1:LpP5IyHw0y+DZUZMBgXEDAF9O8feXrQu7w7nlJzzoZI=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cockroachdb/datadriven v1.0.3-0.20230801171734-e384cf455877 h1:1MLK4YpFtIEo3ZtMA5C795Wtv5VuUnrXX7mQG+aHg6o=
|
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
|
||||||
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
|
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
|
||||||
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
|
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
|
||||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
||||||
github.com/cockroachdb/pebble v1.0.0 h1:WZWlV/s78glZbY2ylUITDOWSVBD3cLjcWPLRPFbHNYg=
|
github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
|
||||||
github.com/cockroachdb/pebble v1.0.0/go.mod h1:bynZ3gvVyhlvjLI7PT6dmZ7g76xzJ7HpxfjgkzCGz6s=
|
github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
|
||||||
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
||||||
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||||
|
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
|
||||||
|
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
@@ -22,8 +24,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/getsentry/sentry-go v0.26.0 h1:IX3++sF6/4B5JcevhdZfdKIHfyvMmAq/UnqcyT2H6mA=
|
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||||
github.com/getsentry/sentry-go v0.26.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||||
@@ -31,13 +33,13 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI=
|
github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI=
|
||||||
github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -53,10 +55,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||||
github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
|
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||||
github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
|
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
@@ -72,8 +74,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||||
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
|
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@@ -89,8 +91,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import (
|
|||||||
|
|
||||||
// telego msg parser
|
// telego msg parser
|
||||||
|
|
||||||
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными
|
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными.
|
||||||
func redisMsgParser(msg string) {
|
func redisMsgParser(msg string) {
|
||||||
if shutdown {
|
if shutdown {
|
||||||
// Если мы завершаем работу программы, то нам ничего обрабатывать не надо
|
// Если мы завершаем работу программы, то нам ничего обрабатывать не надо.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,48 +24,56 @@ func redisMsgParser(msg string) {
|
|||||||
|
|
||||||
if err := json.Unmarshal([]byte(msg), &j); err != nil {
|
if err := json.Unmarshal([]byte(msg), &j); err != nil {
|
||||||
log.Warnf("Unable to to parse message from redis channel: %s", err)
|
log.Warnf("Unable to to parse message from redis channel: %s", err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate our j
|
// Validate our j
|
||||||
if exist := j.From; exist == "" {
|
if exist := j.From; exist == "" {
|
||||||
log.Warnf("Incorrect msg from redis, no from field: %s", msg)
|
log.Warnf("Incorrect msg from redis, no from field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist := j.Chatid; exist == "" {
|
if exist := j.Chatid; exist == "" {
|
||||||
log.Warnf("Incorrect msg from redis, no chatid field: %s", msg)
|
log.Warnf("Incorrect msg from redis, no chatid field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist := j.Userid; exist == "" {
|
if exist := j.Userid; exist == "" {
|
||||||
log.Warnf("Incorrect msg from redis, no userid field: %s", msg)
|
log.Warnf("Incorrect msg from redis, no userid field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist := j.Message; exist == "" {
|
if exist := j.Message; exist == "" {
|
||||||
log.Warnf("Incorrect msg from redis, no message field: %s", msg)
|
log.Warnf("Incorrect msg from redis, no message field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist := j.Plugin; exist == "" {
|
if exist := j.Plugin; exist == "" {
|
||||||
log.Warnf("Incorrect msg from redis, no plugin field: %s", msg)
|
log.Warnf("Incorrect msg from redis, no plugin field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if exist := j.Mode; exist == "" {
|
if exist := j.Mode; exist == "" {
|
||||||
log.Warnf("Incorrect msg from redis, no mode field: %s", msg)
|
log.Warnf("Incorrect msg from redis, no mode field: %s", msg)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет
|
// j.Misc.Answer может и не быть, тогда ответа на такое сообщение не будет
|
||||||
if j.Misc.Answer == 0 {
|
if j.Misc.Answer == 0 {
|
||||||
log.Debug("Field Misc->Answer = 0, skipping message")
|
log.Debug("Field Misc->Answer = 0, skipping message")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// j.Misc.BotNick тоже можно не передавать, тогда будет записана пустая строка
|
// j.Misc.BotNick тоже можно не передавать, тогда будет записана пустая строка
|
||||||
// j.Misc.CSign если нам его не передали, возьмём значение из конфига
|
// j.Misc.CSign если нам его не передали, возьмём значение из конфига. (но по идее нам оно тут не нужнО.)
|
||||||
if exist := j.Misc.Csign; exist == "" {
|
if exist := j.Misc.Csign; exist == "" {
|
||||||
j.Misc.Csign = config.Csign
|
j.Misc.Csign = config.Csign
|
||||||
}
|
}
|
||||||
@@ -82,6 +90,7 @@ func redisMsgParser(msg string) {
|
|||||||
// Отвалидировались, теперь вернёмся к нашим баранам.
|
// Отвалидировались, теперь вернёмся к нашим баранам.
|
||||||
|
|
||||||
var opts *echotron.MessageOptions
|
var opts *echotron.MessageOptions
|
||||||
|
|
||||||
chatid, err := strconv.ParseInt(j.Chatid, 10, 64)
|
chatid, err := strconv.ParseInt(j.Chatid, 10, 64)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -100,8 +109,6 @@ func redisMsgParser(msg string) {
|
|||||||
log.Errorf("Unable to send message to telegram api: %s", err)
|
log.Errorf("Unable to send message to telegram api: %s", err)
|
||||||
log.Errorf("Response dump: %s", spew.Sdump(resp))
|
log.Errorf("Response dump: %s", spew.Sdump(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// telegramMsgParser парсит ивент, прилетевший из bot api.
|
// telegramMsgParser парсит ивент, прилетевший из bot api.
|
||||||
@@ -128,11 +135,14 @@ func telegramMsgParser(msg *echotron.Update) {
|
|||||||
// TODO: проверить, надо ли прощаться с ушедшим участником чата и попрощаться, если надо
|
// TODO: проверить, надо ли прощаться с ушедшим участником чата и попрощаться, если надо
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rmsg rMsg
|
||||||
|
|
||||||
switch msg.Message.Chat.Type {
|
switch msg.Message.Chat.Type {
|
||||||
case "private":
|
case "private":
|
||||||
// handle private messages
|
// handle private messages
|
||||||
// всегда ставим флажок, что надо отвечать.
|
rmsg.Misc.Answer = 1
|
||||||
// если фраза является командой - засылаем её в парсер команд
|
// если фраза является командой - засылаем её в парсер команд
|
||||||
|
|
||||||
// Засылаем фразу в misc-канал (в роутер)
|
// Засылаем фразу в misc-канал (в роутер)
|
||||||
case "group", "supergroup":
|
case "group", "supergroup":
|
||||||
// handle public (super)group messages
|
// handle public (super)group messages
|
||||||
@@ -144,11 +154,14 @@ func telegramMsgParser(msg *echotron.Update) {
|
|||||||
// Здесь же, пытаемся убрать из фразы ник или имя бота
|
// Здесь же, пытаемся убрать из фразы ник или имя бота
|
||||||
// Засылаем фразу в misc-канал (в роутер)
|
// Засылаем фразу в misc-канал (в роутер)
|
||||||
case "channel":
|
case "channel":
|
||||||
// Тут мы ничего сделать не можем
|
// Тут мы ничего сделать не можем.
|
||||||
|
} //nolint:wsl
|
||||||
|
}
|
||||||
|
|
||||||
}
|
func cmdParser(cmd string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StoreKV сохраняет в указанной бд ключ и значение
|
// StoreKV сохраняет в указанной бд ключ и значение.
|
||||||
func StoreKV(db *pebble.DB, key string, value string) error {
|
func StoreKV(db *pebble.DB, key string, value string) error {
|
||||||
var kArray = []byte(key)
|
var kArray = []byte(key)
|
||||||
var vArray = []byte(value)
|
var vArray = []byte(value)
|
||||||
@@ -40,7 +40,7 @@ func FetchV(db *pebble.DB, key string) (string, error) {
|
|||||||
return valueString, err
|
return valueString, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Достанем настройку из БД с настройками
|
// getSetting достаёт настройку из БД с настройками.
|
||||||
func getSetting(chatID string, setting string) string {
|
func getSetting(chatID string, setting string) string {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ func getSetting(chatID string, setting string) string {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраним настройку в БД с настройками
|
// saveSetting сохраняет настройку в БД с настройками
|
||||||
func saveSetting(chatID string, setting string, value string) error {
|
func saveSetting(chatID string, setting string, value string) error {
|
||||||
var chatHash = sha256.Sum256([]byte(chatID))
|
var chatHash = sha256.Sum256([]byte(chatID))
|
||||||
var database = fmt.Sprintf("settings_db/%x", chatHash)
|
var database = fmt.Sprintf("settings_db/%x", chatHash)
|
||||||
|
|||||||
6
types.go
6
types.go
@@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
// Конфиг
|
// myConfig структура, описывающая формат Конфига.
|
||||||
type myConfig struct {
|
type myConfig struct {
|
||||||
Redis struct {
|
Redis struct {
|
||||||
Server string `json:"server,omitempty"`
|
Server string `json:"server,omitempty"`
|
||||||
@@ -20,7 +20,7 @@ type myConfig struct {
|
|||||||
DataDir string `json:"data_dir,omitempty"`
|
DataDir string `json:"data_dir,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Входящее сообщение из pubsub-канала redis-ки
|
// rMsg структура, описывающая формат входящего сообщения из pubsub-канала redis-ки.
|
||||||
type rMsg struct {
|
type rMsg struct {
|
||||||
From string `json:"from,omitempty"`
|
From string `json:"from,omitempty"`
|
||||||
Chatid string `json:"chatid,omitempty"`
|
Chatid string `json:"chatid,omitempty"`
|
||||||
@@ -39,7 +39,7 @@ type rMsg struct {
|
|||||||
} `json:"Misc"`
|
} `json:"Misc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Исходящее сообщение в pubsub-канал redis-ки
|
// sMsg структура, описывающая исходящее сообщение в pubsub-канал redis-ки.
|
||||||
type sMsg struct {
|
type sMsg struct {
|
||||||
From string `json:"from"`
|
From string `json:"from"`
|
||||||
Chatid string `json:"chatid"`
|
Chatid string `json:"chatid"`
|
||||||
|
|||||||
29
util.go
29
util.go
@@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -12,6 +11,7 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// readConfig считывает и парсит конфиг-файл.
|
||||||
func readConfig() {
|
func readConfig() {
|
||||||
configLoaded := false
|
configLoaded := false
|
||||||
executablePath, err := os.Executable()
|
executablePath, err := os.Executable()
|
||||||
@@ -40,27 +40,33 @@ func readConfig() {
|
|||||||
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
|
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
|
||||||
if fileInfo.Size() > 65535 {
|
if fileInfo.Size() > 65535 {
|
||||||
log.Warnf("Config file %s is too long for config, skipping", location)
|
log.Warnf("Config file %s is too long for config, skipping", location)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := ioutil.ReadFile(location)
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
// Не удалось прочитать, попробуем следующего кандидата
|
// Не удалось прочитать, попробуем следующего кандидата
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip reading config file %s: %s", location, err)
|
log.Warnf("Skip reading config file %s: %s", location, err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
||||||
var sampleConfig myConfig
|
var (
|
||||||
var tmp map[string]interface{}
|
sampleConfig myConfig
|
||||||
|
tmp map[string]interface{}
|
||||||
|
)
|
||||||
|
|
||||||
err = hjson.Unmarshal(buf, &tmp)
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
// Не удалось распарсить - попробуем следующего кандидата
|
// Не удалось распарсить - попробуем следующего кандидата
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,17 +75,20 @@ func readConfig() {
|
|||||||
// Не удалось преобразовать map-ку в json
|
// Не удалось преобразовать map-ку в json
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
||||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Валидируем значения из конфига
|
// Валидируем значения из конфига.
|
||||||
if sampleConfig.Redis.Server == "" {
|
if sampleConfig.Redis.Server == "" {
|
||||||
sampleConfig.Redis.Server = "localhost"
|
sampleConfig.Redis.Server = "localhost"
|
||||||
|
|
||||||
log.Infof("Redis server is not defined in config, using localhost")
|
log.Infof("Redis server is not defined in config, using localhost")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +128,9 @@ func readConfig() {
|
|||||||
|
|
||||||
config = sampleConfig
|
config = sampleConfig
|
||||||
configLoaded = true
|
configLoaded = true
|
||||||
|
|
||||||
log.Infof("Using %s as config file", location)
|
log.Infof("Using %s as config file", location)
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +140,7 @@ func readConfig() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения
|
// sigHandler хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения.
|
||||||
func sigHandler() {
|
func sigHandler() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@@ -148,10 +159,10 @@ func sigHandler() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Чтобы не срать в логи ошибками от редиски, проставим shutdown state приложения в true
|
// Чтобы не срать в логи ошибками от редиски, проставим shutdown state приложения в true.
|
||||||
shutdown = true
|
shutdown = true
|
||||||
|
|
||||||
// Отпишемся от всех каналов и закроем коннект к редиске
|
// Отпишемся от всех каналов и закроем коннект к редиске.
|
||||||
if err = subscriber.Unsubscribe(ctx); err != nil {
|
if err = subscriber.Unsubscribe(ctx); err != nil {
|
||||||
log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err)
|
log.Errorf("Unable to unsubscribe from redis channels cleanly: %s", err)
|
||||||
} else {
|
} else {
|
||||||
@@ -164,7 +175,7 @@ func sigHandler() {
|
|||||||
log.Debug("Close redis connection")
|
log.Debug("Close redis connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
// close all telego stuff
|
// close all telego stuff.
|
||||||
|
|
||||||
if len(settingsDB) > 0 {
|
if len(settingsDB) > 0 {
|
||||||
log.Debug("Closing runtime irc channel settings db")
|
log.Debug("Closing runtime irc channel settings db")
|
||||||
|
|||||||
18
vendor/github.com/NicoNex/echotron/v3/types.go
generated
vendored
18
vendor/github.com/NicoNex/echotron/v3/types.go
generated
vendored
@@ -489,11 +489,13 @@ type Chat struct {
|
|||||||
InviteLink string `json:"invite_link,omitempty"`
|
InviteLink string `json:"invite_link,omitempty"`
|
||||||
EmojiStatusCustomEmojiID string `json:"emoji_status_custom_emoji_id,omitempty"`
|
EmojiStatusCustomEmojiID string `json:"emoji_status_custom_emoji_id,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
CustomEmojiStickerSetName string `json:"custom_emoji_sticker_set_name,omitempty"`
|
||||||
AccentColorID int `json:"accent_color_id,omitempty"`
|
AccentColorID int `json:"accent_color_id,omitempty"`
|
||||||
ProfileAccentColorID int `json:"profile_accent_color_id,omitempty"`
|
ProfileAccentColorID int `json:"profile_accent_color_id,omitempty"`
|
||||||
EmojiStatusExpirationDate int `json:"emoji_status_expiration_date,omitempty"`
|
EmojiStatusExpirationDate int `json:"emoji_status_expiration_date,omitempty"`
|
||||||
MessageAutoDeleteTime int `json:"message_auto_delete_time,omitempty"`
|
MessageAutoDeleteTime int `json:"message_auto_delete_time,omitempty"`
|
||||||
SlowModeDelay int `json:"slow_mode_delay,omitempty"`
|
SlowModeDelay int `json:"slow_mode_delay,omitempty"`
|
||||||
|
UnrestrictBoostCount int `json:"unrestrict_boost_count,omitempty"`
|
||||||
LinkedChatID int64 `json:"linked_chat_id,omitempty"`
|
LinkedChatID int64 `json:"linked_chat_id,omitempty"`
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
IsForum bool `json:"is_forum,omitempty"`
|
IsForum bool `json:"is_forum,omitempty"`
|
||||||
@@ -553,10 +555,12 @@ type Message struct {
|
|||||||
UsersShared *UsersShared `json:"users_shared,omitempty"`
|
UsersShared *UsersShared `json:"users_shared,omitempty"`
|
||||||
ChatShared *ChatShared `json:"chat_shared,omitempty"`
|
ChatShared *ChatShared `json:"chat_shared,omitempty"`
|
||||||
Story *Story `json:"story,omitempty"`
|
Story *Story `json:"story,omitempty"`
|
||||||
|
ReplyToStory *Story `json:"reply_to_story,omitempty"`
|
||||||
ExternalReply *ExternalReplyInfo `json:"external_reply,omitempty"`
|
ExternalReply *ExternalReplyInfo `json:"external_reply,omitempty"`
|
||||||
Quote *TextQuote `json:"quote,omitempty"`
|
Quote *TextQuote `json:"quote,omitempty"`
|
||||||
LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"`
|
LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"`
|
||||||
ForwardOrigin *MessageOrigin `json:"forward_origin,omitempty"`
|
ForwardOrigin *MessageOrigin `json:"forward_origin,omitempty"`
|
||||||
|
BoostAdded *ChatBoostAdded `json:"boost_added,omitempty"`
|
||||||
MediaGroupID string `json:"media_group_id,omitempty"`
|
MediaGroupID string `json:"media_group_id,omitempty"`
|
||||||
ConnectedWebsite string `json:"connected_website,omitempty"`
|
ConnectedWebsite string `json:"connected_website,omitempty"`
|
||||||
NewChatTitle string `json:"new_chat_title,omitempty"`
|
NewChatTitle string `json:"new_chat_title,omitempty"`
|
||||||
@@ -575,6 +579,7 @@ type Message struct {
|
|||||||
Date int `json:"date"`
|
Date int `json:"date"`
|
||||||
MigrateToChatID int `json:"migrate_to_chat_id,omitempty"`
|
MigrateToChatID int `json:"migrate_to_chat_id,omitempty"`
|
||||||
EditDate int `json:"edit_date,omitempty"`
|
EditDate int `json:"edit_date,omitempty"`
|
||||||
|
SenderBoostCount int `json:"sender_boost_count,omitempty"`
|
||||||
DeleteChatPhoto bool `json:"delete_chat_photo,omitempty"`
|
DeleteChatPhoto bool `json:"delete_chat_photo,omitempty"`
|
||||||
IsTopicMessage bool `json:"is_topic_message,omitempty"`
|
IsTopicMessage bool `json:"is_topic_message,omitempty"`
|
||||||
IsAutomaticForward bool `json:"is_automatic_forward,omitempty"`
|
IsAutomaticForward bool `json:"is_automatic_forward,omitempty"`
|
||||||
@@ -1173,6 +1178,11 @@ type ChatJoinRequest struct {
|
|||||||
UserChatID int64 `json:"user_chat_id"`
|
UserChatID int64 `json:"user_chat_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChatBoostAdded represents a service message about a user boosting a chat.
|
||||||
|
type ChatBoostAdded struct {
|
||||||
|
BoostCount int `json:"boost_count"`
|
||||||
|
}
|
||||||
|
|
||||||
// ForumTopicCreated represents a service message about a new forum topic created in the chat.
|
// ForumTopicCreated represents a service message about a new forum topic created in the chat.
|
||||||
type ForumTopicCreated struct {
|
type ForumTopicCreated struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -1238,9 +1248,11 @@ type ChatShared struct {
|
|||||||
ChatID int64 `json:"chat_id"`
|
ChatID int64 `json:"chat_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Story represents a message about a forwarded story in the chat.
|
// Story represents a story.
|
||||||
// Currently holds no information.
|
type Story struct {
|
||||||
type Story struct{}
|
Chat Chat `json:"chat"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
type ReactionType interface {
|
type ReactionType interface {
|
||||||
ImplementsReactionType()
|
ImplementsReactionType()
|
||||||
|
|||||||
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.
|
# Profiling artifacts.
|
||||||
cpu.*.prof
|
cpu.*.prof
|
||||||
heap.prof
|
heap.prof
|
||||||
mutex.prof
|
mutex.prof
|
||||||
|
coverprofile.out
|
||||||
# Testing artifacts
|
# Testing artifacts
|
||||||
meta.*.test
|
meta.*.test
|
||||||
|
|||||||
37
vendor/github.com/cockroachdb/pebble/Makefile
generated
vendored
37
vendor/github.com/cockroachdb/pebble/Makefile
generated
vendored
@@ -4,7 +4,8 @@ GOFLAGS :=
|
|||||||
STRESSFLAGS :=
|
STRESSFLAGS :=
|
||||||
TAGS := invariants
|
TAGS := invariants
|
||||||
TESTS := .
|
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
|
.PHONY: all
|
||||||
all:
|
all:
|
||||||
@@ -15,7 +16,10 @@ all:
|
|||||||
@echo " make stressrace"
|
@echo " make stressrace"
|
||||||
@echo " make stressmeta"
|
@echo " make stressmeta"
|
||||||
@echo " make crossversion-meta"
|
@echo " make crossversion-meta"
|
||||||
|
@echo " make testcoverage"
|
||||||
@echo " make mod-update"
|
@echo " make mod-update"
|
||||||
|
@echo " make generate"
|
||||||
|
@echo " make generate-test-data"
|
||||||
@echo " make clean"
|
@echo " make clean"
|
||||||
|
|
||||||
override testflags :=
|
override testflags :=
|
||||||
@@ -23,6 +27,10 @@ override testflags :=
|
|||||||
test:
|
test:
|
||||||
${GO} test -tags '$(TAGS)' ${testflags} -run ${TESTS} ${PKG}
|
${GO} test -tags '$(TAGS)' ${testflags} -run ${TESTS} ${PKG}
|
||||||
|
|
||||||
|
.PHONY: testcoverage
|
||||||
|
testcoverage:
|
||||||
|
${GO} test -tags '$(TAGS)' ${testflags} -run ${TESTS} ${PKG} -coverprofile ${COVER_PROFILE}
|
||||||
|
|
||||||
.PHONY: testrace
|
.PHONY: testrace
|
||||||
testrace: testflags += -race -timeout 20m
|
testrace: testflags += -race -timeout 20m
|
||||||
testrace: test
|
testrace: test
|
||||||
@@ -34,6 +42,10 @@ testmsan: export CC=clang
|
|||||||
testmsan: testflags += -msan -timeout 20m
|
testmsan: testflags += -msan -timeout 20m
|
||||||
testmsan: test
|
testmsan: test
|
||||||
|
|
||||||
|
.PHONY: testobjiotracing
|
||||||
|
testobjiotracing:
|
||||||
|
${GO} test -tags '$(TAGS) pebble_obj_io_tracing' ${testflags} -run ${TESTS} ./objstorage/objstorageprovider/objiotracing
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
${GO} test -tags '$(TAGS)' ${testflags} -run ${TESTS} ./internal/lint
|
${GO} test -tags '$(TAGS)' ${testflags} -run ${TESTS} ./internal/lint
|
||||||
@@ -51,20 +63,31 @@ stressmeta: stress
|
|||||||
|
|
||||||
.PHONY: crossversion-meta
|
.PHONY: crossversion-meta
|
||||||
crossversion-meta:
|
crossversion-meta:
|
||||||
git checkout ${LATEST_RELEASE}; \
|
git checkout ${PREV_RELEASE}; \
|
||||||
${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/${LATEST_RELEASE}.test'; \
|
${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/${PREV_RELEASE}.test'; \
|
||||||
git checkout -; \
|
git checkout -; \
|
||||||
${GO} test -c ./internal/metamorphic -o './internal/metamorphic/crossversion/head.test'; \
|
${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
|
.PHONY: stress-crossversion
|
||||||
stress-crossversion:
|
stress-crossversion:
|
||||||
STRESS=1 ./scripts/run-crossversion-meta.sh crl-release-21.2 crl-release-22.1 crl-release-22.2 master
|
STRESS=1 ./scripts/run-crossversion-meta.sh crl-release-21.2 crl-release-22.1 crl-release-22.2 crl-release-23.1 master
|
||||||
|
|
||||||
.PHONY: generate
|
.PHONY: generate
|
||||||
generate:
|
generate:
|
||||||
${GO} generate ${PKG}
|
${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:
|
mod-update:
|
||||||
${GO} get -u
|
${GO} get -u
|
||||||
${GO} mod tidy
|
${GO} mod tidy
|
||||||
@@ -92,9 +115,11 @@ endif
|
|||||||
@${GO} mod tidy
|
@${GO} mod tidy
|
||||||
$(MAKE) git-clean-check
|
$(MAKE) git-clean-check
|
||||||
|
|
||||||
|
# TODO(radu): switch back to @latest once bogus doc changes are
|
||||||
|
# addressed; see https://github.com/cockroachdb/crlfmt/pull/44
|
||||||
.PHONY: format
|
.PHONY: format
|
||||||
format:
|
format:
|
||||||
go install github.com/cockroachdb/crlfmt@latest && crlfmt -w -tab 2 .
|
go install github.com/cockroachdb/crlfmt@44a36ec7 && crlfmt -w -tab 2 .
|
||||||
|
|
||||||
.PHONY: format-check
|
.PHONY: format-check
|
||||||
format-check:
|
format-check:
|
||||||
|
|||||||
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/)
|
#### [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
|
tombstones, table-level bloom filters, and updates to the MANIFEST
|
||||||
format.
|
format.
|
||||||
|
|
||||||
Pebble intentionally does not aspire to include every feature in
|
Pebble intentionally does not aspire to include every feature in RocksDB and
|
||||||
RocksDB and is specifically targetting the use case and feature set
|
specifically targets the use case and feature set needed by CockroachDB:
|
||||||
needed by CockroachDB:
|
|
||||||
|
|
||||||
* Block-based tables
|
* Block-based tables
|
||||||
* Checkpoints
|
* Checkpoints
|
||||||
@@ -68,6 +67,17 @@ Pebble offers several improvements over RocksDB:
|
|||||||
* Faster commit pipeline that achieves better concurrency.
|
* Faster commit pipeline that achieves better concurrency.
|
||||||
* Seamless merged iteration of indexed batches. The mutations in the
|
* Seamless merged iteration of indexed batches. The mutations in the
|
||||||
batch conceptually occupy another memtable level.
|
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.
|
* Smaller, more approachable code base.
|
||||||
|
|
||||||
See the [Pebble vs RocksDB: Implementation
|
See the [Pebble vs RocksDB: Implementation
|
||||||
@@ -100,10 +110,68 @@ known incompatibilities.
|
|||||||
format.
|
format.
|
||||||
* Plain table format. Pebble does not support the plain table sstable
|
* Plain table format. Pebble does not support the plain table sstable
|
||||||
format.
|
format.
|
||||||
* SSTable format version 3 and 4. Pebble does not currently support
|
* SSTable format version 3 and 4. Pebble does not support version 3
|
||||||
version 3 and version 4 format sstables. The sstable format version
|
and version 4 format sstables. The sstable format version is
|
||||||
is controlled by the `BlockBasedTableOptions::format_version`
|
controlled by the `BlockBasedTableOptions::format_version` option.
|
||||||
option. See [#97](https://github.com/cockroachdb/pebble/issues/97).
|
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
|
## Pedigree
|
||||||
|
|
||||||
|
|||||||
317
vendor/github.com/cockroachdb/pebble/batch.go
generated
vendored
317
vendor/github.com/cockroachdb/pebble/batch.go
generated
vendored
@@ -41,10 +41,10 @@ const (
|
|||||||
var ErrNotIndexed = errors.New("pebble: batch not indexed")
|
var ErrNotIndexed = errors.New("pebble: batch not indexed")
|
||||||
|
|
||||||
// ErrInvalidBatch indicates that a batch is invalid or otherwise corrupted.
|
// 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.
|
// 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
|
// DeferredBatchOp represents a batch operation (eg. set, merge, delete) that is
|
||||||
// being inserted into the batch. Indexing is not performed on the specified key
|
// 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
|
// WAL, and thus stable. New record kinds may be added, but the existing ones
|
||||||
// will not be modified.
|
// will not be modified.
|
||||||
type Batch struct {
|
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:
|
// Data is the wire format of a batch's log entry:
|
||||||
// - 8 bytes for a sequence number of the first batch element,
|
// - 8 bytes for a sequence number of the first batch element,
|
||||||
// or zeroes if the batch has not yet been applied,
|
// or zeroes if the batch has not yet been applied,
|
||||||
@@ -260,10 +267,12 @@ type Batch struct {
|
|||||||
// memtable.
|
// memtable.
|
||||||
flushable *flushableBatch
|
flushable *flushableBatch
|
||||||
|
|
||||||
// ingestedSSTBatch indicates that the batch contains one or more key kinds
|
// minimumFormatMajorVersion indicates the format major version required in
|
||||||
// of InternalKeyKindIngestSST. If the batch contains key kinds of IngestSST
|
// order to commit this batch. If an operation requires a particular format
|
||||||
// then it will only contain key kinds of IngestSST.
|
// major version, it ratchets the batch's minimumFormatMajorVersion. When
|
||||||
ingestedSSTBatch bool
|
// 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
|
// Synchronous Apply uses the commit WaitGroup for both publishing the
|
||||||
// seqnum and waiting for the WAL fsync (if needed). Asynchronous
|
// seqnum and waiting for the WAL fsync (if needed). Asynchronous
|
||||||
@@ -285,7 +294,22 @@ type Batch struct {
|
|||||||
commitStats BatchCommitStats
|
commitStats BatchCommitStats
|
||||||
|
|
||||||
commitErr error
|
commitErr error
|
||||||
applied uint32 // updated atomically
|
|
||||||
|
// 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.
|
// BatchCommitStats exposes stats related to committing a batch.
|
||||||
@@ -312,7 +336,11 @@ type BatchCommitStats struct {
|
|||||||
// commitPipeline.Commit.
|
// commitPipeline.Commit.
|
||||||
SemaphoreWaitDuration time.Duration
|
SemaphoreWaitDuration time.Duration
|
||||||
// WALQueueWaitDuration is the wait time for allocating memory blocks in the
|
// 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
|
WALQueueWaitDuration time.Duration
|
||||||
// MemTableWriteStallDuration is the wait caused by a write stall due to too
|
// MemTableWriteStallDuration is the wait caused by a write stall due to too
|
||||||
// many memtables (due to not flushing fast enough).
|
// many memtables (due to not flushing fast enough).
|
||||||
@@ -356,6 +384,14 @@ func newBatch(db *DB) *Batch {
|
|||||||
return b
|
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 {
|
func newIndexedBatch(db *DB, comparer *Comparer) *Batch {
|
||||||
i := indexedBatchPool.Get().(*indexedBatch)
|
i := indexedBatchPool.Get().(*indexedBatch)
|
||||||
i.batch.cmp = comparer.Compare
|
i.batch.cmp = comparer.Compare
|
||||||
@@ -367,6 +403,14 @@ func newIndexedBatch(db *DB, comparer *Comparer) *Batch {
|
|||||||
return &i.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
|
// 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
|
// 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
|
// 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
|
b.memTableSize = 0
|
||||||
if len(b.data) < batchHeaderLen {
|
if len(b.data) < batchHeaderLen {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b.countRangeDels = 0
|
b.countRangeDels = 0
|
||||||
b.countRangeKeys = 0
|
b.countRangeKeys = 0
|
||||||
|
b.minimumFormatMajorVersion = 0
|
||||||
for r := b.Reader(); ; {
|
for r := b.Reader(); ; {
|
||||||
kind, key, value, ok := r.Next()
|
kind, key, value, ok, err := r.Next()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch kind {
|
switch kind {
|
||||||
@@ -423,12 +471,23 @@ func (b *Batch) refreshMemTableSize() {
|
|||||||
b.countRangeDels++
|
b.countRangeDels++
|
||||||
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||||
b.countRangeKeys++
|
b.countRangeKeys++
|
||||||
|
case InternalKeyKindDeleteSized:
|
||||||
|
if b.minimumFormatMajorVersion < FormatDeleteSizedAndObsolete {
|
||||||
|
b.minimumFormatMajorVersion = FormatDeleteSizedAndObsolete
|
||||||
|
}
|
||||||
case InternalKeyKindIngestSST:
|
case InternalKeyKindIngestSST:
|
||||||
|
if b.minimumFormatMajorVersion < FormatFlushableIngest {
|
||||||
|
b.minimumFormatMajorVersion = FormatFlushableIngest
|
||||||
|
}
|
||||||
// This key kind doesn't contribute to the memtable size.
|
// This key kind doesn't contribute to the memtable size.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
b.memTableSize += memTableEntrySize(len(key), len(value))
|
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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
if len(batch.data) < batchHeaderLen {
|
if len(batch.data) < batchHeaderLen {
|
||||||
return base.CorruptionErrorf("pebble: invalid batch")
|
return ErrInvalidBatch
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := len(b.data)
|
offset := len(b.data)
|
||||||
@@ -459,8 +518,11 @@ func (b *Batch) Apply(batch *Batch, _ *WriteOptions) error {
|
|||||||
// order to update the index.
|
// order to update the index.
|
||||||
for iter := BatchReader(b.data[offset:]); len(iter) > 0; {
|
for iter := BatchReader(b.data[offset:]); len(iter) > 0; {
|
||||||
offset := uintptr(unsafe.Pointer(&iter[0])) - uintptr(unsafe.Pointer(&b.data[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 !ok {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch kind {
|
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) {
|
func (b *Batch) prepareDeferredKeyValueRecord(keyLen, valueLen int, kind InternalKeyKind) {
|
||||||
|
if b.committing {
|
||||||
|
panic("pebble: batch already committing")
|
||||||
|
}
|
||||||
if len(b.data) == 0 {
|
if len(b.data) == 0 {
|
||||||
b.init(keyLen + valueLen + 2*binary.MaxVarintLen64 + batchHeaderLen)
|
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) {
|
func (b *Batch) prepareDeferredKeyRecord(keyLen int, kind InternalKeyKind) {
|
||||||
|
if b.committing {
|
||||||
|
panic("pebble: batch already committing")
|
||||||
|
}
|
||||||
if len(b.data) == 0 {
|
if len(b.data) == 0 {
|
||||||
b.init(keyLen + binary.MaxVarintLen64 + batchHeaderLen)
|
b.init(keyLen + binary.MaxVarintLen64 + batchHeaderLen)
|
||||||
}
|
}
|
||||||
@@ -598,6 +666,41 @@ func (b *Batch) prepareDeferredKeyRecord(keyLen int, kind InternalKeyKind) {
|
|||||||
b.data = b.data[:pos+keyLen]
|
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.
|
// 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.
|
// 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
|
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.
|
// 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.
|
// 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() {
|
func (b *Batch) incrementRangeKeysCount() {
|
||||||
b.countRangeKeys++
|
b.countRangeKeys++
|
||||||
|
if b.minimumFormatMajorVersion < FormatRangeKeys {
|
||||||
|
b.minimumFormatMajorVersion = FormatRangeKeys
|
||||||
|
}
|
||||||
if b.index != nil {
|
if b.index != nil {
|
||||||
b.rangeKeys = nil
|
b.rangeKeys = nil
|
||||||
b.rangeKeysSeqNum = 0
|
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
|
// is not reset because for the InternalKeyKindIngestSST the count is the
|
||||||
// number of sstable paths which have been added to the batch.
|
// number of sstable paths which have been added to the batch.
|
||||||
b.memTableSize = origMemTableSize
|
b.memTableSize = origMemTableSize
|
||||||
|
b.minimumFormatMajorVersion = FormatFlushableIngest
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns true if the batch is empty, and false otherwise.
|
// 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.data = data
|
||||||
b.count = uint64(binary.LittleEndian.Uint32(b.countData()))
|
b.count = uint64(binary.LittleEndian.Uint32(b.countData()))
|
||||||
|
var err error
|
||||||
if b.db != nil {
|
if b.db != nil {
|
||||||
// Only track memTableSize for batches that will be committed to the DB.
|
// 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
|
// 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
|
// The returned Iterator observes all of the Batch's existing mutations, but no
|
||||||
// later mutations. Its view can be refreshed via RefreshBatchSnapshot or
|
// later mutations. Its view can be refreshed via RefreshBatchSnapshot or
|
||||||
// SetOptions().
|
// SetOptions().
|
||||||
func (b *Batch) NewIter(o *IterOptions) *Iterator {
|
func (b *Batch) NewIter(o *IterOptions) (*Iterator, error) {
|
||||||
return b.NewIterWithContext(context.Background(), o)
|
return b.NewIterWithContext(context.Background(), o), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIterWithContext is like NewIter, and additionally accepts a context for
|
// 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 {
|
if b.index == nil {
|
||||||
return &Iterator{err: ErrNotIndexed}
|
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
|
// newInternalIter creates a new internalIterator that iterates over the
|
||||||
@@ -1167,38 +1341,40 @@ func (b *Batch) Indexed() bool {
|
|||||||
return b.index != nil
|
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
|
n := batchInitialSize
|
||||||
for n < cap {
|
for n < size {
|
||||||
n *= 2
|
n *= 2
|
||||||
}
|
}
|
||||||
b.data = rawalloc.New(batchHeaderLen, n)
|
if cap(b.data) < n {
|
||||||
b.setCount(0)
|
b.data = rawalloc.New(batchHeaderLen, n)
|
||||||
b.setSeqNum(0)
|
}
|
||||||
b.data = b.data[:batchHeaderLen]
|
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
|
// 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
|
// method if a batch is explicitly being reused. Close automatically takes are
|
||||||
// of releasing resources when appropriate for batches that are internally
|
// of releasing resources when appropriate for batches that are internally
|
||||||
// being reused.
|
// being reused.
|
||||||
func (b *Batch) Reset() {
|
func (b *Batch) Reset() {
|
||||||
b.count = 0
|
// Zero out the struct, retaining only the fields necessary for manual
|
||||||
b.countRangeDels = 0
|
// reuse.
|
||||||
b.countRangeKeys = 0
|
b.batchInternal = batchInternal{
|
||||||
b.memTableSize = 0
|
data: b.data,
|
||||||
b.deferredOp = DeferredBatchOp{}
|
cmp: b.cmp,
|
||||||
b.tombstones = nil
|
formatKey: b.formatKey,
|
||||||
b.tombstonesSeqNum = 0
|
abbreviatedKey: b.abbreviatedKey,
|
||||||
b.rangeKeys = nil
|
index: b.index,
|
||||||
b.rangeKeysSeqNum = 0
|
db: b.db,
|
||||||
b.flushable = nil
|
}
|
||||||
b.commit = sync.WaitGroup{}
|
b.applied.Store(false)
|
||||||
b.fsyncWait = sync.WaitGroup{}
|
|
||||||
b.commitStats = BatchCommitStats{}
|
|
||||||
b.commitErr = nil
|
|
||||||
atomic.StoreUint32(&b.applied, 0)
|
|
||||||
if b.data != nil {
|
if b.data != nil {
|
||||||
if cap(b.data) > batchMaxRetainedSize {
|
if cap(b.data) > batchMaxRetainedSize {
|
||||||
// If the capacity of the buffer is larger than our maximum
|
// If the capacity of the buffer is larger than our maximum
|
||||||
@@ -1209,13 +1385,14 @@ func (b *Batch) Reset() {
|
|||||||
} else {
|
} else {
|
||||||
// Otherwise, reset the buffer for re-use.
|
// Otherwise, reset the buffer for re-use.
|
||||||
b.data = b.data[:batchHeaderLen]
|
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 {
|
if b.index != nil {
|
||||||
b.index.Init(&b.data, b.cmp, b.abbreviatedKey)
|
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) {
|
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 v uint32
|
||||||
var n int
|
var n int
|
||||||
ptr := unsafe.Pointer(&data[0])
|
ptr := unsafe.Pointer(&data[0])
|
||||||
@@ -1349,29 +1531,32 @@ func ReadBatch(repr []byte) (r BatchReader, count uint32) {
|
|||||||
return repr[batchHeaderLen:], count
|
return repr[batchHeaderLen:], count
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the next entry in this batch. The final return value is false
|
// Next returns the next entry in this batch, if there is one. If the reader has
|
||||||
// if the batch is corrupt. The end of batch is reached when len(r)==0.
|
// reached the end of the batch, Next returns ok=false and a nil error. If the
|
||||||
func (r *BatchReader) Next() (kind InternalKeyKind, ukey []byte, value []byte, ok bool) {
|
// 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 {
|
if len(*r) == 0 {
|
||||||
return 0, nil, nil, false
|
return 0, nil, nil, false, nil
|
||||||
}
|
}
|
||||||
kind = InternalKeyKind((*r)[0])
|
kind = InternalKeyKind((*r)[0])
|
||||||
if kind > InternalKeyKindMax {
|
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:])
|
*r, ukey, ok = batchDecodeStr((*r)[1:])
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, nil, nil, false
|
return 0, nil, nil, false, errors.Wrapf(ErrInvalidBatch, "decoding user key")
|
||||||
}
|
}
|
||||||
switch kind {
|
switch kind {
|
||||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
||||||
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete,
|
||||||
|
InternalKeyKindDeleteSized:
|
||||||
*r, value, ok = batchDecodeStr(*r)
|
*r, value, ok = batchDecodeStr(*r)
|
||||||
if !ok {
|
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
|
// Note: batchIter mirrors the implementation of flushableBatchIter. Keep the
|
||||||
@@ -1500,7 +1685,8 @@ func (i *batchIter) value() []byte {
|
|||||||
|
|
||||||
switch InternalKeyKind(data[offset]) {
|
switch InternalKeyKind(data[offset]) {
|
||||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
||||||
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete,
|
||||||
|
InternalKeyKindDeleteSized:
|
||||||
_, value, ok := batchDecodeStr(data[keyEnd:])
|
_, value, ok := batchDecodeStr(data[keyEnd:])
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
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
|
// 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
|
// queue of flushable memtables. Note that the flushable batch takes ownership
|
||||||
// of the batch data.
|
// of the batch data.
|
||||||
func newFlushableBatch(batch *Batch, comparer *Comparer) *flushableBatch {
|
func newFlushableBatch(batch *Batch, comparer *Comparer) (*flushableBatch, error) {
|
||||||
b := &flushableBatch{
|
b := &flushableBatch{
|
||||||
data: batch.data,
|
data: batch.data,
|
||||||
cmp: comparer.Compare,
|
cmp: comparer.Compare,
|
||||||
@@ -1593,8 +1779,11 @@ func newFlushableBatch(batch *Batch, comparer *Comparer) *flushableBatch {
|
|||||||
var index uint32
|
var index uint32
|
||||||
for iter := BatchReader(b.data[batchHeaderLen:]); len(iter) > 0; index++ {
|
for iter := BatchReader(b.data[batchHeaderLen:]); len(iter) > 0; index++ {
|
||||||
offset := uintptr(unsafe.Pointer(&iter[0])) - uintptr(unsafe.Pointer(&b.data[0]))
|
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 !ok {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
entry := flushableBatchEntry{
|
entry := flushableBatchEntry{
|
||||||
@@ -1666,7 +1855,7 @@ func newFlushableBatch(batch *Batch, comparer *Comparer) *flushableBatch {
|
|||||||
}
|
}
|
||||||
fragmentRangeKeys(frag, it, len(rangeKeyOffsets))
|
fragmentRangeKeys(frag, it, len(rangeKeyOffsets))
|
||||||
}
|
}
|
||||||
return b
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *flushableBatch) setSeqNum(seqNum uint64) {
|
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]
|
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 {
|
func (b *flushableBatch) newIter(o *IterOptions) internalIterator {
|
||||||
return &flushableBatchIter{
|
return &flushableBatchIter{
|
||||||
batch: b,
|
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 {
|
func (b *flushableBatch) newFlushIter(o *IterOptions, bytesFlushed *uint64) internalIterator {
|
||||||
return &flushFlushableBatchIter{
|
return &flushFlushableBatchIter{
|
||||||
flushableBatchIter: flushableBatchIter{
|
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 {
|
func (b *flushableBatch) newRangeDelIter(o *IterOptions) keyspan.FragmentIterator {
|
||||||
if len(b.tombstones) == 0 {
|
if len(b.tombstones) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -1747,6 +1939,7 @@ func (b *flushableBatch) newRangeDelIter(o *IterOptions) keyspan.FragmentIterato
|
|||||||
return keyspan.NewIter(b.cmp, b.tombstones)
|
return keyspan.NewIter(b.cmp, b.tombstones)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newRangeKeyIter is part of the flushable interface.
|
||||||
func (b *flushableBatch) newRangeKeyIter(o *IterOptions) keyspan.FragmentIterator {
|
func (b *flushableBatch) newRangeKeyIter(o *IterOptions) keyspan.FragmentIterator {
|
||||||
if len(b.rangeKeys) == 0 {
|
if len(b.rangeKeys) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -1754,17 +1947,23 @@ func (b *flushableBatch) newRangeKeyIter(o *IterOptions) keyspan.FragmentIterato
|
|||||||
return keyspan.NewIter(b.cmp, b.rangeKeys)
|
return keyspan.NewIter(b.cmp, b.rangeKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// containsRangeKeys is part of the flushable interface.
|
||||||
func (b *flushableBatch) containsRangeKeys() bool { return len(b.rangeKeys) > 0 }
|
func (b *flushableBatch) containsRangeKeys() bool { return len(b.rangeKeys) > 0 }
|
||||||
|
|
||||||
|
// inuseBytes is part of the flushable interface.
|
||||||
func (b *flushableBatch) inuseBytes() uint64 {
|
func (b *flushableBatch) inuseBytes() uint64 {
|
||||||
return uint64(len(b.data) - batchHeaderLen)
|
return uint64(len(b.data) - batchHeaderLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// totalBytes is part of the flushable interface.
|
||||||
func (b *flushableBatch) totalBytes() uint64 {
|
func (b *flushableBatch) totalBytes() uint64 {
|
||||||
return uint64(cap(b.data))
|
return uint64(cap(b.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readyForFlush is part of the flushable interface.
|
||||||
func (b *flushableBatch) readyForFlush() bool {
|
func (b *flushableBatch) readyForFlush() bool {
|
||||||
|
// A flushable batch is always ready for flush; it must be flushed together
|
||||||
|
// with the previous memtable.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1944,7 +2143,8 @@ func (i *flushableBatchIter) value() base.LazyValue {
|
|||||||
var ok bool
|
var ok bool
|
||||||
switch kind {
|
switch kind {
|
||||||
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
case InternalKeyKindSet, InternalKeyKindMerge, InternalKeyKindRangeDelete,
|
||||||
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete,
|
||||||
|
InternalKeyKindDeleteSized:
|
||||||
keyEnd := i.offsets[i.index].keyEnd
|
keyEnd := i.offsets[i.index].keyEnd
|
||||||
_, value, ok = batchDecodeStr(i.data[keyEnd:])
|
_, value, ok = batchDecodeStr(i.data[keyEnd:])
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -2081,7 +2281,10 @@ func batchSort(
|
|||||||
rangeKeyIter := b.newRangeKeyIter(nil, math.MaxUint64)
|
rangeKeyIter := b.newRangeKeyIter(nil, math.MaxUint64)
|
||||||
return pointIter, rangeDelIter, rangeKeyIter
|
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)
|
return f.newIter(nil), f.newRangeDelIter(nil), f.newRangeKeyIter(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
52
vendor/github.com/cockroachdb/pebble/checkpoint.go
generated
vendored
52
vendor/github.com/cockroachdb/pebble/checkpoint.go
generated
vendored
@@ -188,7 +188,10 @@ func (d *DB) Checkpoint(
|
|||||||
manifestFileNum := d.mu.versions.manifestFileNum
|
manifestFileNum := d.mu.versions.manifestFileNum
|
||||||
manifestSize := d.mu.versions.manifest.Size()
|
manifestSize := d.mu.versions.manifest.Size()
|
||||||
optionsFileNum := d.optionsFileNum
|
optionsFileNum := d.optionsFileNum
|
||||||
|
virtualBackingFiles := make(map[base.DiskFileNum]struct{})
|
||||||
|
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
|
// Release the manifest and DB.mu so we don't block other operations on
|
||||||
// the database.
|
// the database.
|
||||||
d.mu.versions.logUnlock()
|
d.mu.versions.logUnlock()
|
||||||
@@ -209,11 +212,7 @@ func (d *DB) Checkpoint(
|
|||||||
}
|
}
|
||||||
if ckErr != nil {
|
if ckErr != nil {
|
||||||
// Attempt to cleanup on error.
|
// Attempt to cleanup on error.
|
||||||
paths, _ := fs.List(destDir)
|
_ = fs.RemoveAll(destDir)
|
||||||
for _, path := range paths {
|
|
||||||
_ = fs.Remove(path)
|
|
||||||
}
|
|
||||||
_ = fs.Remove(destDir)
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
dir, ckErr = mkdirAllAndSyncParents(fs, destDir)
|
dir, ckErr = mkdirAllAndSyncParents(fs, destDir)
|
||||||
@@ -254,7 +253,9 @@ func (d *DB) Checkpoint(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var excludedFiles map[deletedFileEntry]*fileMetadata
|
var excludedFiles map[deletedFileEntry]*fileMetadata
|
||||||
|
// Set of FileBacking.DiskFileNum which will be required by virtual sstables
|
||||||
|
// in the checkpoint.
|
||||||
|
requiredVirtualBackingFiles := make(map[base.DiskFileNum]struct{})
|
||||||
// Link or copy the sstables.
|
// Link or copy the sstables.
|
||||||
for l := range current.Levels {
|
for l := range current.Levels {
|
||||||
iter := current.Levels[l].Iter()
|
iter := current.Levels[l].Iter()
|
||||||
@@ -270,7 +271,15 @@ func (d *DB) Checkpoint(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
srcPath := base.MakeFilepath(fs, d.dirname, fileTypeTable, f.FileNum)
|
fileBacking := f.FileBacking
|
||||||
|
if f.Virtual {
|
||||||
|
if _, ok := requiredVirtualBackingFiles[fileBacking.DiskFileNum]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
requiredVirtualBackingFiles[fileBacking.DiskFileNum] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
srcPath := base.MakeFilepath(fs, d.dirname, fileTypeTable, fileBacking.DiskFileNum)
|
||||||
destPath := fs.PathJoin(destDir, fs.PathBase(srcPath))
|
destPath := fs.PathJoin(destDir, fs.PathBase(srcPath))
|
||||||
ckErr = vfs.LinkOrCopy(fs, srcPath, destPath)
|
ckErr = vfs.LinkOrCopy(fs, srcPath, destPath)
|
||||||
if ckErr != nil {
|
if ckErr != nil {
|
||||||
@@ -279,7 +288,19 @@ func (d *DB) Checkpoint(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ckErr = d.writeCheckpointManifest(fs, formatVers, destDir, dir, manifestFileNum, manifestSize, excludedFiles)
|
var removeBackingTables []base.DiskFileNum
|
||||||
|
for diskFileNum := range virtualBackingFiles {
|
||||||
|
if _, ok := requiredVirtualBackingFiles[diskFileNum]; !ok {
|
||||||
|
// The backing sstable associated with fileNum is no longer
|
||||||
|
// required.
|
||||||
|
removeBackingTables = append(removeBackingTables, diskFileNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ckErr = d.writeCheckpointManifest(
|
||||||
|
fs, formatVers, destDir, dir, manifestFileNum.DiskFileNum(), manifestSize,
|
||||||
|
excludedFiles, removeBackingTables,
|
||||||
|
)
|
||||||
if ckErr != nil {
|
if ckErr != nil {
|
||||||
return ckErr
|
return ckErr
|
||||||
}
|
}
|
||||||
@@ -292,7 +313,7 @@ func (d *DB) Checkpoint(
|
|||||||
if logNum == 0 {
|
if logNum == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
srcPath := base.MakeFilepath(fs, d.walDirname, fileTypeLog, logNum)
|
srcPath := base.MakeFilepath(fs, d.walDirname, fileTypeLog, logNum.DiskFileNum())
|
||||||
destPath := fs.PathJoin(destDir, fs.PathBase(srcPath))
|
destPath := fs.PathJoin(destDir, fs.PathBase(srcPath))
|
||||||
ckErr = vfs.Copy(fs, srcPath, destPath)
|
ckErr = vfs.Copy(fs, srcPath, destPath)
|
||||||
if ckErr != nil {
|
if ckErr != nil {
|
||||||
@@ -315,9 +336,10 @@ func (d *DB) writeCheckpointManifest(
|
|||||||
formatVers FormatMajorVersion,
|
formatVers FormatMajorVersion,
|
||||||
destDirPath string,
|
destDirPath string,
|
||||||
destDir vfs.File,
|
destDir vfs.File,
|
||||||
manifestFileNum FileNum,
|
manifestFileNum base.DiskFileNum,
|
||||||
manifestSize int64,
|
manifestSize int64,
|
||||||
excludedFiles map[deletedFileEntry]*fileMetadata,
|
excludedFiles map[deletedFileEntry]*fileMetadata,
|
||||||
|
removeBackingTables []base.DiskFileNum,
|
||||||
) error {
|
) error {
|
||||||
// Copy the MANIFEST, and create a pointer to it. We copy rather
|
// Copy the MANIFEST, and create a pointer to it. We copy rather
|
||||||
// than link because additional version edits added to the
|
// than link because additional version edits added to the
|
||||||
@@ -346,7 +368,7 @@ func (d *DB) writeCheckpointManifest(
|
|||||||
// need to append another record with the excluded files (we cannot simply
|
// need to append another record with the excluded files (we cannot simply
|
||||||
// append a record after a raw data copy; see
|
// append a record after a raw data copy; see
|
||||||
// https://github.com/cockroachdb/cockroach/issues/100935).
|
// https://github.com/cockroachdb/cockroach/issues/100935).
|
||||||
r := record.NewReader(&io.LimitedReader{R: src, N: manifestSize}, manifestFileNum)
|
r := record.NewReader(&io.LimitedReader{R: src, N: manifestSize}, manifestFileNum.FileNum())
|
||||||
w := record.NewWriter(dst)
|
w := record.NewWriter(dst)
|
||||||
for {
|
for {
|
||||||
rr, err := r.Next()
|
rr, err := r.Next()
|
||||||
@@ -369,8 +391,10 @@ func (d *DB) writeCheckpointManifest(
|
|||||||
if len(excludedFiles) > 0 {
|
if len(excludedFiles) > 0 {
|
||||||
// Write out an additional VersionEdit that deletes the excluded SST files.
|
// Write out an additional VersionEdit that deletes the excluded SST files.
|
||||||
ve := versionEdit{
|
ve := versionEdit{
|
||||||
DeletedFiles: excludedFiles,
|
DeletedFiles: excludedFiles,
|
||||||
|
RemovedBackingTables: removeBackingTables,
|
||||||
}
|
}
|
||||||
|
|
||||||
rw, err := w.Next()
|
rw, err := w.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -397,7 +421,7 @@ func (d *DB) writeCheckpointManifest(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := setCurrentFunc(formatVers, manifestMarker, fs, destDirPath, destDir)(manifestFileNum); err != nil {
|
if err := setCurrentFunc(formatVers, manifestMarker, fs, destDirPath, destDir)(manifestFileNum.FileNum()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return manifestMarker.Close()
|
return manifestMarker.Close()
|
||||||
|
|||||||
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
|
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.
|
// Cleaner exports the base.Cleaner type.
|
||||||
type Cleaner = base.Cleaner
|
type Cleaner = base.Cleaner
|
||||||
@@ -14,3 +25,271 @@ type DeleteCleaner = base.DeleteCleaner
|
|||||||
|
|
||||||
// ArchiveCleaner exports the base.ArchiveCleaner type.
|
// ArchiveCleaner exports the base.ArchiveCleaner type.
|
||||||
type ArchiveCleaner = base.ArchiveCleaner
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
61
vendor/github.com/cockroachdb/pebble/commit.go
generated
vendored
61
vendor/github.com/cockroachdb/pebble/commit.go
generated
vendored
@@ -9,7 +9,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/pebble/record"
|
"github.com/cockroachdb/pebble/record"
|
||||||
)
|
)
|
||||||
@@ -33,13 +32,13 @@ type commitQueue struct {
|
|||||||
//
|
//
|
||||||
// The head index is stored in the most-significant bits so that we can
|
// The head index is stored in the most-significant bits so that we can
|
||||||
// atomically add to it and the overflow is harmless.
|
// atomically add to it and the overflow is harmless.
|
||||||
headTail uint64
|
headTail atomic.Uint64
|
||||||
|
|
||||||
// slots is a ring buffer of values stored in this queue. The size must be a
|
// slots is a ring buffer of values stored in this queue. The size must be a
|
||||||
// power of 2. A slot is in use until *both* the tail index has moved beyond
|
// 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
|
// 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.
|
// atomically by the consumer and read atomically by the producer.
|
||||||
slots [record.SyncConcurrency]unsafe.Pointer
|
slots [record.SyncConcurrency]atomic.Pointer[Batch]
|
||||||
}
|
}
|
||||||
|
|
||||||
const dequeueBits = 32
|
const dequeueBits = 32
|
||||||
@@ -58,7 +57,7 @@ func (q *commitQueue) pack(head, tail uint32) uint64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *commitQueue) enqueue(b *Batch) {
|
func (q *commitQueue) enqueue(b *Batch) {
|
||||||
ptrs := atomic.LoadUint64(&q.headTail)
|
ptrs := q.headTail.Load()
|
||||||
head, tail := q.unpack(ptrs)
|
head, tail := q.unpack(ptrs)
|
||||||
if (tail+uint32(len(q.slots)))&(1<<dequeueBits-1) == head {
|
if (tail+uint32(len(q.slots)))&(1<<dequeueBits-1) == head {
|
||||||
// Queue is full. This should never be reached because commitPipeline.commitQueueSem
|
// Queue is full. This should never be reached because commitPipeline.commitQueueSem
|
||||||
@@ -67,8 +66,8 @@ func (q *commitQueue) enqueue(b *Batch) {
|
|||||||
}
|
}
|
||||||
slot := &q.slots[head&uint32(len(q.slots)-1)]
|
slot := &q.slots[head&uint32(len(q.slots)-1)]
|
||||||
|
|
||||||
// Check if the head slot has been released by dequeue.
|
// Check if the head slot has been released by dequeueApplied.
|
||||||
for atomic.LoadPointer(slot) != nil {
|
for slot.Load() != nil {
|
||||||
// Another goroutine is still cleaning up the tail, so the queue is
|
// Another goroutine is still cleaning up the tail, so the queue is
|
||||||
// actually still full. We spin because this should resolve itself
|
// actually still full. We spin because this should resolve itself
|
||||||
// momentarily.
|
// momentarily.
|
||||||
@@ -76,16 +75,20 @@ func (q *commitQueue) enqueue(b *Batch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The head slot is free, so we own it.
|
// 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.
|
// store barrier for writing the slot.
|
||||||
atomic.AddUint64(&q.headTail, 1<<dequeueBits)
|
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 {
|
for {
|
||||||
ptrs := atomic.LoadUint64(&q.headTail)
|
ptrs := q.headTail.Load()
|
||||||
head, tail := q.unpack(ptrs)
|
head, tail := q.unpack(ptrs)
|
||||||
if tail == head {
|
if tail == head {
|
||||||
// Queue is empty.
|
// Queue is empty.
|
||||||
@@ -93,8 +96,8 @@ func (q *commitQueue) dequeue() *Batch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
slot := &q.slots[tail&uint32(len(q.slots)-1)]
|
slot := &q.slots[tail&uint32(len(q.slots)-1)]
|
||||||
b := (*Batch)(atomic.LoadPointer(slot))
|
b := slot.Load()
|
||||||
if b == nil || atomic.LoadUint32(&b.applied) == 0 {
|
if b == nil || !b.applied.Load() {
|
||||||
// The batch is not ready to be dequeued, or another goroutine has
|
// The batch is not ready to be dequeued, or another goroutine has
|
||||||
// already dequeued it.
|
// already dequeued it.
|
||||||
return nil
|
return nil
|
||||||
@@ -103,13 +106,13 @@ func (q *commitQueue) dequeue() *Batch {
|
|||||||
// Confirm head and tail (for our speculative check above) and increment
|
// Confirm head and tail (for our speculative check above) and increment
|
||||||
// tail. If this succeeds, then we own the slot at tail.
|
// tail. If this succeeds, then we own the slot at tail.
|
||||||
ptrs2 := q.pack(head, tail+1)
|
ptrs2 := q.pack(head, tail+1)
|
||||||
if atomic.CompareAndSwapUint64(&q.headTail, ptrs, ptrs2) {
|
if q.headTail.CompareAndSwap(ptrs, ptrs2) {
|
||||||
// We now own slot.
|
// We now own slot.
|
||||||
//
|
//
|
||||||
// Tell enqueue that we're done with this slot. Zeroing the slot is also
|
// 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
|
// important so we don't leave behind references that could keep this object
|
||||||
// live longer than necessary.
|
// live longer than necessary.
|
||||||
atomic.StorePointer(slot, nil)
|
slot.Store(nil)
|
||||||
// At this point enqueue owns the slot.
|
// At this point enqueue owns the slot.
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
@@ -122,10 +125,10 @@ func (q *commitQueue) dequeue() *Batch {
|
|||||||
type commitEnv struct {
|
type commitEnv struct {
|
||||||
// The next sequence number to give to a batch. Protected by
|
// The next sequence number to give to a batch. Protected by
|
||||||
// commitPipeline.mu.
|
// commitPipeline.mu.
|
||||||
logSeqNum *uint64
|
logSeqNum *atomic.Uint64
|
||||||
// The visible sequence number at which reads should be performed. Ratcheted
|
// The visible sequence number at which reads should be performed. Ratcheted
|
||||||
// upwards atomically as batches are applied to the memtable.
|
// upwards atomically as batches are applied to the memtable.
|
||||||
visibleSeqNum *uint64
|
visibleSeqNum *atomic.Uint64
|
||||||
|
|
||||||
// Apply the batch to the specified memtable. Called concurrently.
|
// Apply the batch to the specified memtable. Called concurrently.
|
||||||
apply func(b *Batch, mem *memTable) error
|
apply func(b *Batch, mem *memTable) error
|
||||||
@@ -239,6 +242,7 @@ type commitPipeline struct {
|
|||||||
// logSyncQSem are used for this reservation.
|
// logSyncQSem are used for this reservation.
|
||||||
commitQueueSem chan struct{}
|
commitQueueSem chan struct{}
|
||||||
logSyncQSem chan struct{}
|
logSyncQSem chan struct{}
|
||||||
|
ingestSem chan struct{}
|
||||||
// The mutex to use for synchronizing access to logSeqNum and serializing
|
// The mutex to use for synchronizing access to logSeqNum and serializing
|
||||||
// calls to commitEnv.write().
|
// calls to commitEnv.write().
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@@ -263,6 +267,7 @@ func newCommitPipeline(env commitEnv) *commitPipeline {
|
|||||||
// and sync the WAL.
|
// and sync the WAL.
|
||||||
commitQueueSem: make(chan struct{}, record.SyncConcurrency-1),
|
commitQueueSem: make(chan struct{}, record.SyncConcurrency-1),
|
||||||
logSyncQSem: make(chan struct{}, record.SyncConcurrency-1),
|
logSyncQSem: make(chan struct{}, record.SyncConcurrency-1),
|
||||||
|
ingestSem: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
@@ -381,12 +386,12 @@ func (p *commitPipeline) AllocateSeqNum(
|
|||||||
// Assign the batch a sequence number. Note that we use atomic operations
|
// Assign the batch a sequence number. Note that we use atomic operations
|
||||||
// here to handle concurrent reads of logSeqNum. commitPipeline.mu provides
|
// here to handle concurrent reads of logSeqNum. commitPipeline.mu provides
|
||||||
// mutual exclusion for other goroutines writing to logSeqNum.
|
// mutual exclusion for other goroutines writing to logSeqNum.
|
||||||
logSeqNum := atomic.AddUint64(p.env.logSeqNum, uint64(count)) - uint64(count)
|
logSeqNum := p.env.logSeqNum.Add(uint64(count)) - uint64(count)
|
||||||
seqNum := logSeqNum
|
seqNum := logSeqNum
|
||||||
if seqNum == 0 {
|
if seqNum == 0 {
|
||||||
// We can't use the value 0 for the global seqnum during ingestion, because
|
// We can't use the value 0 for the global seqnum during ingestion, because
|
||||||
// 0 indicates no global seqnum. So allocate one more seqnum.
|
// 0 indicates no global seqnum. So allocate one more seqnum.
|
||||||
atomic.AddUint64(p.env.logSeqNum, 1)
|
p.env.logSeqNum.Add(1)
|
||||||
seqNum++
|
seqNum++
|
||||||
}
|
}
|
||||||
b.setSeqNum(seqNum)
|
b.setSeqNum(seqNum)
|
||||||
@@ -396,7 +401,7 @@ func (p *commitPipeline) AllocateSeqNum(
|
|||||||
// writes that were sequenced before the ingestion. The spin loop is
|
// writes that were sequenced before the ingestion. The spin loop is
|
||||||
// unfortunate, but obviates the need for additional synchronization.
|
// unfortunate, but obviates the need for additional synchronization.
|
||||||
for {
|
for {
|
||||||
visibleSeqNum := atomic.LoadUint64(p.env.visibleSeqNum)
|
visibleSeqNum := p.env.visibleSeqNum.Load()
|
||||||
if visibleSeqNum == logSeqNum {
|
if visibleSeqNum == logSeqNum {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -455,7 +460,7 @@ func (p *commitPipeline) prepare(b *Batch, syncWAL bool, noSyncWait bool) (*memT
|
|||||||
// Assign the batch a sequence number. Note that we use atomic operations
|
// Assign the batch a sequence number. Note that we use atomic operations
|
||||||
// here to handle concurrent reads of logSeqNum. commitPipeline.mu provides
|
// here to handle concurrent reads of logSeqNum. commitPipeline.mu provides
|
||||||
// mutual exclusion for other goroutines writing to logSeqNum.
|
// mutual exclusion for other goroutines writing to logSeqNum.
|
||||||
b.setSeqNum(atomic.AddUint64(p.env.logSeqNum, n) - n)
|
b.setSeqNum(p.env.logSeqNum.Add(n) - n)
|
||||||
|
|
||||||
// Write the data to the WAL.
|
// Write the data to the WAL.
|
||||||
mem, err := p.env.write(b, syncWG, syncErr)
|
mem, err := p.env.write(b, syncWG, syncErr)
|
||||||
@@ -467,17 +472,17 @@ func (p *commitPipeline) prepare(b *Batch, syncWAL bool, noSyncWait bool) (*memT
|
|||||||
|
|
||||||
func (p *commitPipeline) publish(b *Batch) {
|
func (p *commitPipeline) publish(b *Batch) {
|
||||||
// Mark the batch as applied.
|
// Mark the batch as applied.
|
||||||
atomic.StoreUint32(&b.applied, 1)
|
b.applied.Store(true)
|
||||||
|
|
||||||
// Loop dequeuing applied batches from the pending queue. If our batch was
|
// 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
|
// 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
|
// it or someone else will dequeueApplied and publish it. If our batch is not the
|
||||||
// head of the queue then either we'll dequeue applied batches and reach our
|
// 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 or there is an unapplied batch blocking us. When that unapplied
|
||||||
// batch applies it will go through the same process and publish our batch
|
// batch applies it will go through the same process and publish our batch
|
||||||
// for us.
|
// for us.
|
||||||
for {
|
for {
|
||||||
t := p.pending.dequeue()
|
t := p.pending.dequeueApplied()
|
||||||
if t == nil {
|
if t == nil {
|
||||||
// Wait for another goroutine to publish us. We might also be waiting for
|
// Wait for another goroutine to publish us. We might also be waiting for
|
||||||
// the WAL sync to finish.
|
// the WAL sync to finish.
|
||||||
@@ -486,7 +491,7 @@ func (p *commitPipeline) publish(b *Batch) {
|
|||||||
b.commitStats.CommitWaitDuration += time.Since(now)
|
b.commitStats.CommitWaitDuration += time.Since(now)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if atomic.LoadUint32(&t.applied) != 1 {
|
if !t.applied.Load() {
|
||||||
panic("not reached")
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,13 +500,13 @@ func (p *commitPipeline) publish(b *Batch) {
|
|||||||
// number for a subsequent batch. That's ok as all we're guaranteeing is
|
// number for a subsequent batch. That's ok as all we're guaranteeing is
|
||||||
// that the sequence number ratchets up.
|
// that the sequence number ratchets up.
|
||||||
for {
|
for {
|
||||||
curSeqNum := atomic.LoadUint64(p.env.visibleSeqNum)
|
curSeqNum := p.env.visibleSeqNum.Load()
|
||||||
newSeqNum := t.SeqNum() + uint64(t.Count())
|
newSeqNum := t.SeqNum() + uint64(t.Count())
|
||||||
if newSeqNum <= curSeqNum {
|
if newSeqNum <= curSeqNum {
|
||||||
// t's sequence number has already been published.
|
// t's sequence number has already been published.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if atomic.CompareAndSwapUint64(p.env.visibleSeqNum, curSeqNum, newSeqNum) {
|
if p.env.visibleSeqNum.CompareAndSwap(curSeqNum, newSeqNum) {
|
||||||
// We successfully published t's sequence number.
|
// We successfully published t's sequence number.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
1401
vendor/github.com/cockroachdb/pebble/compaction.go
generated
vendored
1401
vendor/github.com/cockroachdb/pebble/compaction.go
generated
vendored
File diff suppressed because it is too large
Load Diff
700
vendor/github.com/cockroachdb/pebble/compaction_iter.go
generated
vendored
700
vendor/github.com/cockroachdb/pebble/compaction_iter.go
generated
vendored
@@ -6,6 +6,7 @@ package pebble
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -14,9 +15,9 @@ import (
|
|||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
"github.com/cockroachdb/pebble/internal/bytealloc"
|
"github.com/cockroachdb/pebble/internal/bytealloc"
|
||||||
"github.com/cockroachdb/pebble/internal/invariants"
|
|
||||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||||
"github.com/cockroachdb/pebble/internal/rangekey"
|
"github.com/cockroachdb/pebble/internal/rangekey"
|
||||||
|
"github.com/cockroachdb/redact"
|
||||||
)
|
)
|
||||||
|
|
||||||
// compactionIter provides a forward-only iterator that encapsulates the logic
|
// 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
|
// compaction iterator was only returned because an open snapshot prevents
|
||||||
// its elision. This field only applies to point keys, and not to range
|
// its elision. This field only applies to point keys, and not to range
|
||||||
// deletions or range keys.
|
// 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
|
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.
|
// The index of the snapshot for the current key within the snapshots slice.
|
||||||
curSnapshotIdx int
|
curSnapshotIdx int
|
||||||
curSnapshotSeqNum uint64
|
curSnapshotSeqNum uint64
|
||||||
@@ -233,13 +256,19 @@ type compactionIter struct {
|
|||||||
// The fragmented range keys.
|
// The fragmented range keys.
|
||||||
rangeKeys []keyspan.Span
|
rangeKeys []keyspan.Span
|
||||||
// Byte allocator for the tombstone keys.
|
// Byte allocator for the tombstone keys.
|
||||||
alloc bytealloc.A
|
alloc bytealloc.A
|
||||||
allowZeroSeqNum bool
|
allowZeroSeqNum bool
|
||||||
elideTombstone func(key []byte) bool
|
elideTombstone func(key []byte) bool
|
||||||
elideRangeTombstone func(start, end []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
|
// The on-disk format major version. This informs the types of keys that
|
||||||
// may be written to disk during a compaction.
|
// may be written to disk during a compaction.
|
||||||
formatVersion FormatMajorVersion
|
formatVersion FormatMajorVersion
|
||||||
|
stats struct {
|
||||||
|
// count of DELSIZED keys that were missized.
|
||||||
|
countMissizedDels uint64
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCompactionIter(
|
func newCompactionIter(
|
||||||
@@ -254,20 +283,24 @@ func newCompactionIter(
|
|||||||
allowZeroSeqNum bool,
|
allowZeroSeqNum bool,
|
||||||
elideTombstone func(key []byte) bool,
|
elideTombstone func(key []byte) bool,
|
||||||
elideRangeTombstone func(start, end []byte) bool,
|
elideRangeTombstone func(start, end []byte) bool,
|
||||||
|
ineffectualSingleDeleteCallback func(userKey []byte),
|
||||||
|
singleDeleteInvariantViolationCallback func(userKey []byte),
|
||||||
formatVersion FormatMajorVersion,
|
formatVersion FormatMajorVersion,
|
||||||
) *compactionIter {
|
) *compactionIter {
|
||||||
i := &compactionIter{
|
i := &compactionIter{
|
||||||
equal: equal,
|
equal: equal,
|
||||||
merge: merge,
|
merge: merge,
|
||||||
iter: iter,
|
iter: iter,
|
||||||
snapshots: snapshots,
|
snapshots: snapshots,
|
||||||
frontiers: frontiers{cmp: cmp},
|
frontiers: frontiers{cmp: cmp},
|
||||||
rangeDelFrag: rangeDelFrag,
|
rangeDelFrag: rangeDelFrag,
|
||||||
rangeKeyFrag: rangeKeyFrag,
|
rangeKeyFrag: rangeKeyFrag,
|
||||||
allowZeroSeqNum: allowZeroSeqNum,
|
allowZeroSeqNum: allowZeroSeqNum,
|
||||||
elideTombstone: elideTombstone,
|
elideTombstone: elideTombstone,
|
||||||
elideRangeTombstone: elideRangeTombstone,
|
elideRangeTombstone: elideRangeTombstone,
|
||||||
formatVersion: formatVersion,
|
ineffectualSingleDeleteCallback: ineffectualSingleDeleteCallback,
|
||||||
|
singleDeleteInvariantViolationCallback: singleDeleteInvariantViolationCallback,
|
||||||
|
formatVersion: formatVersion,
|
||||||
}
|
}
|
||||||
i.rangeDelFrag.Cmp = cmp
|
i.rangeDelFrag.Cmp = cmp
|
||||||
i.rangeDelFrag.Format = formatKey
|
i.rangeDelFrag.Format = formatKey
|
||||||
@@ -306,21 +339,43 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
|||||||
return nil, nil
|
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:
|
// respect to `iterKey` and related state:
|
||||||
//
|
//
|
||||||
// - `!skip && pos == iterPosNext`: `iterKey` is already at the next key.
|
// - `!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
|
// To move forward we advance by one key, even if that lands us in the same
|
||||||
// snapshot stripe.
|
// 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.
|
// 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.pos == iterPosCurForward {
|
||||||
if i.skip {
|
if i.skip {
|
||||||
i.skipInStripe()
|
i.skipInStripe()
|
||||||
} else {
|
} else {
|
||||||
i.nextInStripe()
|
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
|
i.pos = iterPosCurForward
|
||||||
@@ -374,6 +429,10 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
|||||||
return &i.key, i.value
|
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 {
|
if cover := i.rangeDelFrag.Covers(*i.iterKey, i.curSnapshotSeqNum); cover == keyspan.CoversVisibly {
|
||||||
// A pending range deletion deletes this key. Skip it.
|
// A pending range deletion deletes this key. Skip it.
|
||||||
i.saveKey()
|
i.saveKey()
|
||||||
@@ -382,17 +441,37 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
|||||||
} else if cover == keyspan.CoversInvisibly {
|
} else if cover == keyspan.CoversInvisibly {
|
||||||
// i.iterKey would be deleted by a range deletion if there weren't
|
// i.iterKey would be deleted by a range deletion if there weren't
|
||||||
// any open snapshots. Mark it as pinned.
|
// 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.snapshotPinned = true
|
||||||
|
i.forceObsoleteDueToRangeDel = true
|
||||||
|
} else {
|
||||||
|
i.forceObsoleteDueToRangeDel = false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch i.iterKey.Kind() {
|
switch i.iterKey.Kind() {
|
||||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||||
if i.elideTombstone(i.iterKey.UserKey) {
|
if i.elideTombstone(i.iterKey.UserKey) {
|
||||||
if i.curSnapshotIdx == 0 {
|
if i.curSnapshotIdx == 0 {
|
||||||
// If we're at the last snapshot stripe and the tombstone
|
// If we're at the last snapshot stripe and the tombstone
|
||||||
// can be elided skip skippable keys in the same stripe.
|
// can be elided skip skippable keys in the same stripe.
|
||||||
i.saveKey()
|
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
|
continue
|
||||||
} else {
|
} else {
|
||||||
// We're not at the last snapshot stripe, so the tombstone
|
// We're not at the last snapshot stripe, so the tombstone
|
||||||
@@ -410,11 +489,23 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
|||||||
i.skip = true
|
i.skip = true
|
||||||
return &i.key, i.value
|
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:
|
case InternalKeyKindSingleDelete:
|
||||||
if i.singleDeleteNext() {
|
if i.singleDeleteNext() {
|
||||||
return &i.key, i.value
|
return &i.key, i.value
|
||||||
|
} else if i.err != nil {
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(errors.AssertionFailedf(
|
||||||
|
"unexpected kind %s", redact.SafeString(i.iterKey.Kind().String())))
|
||||||
}
|
}
|
||||||
|
|
||||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||||
@@ -424,6 +515,9 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
|||||||
// preserving the original value, and potentially mutating the key
|
// preserving the original value, and potentially mutating the key
|
||||||
// kind.
|
// kind.
|
||||||
i.setNext()
|
i.setNext()
|
||||||
|
if i.err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
return &i.key, i.value
|
return &i.key, i.value
|
||||||
|
|
||||||
case InternalKeyKindMerge:
|
case InternalKeyKindMerge:
|
||||||
@@ -440,7 +534,15 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
|||||||
if i.err == nil {
|
if i.err == nil {
|
||||||
// includesBase is true whenever we've transformed the MERGE record
|
// includesBase is true whenever we've transformed the MERGE record
|
||||||
// into a SET.
|
// 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)
|
i.value, needDelete, i.valueCloser, i.err = finishValueMerger(valueMerger, includesBase)
|
||||||
}
|
}
|
||||||
if i.err == nil {
|
if i.err == nil {
|
||||||
@@ -469,6 +571,8 @@ func (i *compactionIter) Next() (*InternalKey, []byte) {
|
|||||||
}
|
}
|
||||||
if i.err != nil {
|
if i.err != nil {
|
||||||
i.valid = false
|
i.valid = false
|
||||||
|
// TODO(sumeer): why is MarkCorruptionError only being called for
|
||||||
|
// MERGE?
|
||||||
i.err = base.MarkCorruptionError(i.err)
|
i.err = base.MarkCorruptionError(i.err)
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -508,10 +612,16 @@ func snapshotIndex(seq uint64, snapshots []uint64) (int, uint64) {
|
|||||||
return index, snapshots[index]
|
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() {
|
func (i *compactionIter) skipInStripe() {
|
||||||
i.skip = true
|
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 {
|
for i.nextInStripe() == sameStripeSkippable {
|
||||||
|
if i.err != nil {
|
||||||
|
panic(i.err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Reset skip if we landed outside the original stripe. Otherwise, we landed
|
// 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
|
// 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
|
// nextInStripe advances the iterator and returns one of the above const ints
|
||||||
// indicating how its state changed.
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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.)
|
// 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 {
|
func (i *compactionIter) nextInStripe() stripeChangeType {
|
||||||
i.iterStripeChange = i.nextInStripeHelper()
|
i.iterStripeChange = i.nextInStripeHelper()
|
||||||
return i.iterStripeChange
|
return i.iterStripeChange
|
||||||
@@ -562,58 +678,72 @@ func (i *compactionIter) nextInStripe() stripeChangeType {
|
|||||||
// nextInStripeHelper is an internal helper for nextInStripe; callers should use
|
// nextInStripeHelper is an internal helper for nextInStripe; callers should use
|
||||||
// nextInStripe and not call nextInStripeHelper.
|
// nextInStripe and not call nextInStripeHelper.
|
||||||
func (i *compactionIter) nextInStripeHelper() stripeChangeType {
|
func (i *compactionIter) nextInStripeHelper() stripeChangeType {
|
||||||
if !i.iterNext() {
|
origSnapshotIdx := i.curSnapshotIdx
|
||||||
return newStripeNewKey
|
for {
|
||||||
}
|
if !i.iterNext() {
|
||||||
key := i.iterKey
|
return newStripeNewKey
|
||||||
|
}
|
||||||
|
key := i.iterKey
|
||||||
|
|
||||||
// NB: The below conditional is an optimization to avoid a user key
|
if !i.equal(i.key.UserKey, key.UserKey) {
|
||||||
// comparison in many cases. Internal keys with the same user key are
|
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||||
// ordered in (strictly) descending order by trailer. If the new key has a
|
return newStripeNewKey
|
||||||
// greater or equal trailer, or the previous key had a zero sequence number,
|
}
|
||||||
// the new key must have a new user key.
|
|
||||||
//
|
// If i.key and key have the same user key, then
|
||||||
// A couple things make these cases common:
|
// 1. i.key must not have had a zero sequence number (or it would've be the last
|
||||||
// - Sequence-number zeroing ensures ~all of the keys in L6 have a zero
|
// key with its user key).
|
||||||
// sequence number.
|
// 2. i.key must have a strictly larger sequence number
|
||||||
// - Ingested sstables' keys all adopt the same sequence number.
|
// There's an exception in that either key may be a range delete. Range
|
||||||
if i.keyTrailer <= base.InternalKeyZeroSeqnumMaxTrailer || key.Trailer >= i.keyTrailer {
|
// deletes may share a sequence number with a point key if the keys were
|
||||||
if invariants.Enabled && i.equal(i.key.UserKey, key.UserKey) {
|
// 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 := i.key
|
||||||
prevKey.Trailer = i.keyTrailer
|
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)
|
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
||||||
return newStripeNewKey
|
switch key.Kind() {
|
||||||
} else if !i.equal(i.key.UserKey, key.UserKey) {
|
case InternalKeyKindRangeDelete:
|
||||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
// Range tombstones need to be exposed by the compactionIter to the upper level
|
||||||
return newStripeNewKey
|
// `compaction` object, so return them regardless of whether they are in the same
|
||||||
}
|
// snapshot stripe.
|
||||||
origSnapshotIdx := i.curSnapshotIdx
|
if i.curSnapshotIdx == origSnapshotIdx {
|
||||||
i.curSnapshotIdx, i.curSnapshotSeqNum = snapshotIndex(key.SeqNum(), i.snapshots)
|
return sameStripeNonSkippable
|
||||||
switch key.Kind() {
|
}
|
||||||
case InternalKeyKindRangeDelete:
|
return newStripeSameKey
|
||||||
// Range tombstones need to be exposed by the compactionIter to the upper level
|
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||||
// `compaction` object, so return them regardless of whether they are in the same
|
// Range keys are interleaved at the max sequence number for a given user
|
||||||
// snapshot stripe.
|
// key, so we should not see any more range keys in this stripe.
|
||||||
if i.curSnapshotIdx == origSnapshotIdx {
|
panic("unreachable")
|
||||||
return sameStripeNonSkippable
|
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 {
|
if i.curSnapshotIdx == origSnapshotIdx {
|
||||||
return sameStripeNonSkippable
|
// Same snapshot.
|
||||||
|
if i.rangeDelFrag.Covers(*i.iterKey, i.curSnapshotSeqNum) == keyspan.CoversVisibly {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return sameStripeSkippable
|
||||||
}
|
}
|
||||||
return newStripeSameKey
|
return newStripeSameKey
|
||||||
}
|
}
|
||||||
if i.curSnapshotIdx == origSnapshotIdx {
|
|
||||||
return sameStripeSkippable
|
|
||||||
}
|
|
||||||
return newStripeSameKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *compactionIter) setNext() {
|
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
|
// 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.
|
// 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 {
|
for {
|
||||||
switch i.nextInStripe() {
|
switch i.nextInStripe() {
|
||||||
case newStripeNewKey, newStripeSameKey:
|
case newStripeNewKey, newStripeSameKey:
|
||||||
@@ -686,14 +820,19 @@ func (i *compactionIter) setNext() {
|
|||||||
i.skip = true
|
i.skip = true
|
||||||
return
|
return
|
||||||
case sameStripeSkippable:
|
case sameStripeSkippable:
|
||||||
// We're still in the same stripe. If this is a DEL/SINGLEDEL, we
|
// We're still in the same stripe. If this is a
|
||||||
// stop looking and emit a SETWITHDEL. Subsequent keys are
|
// DEL/SINGLEDEL/DELSIZED, we stop looking and emit a SETWITHDEL.
|
||||||
// eligible for skipping.
|
// Subsequent keys are eligible for skipping.
|
||||||
if i.iterKey.Kind() == InternalKeyKindDelete ||
|
switch i.iterKey.Kind() {
|
||||||
i.iterKey.Kind() == InternalKeyKindSingleDelete {
|
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||||
i.key.SetKind(InternalKeyKindSetWithDelete)
|
i.key.SetKind(InternalKeyKindSetWithDelete)
|
||||||
i.skip = true
|
i.skip = true
|
||||||
return
|
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:
|
default:
|
||||||
panic("pebble: unexpected stripeChangeType: " + strconv.Itoa(int(i.iterStripeChange)))
|
panic("pebble: unexpected stripeChangeType: " + strconv.Itoa(int(i.iterStripeChange)))
|
||||||
@@ -713,13 +852,24 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
|||||||
i.pos = iterPosNext
|
i.pos = iterPosNext
|
||||||
return i.iterStripeChange
|
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
|
key := i.iterKey
|
||||||
switch key.Kind() {
|
switch key.Kind() {
|
||||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||||
// We've hit a deletion tombstone. Return everything up to this point and
|
// 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
|
// 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
|
// 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
|
// We do the same for SingleDelete since SingleDelete is only
|
||||||
// permitted (with deterministic behavior) for keys that have been
|
// permitted (with deterministic behavior) for keys that have been
|
||||||
// set once since the last SingleDelete/Delete, so everything
|
// 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
|
// single Set, and then merge in any following Sets, but that is
|
||||||
// complicated wrt code and unnecessary given the narrow permitted
|
// complicated wrt code and unnecessary given the narrow permitted
|
||||||
// use of SingleDelete.
|
// use of SingleDelete.
|
||||||
i.key.SetKind(InternalKeyKindSet)
|
i.key.SetKind(InternalKeyKindSetWithDelete)
|
||||||
i.skip = true
|
i.skip = true
|
||||||
return sameStripeSkippable
|
return sameStripeSkippable
|
||||||
|
|
||||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
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
|
// 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
|
// value and return. We change the kind of the resulting key to a
|
||||||
// Set so that it shadows keys in lower levels. That is:
|
// Set so that it shadows keys in lower levels. That is:
|
||||||
@@ -761,16 +901,6 @@ func (i *compactionIter) mergeNext(valueMerger ValueMerger) stripeChangeType {
|
|||||||
return sameStripeSkippable
|
return sameStripeSkippable
|
||||||
|
|
||||||
case InternalKeyKindMerge:
|
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
|
// We've hit another Merge value. Merge with the existing value and
|
||||||
// continue looping.
|
// continue looping.
|
||||||
i.err = valueMerger.MergeOlder(i.iterValue)
|
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 {
|
func (i *compactionIter) singleDeleteNext() bool {
|
||||||
// Save the current key.
|
// Save the current key.
|
||||||
i.saveKey()
|
i.saveKey()
|
||||||
@@ -795,26 +935,85 @@ func (i *compactionIter) singleDeleteNext() bool {
|
|||||||
|
|
||||||
// Loop until finds a key to be passed to the next level.
|
// Loop until finds a key to be passed to the next level.
|
||||||
for {
|
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 {
|
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
|
i.pos = iterPosNext
|
||||||
return true
|
return i.err == nil
|
||||||
}
|
}
|
||||||
|
if i.err != nil {
|
||||||
|
panic(i.err)
|
||||||
|
}
|
||||||
|
// INVARIANT: sameStripeSkippable.
|
||||||
key := i.iterKey
|
key := i.iterKey
|
||||||
switch key.Kind() {
|
kind := key.Kind()
|
||||||
case InternalKeyKindDelete, InternalKeyKindMerge, InternalKeyKindSetWithDelete:
|
switch kind {
|
||||||
// We've hit a Delete, Merge or SetWithDelete, transform the
|
case InternalKeyKindDelete, InternalKeyKindSetWithDelete, InternalKeyKindDeleteSized:
|
||||||
// SingleDelete into a full Delete.
|
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.key.SetKind(InternalKeyKindDelete)
|
||||||
i.skip = true
|
i.skip = true
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case InternalKeyKindSet:
|
case InternalKeyKindSet, InternalKeyKindMerge:
|
||||||
i.nextInStripe()
|
// 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
|
i.valid = false
|
||||||
return false
|
return false
|
||||||
|
|
||||||
case InternalKeyKindSingleDelete:
|
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
|
continue
|
||||||
|
|
||||||
default:
|
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() {
|
func (i *compactionIter) saveKey() {
|
||||||
i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
|
i.keyBuf = append(i.keyBuf[:0], i.iterKey.UserKey...)
|
||||||
i.key.UserKey = i.keyBuf
|
i.key.UserKey = i.keyBuf
|
||||||
@@ -957,7 +1453,7 @@ func (i *compactionIter) maybeZeroSeqnum(snapshotIdx int) {
|
|||||||
// This is not the last snapshot
|
// This is not the last snapshot
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
i.key.SetSeqNum(0)
|
i.key.SetSeqNum(base.SeqNumZero)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A frontier is used to monitor a compaction's progression across the user
|
// A frontier is used to monitor a compaction's progression across the user
|
||||||
|
|||||||
685
vendor/github.com/cockroachdb/pebble/compaction_picker.go
generated
vendored
685
vendor/github.com/cockroachdb/pebble/compaction_picker.go
generated
vendored
File diff suppressed because it is too large
Load Diff
982
vendor/github.com/cockroachdb/pebble/db.go
generated
vendored
982
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()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LevelInfo contains info pertaining to a partificular level.
|
// LevelInfo contains info pertaining to a particular level.
|
||||||
type LevelInfo struct {
|
type LevelInfo struct {
|
||||||
Level int
|
Level int
|
||||||
Tables []TableInfo
|
Tables []TableInfo
|
||||||
|
Score float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i LevelInfo) String() string {
|
func (i LevelInfo) String() string {
|
||||||
@@ -51,8 +52,11 @@ func (i LevelInfo) String() string {
|
|||||||
|
|
||||||
// SafeFormat implements redact.SafeFormatter.
|
// SafeFormat implements redact.SafeFormatter.
|
||||||
func (i LevelInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
func (i LevelInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||||
w.Printf("L%d [%s] (%s)", redact.Safe(i.Level), redact.Safe(formatFileNums(i.Tables)),
|
w.Printf("L%d [%s] (%s) Score=%.2f",
|
||||||
redact.Safe(humanize.Uint64(tablesTotalSize(i.Tables))))
|
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.
|
// CompactionInfo contains the info for a compaction event.
|
||||||
@@ -75,6 +79,27 @@ type CompactionInfo struct {
|
|||||||
TotalDuration time.Duration
|
TotalDuration time.Duration
|
||||||
Done bool
|
Done bool
|
||||||
Err error
|
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 {
|
func (i CompactionInfo) String() string {
|
||||||
@@ -90,20 +115,25 @@ func (i CompactionInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !i.Done {
|
if !i.Done {
|
||||||
w.Printf("[JOB %d] compacting(%s) ", redact.Safe(i.JobID), redact.SafeString(i.Reason))
|
w.Printf("[JOB %d] compacting(%s) ",
|
||||||
w.Print(levelInfos(i.Input))
|
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
|
return
|
||||||
}
|
}
|
||||||
outputSize := tablesTotalSize(i.Output.Tables)
|
outputSize := tablesTotalSize(i.Output.Tables)
|
||||||
w.Printf("[JOB %d] compacted(%s) ", redact.Safe(i.JobID), redact.SafeString(i.Reason))
|
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.Print(levelInfos(i.Input))
|
||||||
w.Printf(" -> L%d [%s] (%s), in %.1fs (%.1fs total), output rate %s/s",
|
w.Printf(" -> L%d [%s] (%s), in %.1fs (%.1fs total), output rate %s/s",
|
||||||
redact.Safe(i.Output.Level),
|
redact.Safe(i.Output.Level),
|
||||||
redact.Safe(formatFileNums(i.Output.Tables)),
|
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.Duration.Seconds()),
|
||||||
redact.Safe(i.TotalDuration.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
|
type levelInfos []LevelInfo
|
||||||
@@ -129,6 +159,9 @@ type FlushInfo struct {
|
|||||||
Reason string
|
Reason string
|
||||||
// Input contains the count of input memtables that were flushed.
|
// Input contains the count of input memtables that were flushed.
|
||||||
Input int
|
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
|
// Output contains the ouptut table generated by the flush. The output info
|
||||||
// is empty for the flush begin event.
|
// is empty for the flush begin event.
|
||||||
Output []TableInfo
|
Output []TableInfo
|
||||||
@@ -168,7 +201,7 @@ func (i FlushInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
|||||||
if !i.Ingest {
|
if !i.Ingest {
|
||||||
w.Printf("flushing %d memtable", redact.Safe(i.Input))
|
w.Printf("flushing %d memtable", redact.Safe(i.Input))
|
||||||
w.SafeString(plural)
|
w.SafeString(plural)
|
||||||
w.Printf(" to L0")
|
w.Printf(" (%s) to L0", redact.Safe(humanize.Bytes.Uint64(i.InputBytes)))
|
||||||
} else {
|
} else {
|
||||||
w.Printf("flushing %d ingested table%s", redact.Safe(i.Input), plural)
|
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 {
|
if invariants.Enabled && len(i.IngestLevels) > 0 {
|
||||||
panic(errors.AssertionFailedf("pebble: expected len(IngestedLevels) == 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(i.JobID), redact.Safe(i.Input), plural,
|
||||||
|
redact.Safe(humanize.Bytes.Uint64(i.InputBytes)),
|
||||||
redact.Safe(formatFileNums(i.Output)),
|
redact.Safe(formatFileNums(i.Output)),
|
||||||
redact.Safe(humanize.Uint64(outputSize)),
|
redact.Safe(humanize.Bytes.Uint64(outputSize)),
|
||||||
redact.Safe(i.Duration.Seconds()),
|
redact.Safe(i.Duration.Seconds()),
|
||||||
redact.Safe(i.TotalDuration.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 {
|
} else {
|
||||||
if invariants.Enabled && len(i.IngestLevels) == 0 {
|
if invariants.Enabled && len(i.IngestLevels) == 0 {
|
||||||
panic(errors.AssertionFailedf("pebble: expected len(IngestedLevels) > 0"))
|
panic(errors.AssertionFailedf("pebble: expected len(IngestedLevels) > 0"))
|
||||||
@@ -198,12 +232,12 @@ func (i FlushInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
|||||||
if j > 0 {
|
if j > 0 {
|
||||||
w.Printf(" +")
|
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",
|
w.Printf(" in %.1fs (%.1fs total), output rate %s/s",
|
||||||
redact.Safe(i.Duration.Seconds()),
|
redact.Safe(i.Duration.Seconds()),
|
||||||
redact.Safe(i.TotalDuration.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)
|
w.Printf("[JOB %d] MANIFEST create error: %s", redact.Safe(i.JobID), i.Err)
|
||||||
return
|
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.
|
// 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)
|
w.Printf("[JOB %d] MANIFEST delete error: %s", redact.Safe(i.JobID), i.Err)
|
||||||
return
|
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.
|
// TableCreateInfo contains the info for a table creation event.
|
||||||
@@ -269,7 +303,7 @@ func (i TableCreateInfo) String() string {
|
|||||||
// SafeFormat implements redact.SafeFormatter.
|
// SafeFormat implements redact.SafeFormatter.
|
||||||
func (i TableCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
func (i TableCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||||
w.Printf("[JOB %d] %s: sstable created %s",
|
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.
|
// 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) {
|
func (i TableDeleteInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
||||||
if i.Err != nil {
|
if i.Err != nil {
|
||||||
w.Printf("[JOB %d] sstable delete error %s: %s",
|
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
|
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.
|
// TableIngestInfo contains the info for a table ingestion event.
|
||||||
@@ -337,8 +371,8 @@ func (i TableIngestInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
|||||||
if !i.flushable {
|
if !i.flushable {
|
||||||
levelStr = fmt.Sprintf("L%d:", t.Level)
|
levelStr = fmt.Sprintf("L%d:", t.Level)
|
||||||
}
|
}
|
||||||
w.Printf(" %s%s (%s)", redact.Safe(levelStr), redact.Safe(t.FileNum),
|
w.Printf(" %s%s (%s)", redact.Safe(levelStr), t.FileNum,
|
||||||
redact.Safe(humanize.Uint64(t.Size)))
|
redact.Safe(humanize.Bytes.Uint64(t.Size)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,12 +433,12 @@ func (i WALCreateInfo) SafeFormat(w redact.SafePrinter, _ rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if i.RecycledFileNum == 0 {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Printf("[JOB %d] WAL created %s (recycled %s)",
|
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.
|
// 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)
|
w.Printf("[JOB %d] WAL delete error: %s", redact.Safe(i.JobID), i.Err)
|
||||||
return
|
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.
|
// 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
|
pointIter internalIterator
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
pointIter, err = r.NewIterWithBlockPropertyFiltersAndContext(
|
// We could set hideObsoletePoints=true, since we are reading at
|
||||||
ctx,
|
// InternalKeySeqNumMax, but we don't bother since these sstables should
|
||||||
it.opts.LowerBound,
|
// not have obsolete points (so the performance optimization is
|
||||||
it.opts.UpperBound,
|
// unnecessary), and we don't want to bother constructing a
|
||||||
nil, /* BlockPropertiesFilterer */
|
// BlockPropertiesFilterer that includes obsoleteKeyBlockPropertyFilter.
|
||||||
false, /* useFilterBlock */
|
pointIter, err = r.NewIterWithBlockPropertyFiltersAndContextEtc(
|
||||||
&it.stats.InternalStats,
|
ctx, it.opts.LowerBound, it.opts.UpperBound, nil, /* BlockPropertiesFilterer */
|
||||||
sstable.TrivialReaderProvider{Reader: r},
|
false /* hideObsoletePoints */, false, /* useFilterBlock */
|
||||||
)
|
&it.stats.InternalStats, sstable.TrivialReaderProvider{Reader: r})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -311,7 +311,7 @@ func finishInitializingExternal(ctx context.Context, it *Iterator) {
|
|||||||
base.InternalKeySeqNumMax,
|
base.InternalKeySeqNumMax,
|
||||||
it.opts.LowerBound, it.opts.UpperBound,
|
it.opts.LowerBound, it.opts.UpperBound,
|
||||||
&it.hasPrefix, &it.prefixOrFullSeekKey,
|
&it.hasPrefix, &it.prefixOrFullSeekKey,
|
||||||
true /* onlySets */, &it.rangeKey.internal,
|
false /* internalKeys */, &it.rangeKey.internal,
|
||||||
)
|
)
|
||||||
for i := range rangeKeyIters {
|
for i := range rangeKeyIters {
|
||||||
it.rangeKey.iterConfig.AddLevel(rangeKeyIters[i])
|
it.rangeKey.iterConfig.AddLevel(rangeKeyIters[i])
|
||||||
@@ -319,8 +319,12 @@ func finishInitializingExternal(ctx context.Context, it *Iterator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if it.rangeKey != nil {
|
if it.rangeKey != nil {
|
||||||
it.rangeKey.iiter.Init(&it.comparer, it.iter, it.rangeKey.rangeKeyIter, &it.rangeKeyMasking,
|
it.rangeKey.iiter.Init(&it.comparer, it.iter, it.rangeKey.rangeKeyIter,
|
||||||
it.opts.LowerBound, it.opts.UpperBound)
|
keyspan.InterleavingIterOpts{
|
||||||
|
Mask: &it.rangeKeyMasking,
|
||||||
|
LowerBound: it.opts.LowerBound,
|
||||||
|
UpperBound: it.opts.UpperBound,
|
||||||
|
})
|
||||||
it.iter = &it.rangeKey.iiter
|
it.iter = &it.rangeKey.iiter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
vendor/github.com/cockroachdb/pebble/filenames.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/filenames.go
generated
vendored
@@ -33,7 +33,7 @@ const (
|
|||||||
// NB: This is a low-level routine and typically not what you want to
|
// NB: This is a low-level routine and typically not what you want to
|
||||||
// use. Newer versions of Pebble running newer format major versions do
|
// use. Newer versions of Pebble running newer format major versions do
|
||||||
// not use the CURRENT file. See setCurrentFunc in version_set.go.
|
// not use the CURRENT file. See setCurrentFunc in version_set.go.
|
||||||
func setCurrentFile(dirname string, fs vfs.FS, fileNum FileNum) error {
|
func setCurrentFile(dirname string, fs vfs.FS, fileNum base.DiskFileNum) error {
|
||||||
newFilename := base.MakeFilepath(fs, dirname, fileTypeCurrent, fileNum)
|
newFilename := base.MakeFilepath(fs, dirname, fileTypeCurrent, fileNum)
|
||||||
oldFilename := base.MakeFilepath(fs, dirname, fileTypeTemp, fileNum)
|
oldFilename := base.MakeFilepath(fs, dirname, fileTypeTemp, fileNum)
|
||||||
fs.Remove(oldFilename)
|
fs.Remove(oldFilename)
|
||||||
|
|||||||
62
vendor/github.com/cockroachdb/pebble/flushable.go
generated
vendored
62
vendor/github.com/cockroachdb/pebble/flushable.go
generated
vendored
@@ -60,20 +60,20 @@ type flushableEntry struct {
|
|||||||
// flushable is a memTable, when the reader refs drops to zero, the writer
|
// 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
|
// refs will already be zero because the memtable will have been flushed and
|
||||||
// that only occurs once the writer refs drops to zero.
|
// that only occurs once the writer refs drops to zero.
|
||||||
readerRefs int32
|
readerRefs atomic.Int32
|
||||||
// Closure to invoke to release memory accounting.
|
// Closure to invoke to release memory accounting.
|
||||||
releaseMemAccounting func()
|
releaseMemAccounting func()
|
||||||
// unrefFiles, if not nil, should be invoked to decrease the ref count of
|
// unrefFiles, if not nil, should be invoked to decrease the ref count of
|
||||||
// files which are backing the flushable.
|
// files which are backing the flushable.
|
||||||
unrefFiles func() []*fileMetadata
|
unrefFiles func() []*fileBacking
|
||||||
// deleteFnLocked should be called if the caller is holding DB.mu.
|
// deleteFnLocked should be called if the caller is holding DB.mu.
|
||||||
deleteFnLocked func(obsolete []*fileMetadata)
|
deleteFnLocked func(obsolete []*fileBacking)
|
||||||
// deleteFn should be called if the caller is not holding DB.mu.
|
// deleteFn should be called if the caller is not holding DB.mu.
|
||||||
deleteFn func(obsolete []*fileMetadata)
|
deleteFn func(obsolete []*fileBacking)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *flushableEntry) readerRef() {
|
func (e *flushableEntry) readerRef() {
|
||||||
switch v := atomic.AddInt32(&e.readerRefs, 1); {
|
switch v := e.readerRefs.Add(1); {
|
||||||
case v <= 1:
|
case v <= 1:
|
||||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||||
}
|
}
|
||||||
@@ -90,9 +90,9 @@ func (e *flushableEntry) readerUnrefLocked(deleteFiles bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *flushableEntry) readerUnrefHelper(
|
func (e *flushableEntry) readerUnrefHelper(
|
||||||
deleteFiles bool, deleteFn func(obsolete []*manifest.FileMetadata),
|
deleteFiles bool, deleteFn func(obsolete []*fileBacking),
|
||||||
) {
|
) {
|
||||||
switch v := atomic.AddInt32(&e.readerRefs, -1); {
|
switch v := e.readerRefs.Add(-1); {
|
||||||
case v < 0:
|
case v < 0:
|
||||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||||
case v == 0:
|
case v == 0:
|
||||||
@@ -116,9 +116,8 @@ type flushableList []*flushableEntry
|
|||||||
// ingestedFlushable is the implementation of the flushable interface for the
|
// ingestedFlushable is the implementation of the flushable interface for the
|
||||||
// ingesting sstables which are added to the flushable list.
|
// ingesting sstables which are added to the flushable list.
|
||||||
type ingestedFlushable struct {
|
type ingestedFlushable struct {
|
||||||
files []*fileMetadata
|
files []physicalMeta
|
||||||
cmp Compare
|
comparer *Comparer
|
||||||
split Split
|
|
||||||
newIters tableNewIters
|
newIters tableNewIters
|
||||||
newRangeKeyIters keyspan.TableNewSpanIter
|
newRangeKeyIters keyspan.TableNewSpanIter
|
||||||
|
|
||||||
@@ -131,26 +130,27 @@ type ingestedFlushable struct {
|
|||||||
|
|
||||||
func newIngestedFlushable(
|
func newIngestedFlushable(
|
||||||
files []*fileMetadata,
|
files []*fileMetadata,
|
||||||
cmp Compare,
|
comparer *Comparer,
|
||||||
split Split,
|
|
||||||
newIters tableNewIters,
|
newIters tableNewIters,
|
||||||
newRangeKeyIters keyspan.TableNewSpanIter,
|
newRangeKeyIters keyspan.TableNewSpanIter,
|
||||||
) *ingestedFlushable {
|
) *ingestedFlushable {
|
||||||
|
var physicalFiles []physicalMeta
|
||||||
|
var hasRangeKeys bool
|
||||||
|
for _, f := range files {
|
||||||
|
if f.HasRangeKeys {
|
||||||
|
hasRangeKeys = true
|
||||||
|
}
|
||||||
|
physicalFiles = append(physicalFiles, f.PhysicalMeta())
|
||||||
|
}
|
||||||
|
|
||||||
ret := &ingestedFlushable{
|
ret := &ingestedFlushable{
|
||||||
files: files,
|
files: physicalFiles,
|
||||||
cmp: cmp,
|
comparer: comparer,
|
||||||
split: split,
|
|
||||||
newIters: newIters,
|
newIters: newIters,
|
||||||
newRangeKeyIters: newRangeKeyIters,
|
newRangeKeyIters: newRangeKeyIters,
|
||||||
// slice is immutable and can be set once and used many times.
|
// 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,
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
if f.HasRangeKeys {
|
|
||||||
ret.hasRangeKeys = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
@@ -159,6 +159,7 @@ func newIngestedFlushable(
|
|||||||
// TODO(sumeer): ingestedFlushable iters also need to plumb context for
|
// TODO(sumeer): ingestedFlushable iters also need to plumb context for
|
||||||
// tracing.
|
// tracing.
|
||||||
|
|
||||||
|
// newIter is part of the flushable interface.
|
||||||
func (s *ingestedFlushable) newIter(o *IterOptions) internalIterator {
|
func (s *ingestedFlushable) newIter(o *IterOptions) internalIterator {
|
||||||
var opts IterOptions
|
var opts IterOptions
|
||||||
if o != nil {
|
if o != nil {
|
||||||
@@ -169,10 +170,11 @@ func (s *ingestedFlushable) newIter(o *IterOptions) internalIterator {
|
|||||||
// aren't truly levels in the lsm. Right now, the encoding only supports
|
// aren't truly levels in the lsm. Right now, the encoding only supports
|
||||||
// L0 sublevels, and the rest of the levels in the lsm.
|
// L0 sublevels, and the rest of the levels in the lsm.
|
||||||
return newLevelIter(
|
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 {
|
func (s *ingestedFlushable) newFlushIter(o *IterOptions, bytesFlushed *uint64) internalIterator {
|
||||||
// newFlushIter is only used for writing memtables to disk as sstables.
|
// newFlushIter is only used for writing memtables to disk as sstables.
|
||||||
// Since ingested sstables are already present on disk, they don't need to
|
// Since ingested sstables are already present on disk, they don't need to
|
||||||
@@ -181,7 +183,7 @@ func (s *ingestedFlushable) newFlushIter(o *IterOptions, bytesFlushed *uint64) i
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ingestedFlushable) constructRangeDelIter(
|
func (s *ingestedFlushable) constructRangeDelIter(
|
||||||
file *manifest.FileMetadata, _ *keyspan.SpanIterOptions,
|
file *manifest.FileMetadata, _ keyspan.SpanIterOptions,
|
||||||
) (keyspan.FragmentIterator, error) {
|
) (keyspan.FragmentIterator, error) {
|
||||||
// Note that the keyspan level iter expects a non-nil iterator to be
|
// 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.
|
// returned even if there is an error. So, we return the emptyKeyspanIter.
|
||||||
@@ -196,41 +198,47 @@ func (s *ingestedFlushable) constructRangeDelIter(
|
|||||||
return rangeDelIter, nil
|
return rangeDelIter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newRangeDelIter is part of the flushable interface.
|
||||||
// TODO(bananabrick): Using a level iter instead of a keyspan level iter to
|
// TODO(bananabrick): Using a level iter instead of a keyspan level iter to
|
||||||
// surface range deletes is more efficient.
|
// surface range deletes is more efficient.
|
||||||
func (s *ingestedFlushable) newRangeDelIter(_ *IterOptions) keyspan.FragmentIterator {
|
func (s *ingestedFlushable) newRangeDelIter(_ *IterOptions) keyspan.FragmentIterator {
|
||||||
return keyspan.NewLevelIter(
|
return keyspan.NewLevelIter(
|
||||||
keyspan.SpanIterOptions{}, s.cmp,
|
keyspan.SpanIterOptions{}, s.comparer.Compare,
|
||||||
s.constructRangeDelIter, s.slice.Iter(), manifest.Level(0),
|
s.constructRangeDelIter, s.slice.Iter(), manifest.Level(0),
|
||||||
manifest.KeyTypePoint,
|
manifest.KeyTypePoint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newRangeKeyIter is part of the flushable interface.
|
||||||
func (s *ingestedFlushable) newRangeKeyIter(o *IterOptions) keyspan.FragmentIterator {
|
func (s *ingestedFlushable) newRangeKeyIter(o *IterOptions) keyspan.FragmentIterator {
|
||||||
if !s.containsRangeKeys() {
|
if !s.containsRangeKeys() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return keyspan.NewLevelIter(
|
return keyspan.NewLevelIter(
|
||||||
keyspan.SpanIterOptions{}, s.cmp, s.newRangeKeyIters,
|
keyspan.SpanIterOptions{}, s.comparer.Compare, s.newRangeKeyIters,
|
||||||
s.slice.Iter(), manifest.Level(0), manifest.KeyTypeRange,
|
s.slice.Iter(), manifest.Level(0), manifest.KeyTypeRange,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// containsRangeKeys is part of the flushable interface.
|
||||||
func (s *ingestedFlushable) containsRangeKeys() bool {
|
func (s *ingestedFlushable) containsRangeKeys() bool {
|
||||||
return s.hasRangeKeys
|
return s.hasRangeKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inuseBytes is part of the flushable interface.
|
||||||
func (s *ingestedFlushable) inuseBytes() uint64 {
|
func (s *ingestedFlushable) inuseBytes() uint64 {
|
||||||
// inuseBytes is only used when memtables are flushed to disk as sstables.
|
// inuseBytes is only used when memtables are flushed to disk as sstables.
|
||||||
panic("pebble: not implemented")
|
panic("pebble: not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// totalBytes is part of the flushable interface.
|
||||||
func (s *ingestedFlushable) totalBytes() uint64 {
|
func (s *ingestedFlushable) totalBytes() uint64 {
|
||||||
// We don't allocate additional bytes for the ingestedFlushable.
|
// We don't allocate additional bytes for the ingestedFlushable.
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readyForFlush is part of the flushable interface.
|
||||||
func (s *ingestedFlushable) readyForFlush() bool {
|
func (s *ingestedFlushable) readyForFlush() bool {
|
||||||
// ingestedFlushable should always be ready to flush. However, note that
|
// ingestedFlushable should always be ready to flush. However, note that
|
||||||
// memtables before the ingested sstables in the memtable queue must be
|
// memtables before the ingested sstables in the memtable queue must be
|
||||||
|
|||||||
106
vendor/github.com/cockroachdb/pebble/format_major_version.go
generated
vendored
106
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.
|
// FormatVersion that the default corresponds to may change with time.
|
||||||
type FormatMajorVersion uint64
|
type FormatMajorVersion uint64
|
||||||
|
|
||||||
|
// SafeValue implements redact.SafeValue.
|
||||||
|
func (v FormatMajorVersion) SafeValue() {}
|
||||||
|
|
||||||
// String implements fmt.Stringer.
|
// String implements fmt.Stringer.
|
||||||
func (v FormatMajorVersion) String() string {
|
func (v FormatMajorVersion) String() string {
|
||||||
// NB: This must not change. It's used as the value for the the
|
// NB: This must not change. It's used as the value for the on-disk
|
||||||
// on-disk version marker file.
|
// version marker file.
|
||||||
//
|
//
|
||||||
// Specifically, this value must always parse as a base 10 integer
|
// Specifically, this value must always parse as a base 10 integer
|
||||||
// that fits in a uint64. We format it as zero-padded, 3-digit
|
// that fits in a uint64. We format it as zero-padded, 3-digit
|
||||||
@@ -110,12 +113,12 @@ const (
|
|||||||
|
|
||||||
// 23.1 versions.
|
// 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
|
// 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
|
// release. It was later decided that this should be deferred until a
|
||||||
// subsequent release. The original ordering is preserved so as not to
|
// subsequent release. The original ordering is preserved so as not to
|
||||||
// introduce breaking changes in Cockroach.
|
// introduce breaking changes in Cockroach.
|
||||||
FormatUnusedPrePebblev1MarkedCompacted
|
formatUnusedPrePebblev1MarkedCompacted
|
||||||
|
|
||||||
// FormatSSTableValueBlocks is a format major version that adds support for
|
// FormatSSTableValueBlocks is a format major version that adds support for
|
||||||
// storing values in value blocks in the sstable. Value block support is not
|
// 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.
|
// compactions for files marked for compaction are complete.
|
||||||
FormatPrePebblev1MarkedCompacted
|
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 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
|
// MaxTableFormat returns the maximum sstable.TableFormat that can be used at
|
||||||
@@ -166,11 +189,12 @@ func (v FormatMajorVersion) MaxTableFormat() sstable.TableFormat {
|
|||||||
FormatSplitUserKeysMarkedCompacted:
|
FormatSplitUserKeysMarkedCompacted:
|
||||||
return sstable.TableFormatPebblev1
|
return sstable.TableFormatPebblev1
|
||||||
case FormatRangeKeys, FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
|
case FormatRangeKeys, FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
|
||||||
FormatUnusedPrePebblev1MarkedCompacted:
|
formatUnusedPrePebblev1MarkedCompacted:
|
||||||
return sstable.TableFormatPebblev2
|
return sstable.TableFormatPebblev2
|
||||||
case FormatSSTableValueBlocks, FormatFlushableIngest,
|
case FormatSSTableValueBlocks, FormatFlushableIngest, FormatPrePebblev1MarkedCompacted:
|
||||||
FormatPrePebblev1MarkedCompacted:
|
|
||||||
return sstable.TableFormatPebblev3
|
return sstable.TableFormatPebblev3
|
||||||
|
case FormatDeleteSizedAndObsolete, FormatVirtualSSTables:
|
||||||
|
return sstable.TableFormatPebblev4
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
|
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
|
||||||
}
|
}
|
||||||
@@ -186,14 +210,27 @@ func (v FormatMajorVersion) MinTableFormat() sstable.TableFormat {
|
|||||||
FormatRangeKeys:
|
FormatRangeKeys:
|
||||||
return sstable.TableFormatLevelDB
|
return sstable.TableFormatLevelDB
|
||||||
case FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
|
case FormatMinTableFormatPebblev1, FormatPrePebblev1Marked,
|
||||||
FormatUnusedPrePebblev1MarkedCompacted, FormatSSTableValueBlocks,
|
formatUnusedPrePebblev1MarkedCompacted, FormatSSTableValueBlocks,
|
||||||
FormatFlushableIngest, FormatPrePebblev1MarkedCompacted:
|
FormatFlushableIngest, FormatPrePebblev1MarkedCompacted,
|
||||||
|
FormatDeleteSizedAndObsolete, FormatVirtualSSTables:
|
||||||
return sstable.TableFormatPebblev1
|
return sstable.TableFormatPebblev1
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("pebble: unsupported format major version: %s", v))
|
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
|
// formatMajorVersionMigrations defines the migrations from one format
|
||||||
// major version to the next. Each migration is defined as a closure
|
// major version to the next. Each migration is defined as a closure
|
||||||
// which will be invoked on the database before the new format major
|
// which will be invoked on the database before the new format major
|
||||||
@@ -219,7 +256,7 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
|
|||||||
// guaranteed to exist, because we unconditionally locate it
|
// guaranteed to exist, because we unconditionally locate it
|
||||||
// during Open.
|
// during Open.
|
||||||
manifestFileNum := d.mu.versions.manifestFileNum
|
manifestFileNum := d.mu.versions.manifestFileNum
|
||||||
filename := base.MakeFilename(fileTypeManifest, manifestFileNum)
|
filename := base.MakeFilename(fileTypeManifest, manifestFileNum.DiskFileNum())
|
||||||
if err := d.mu.versions.manifestMarker.Move(filename); err != nil {
|
if err := d.mu.versions.manifestMarker.Move(filename); err != nil {
|
||||||
return errors.Wrap(err, "moving manifest marker")
|
return errors.Wrap(err, "moving manifest marker")
|
||||||
}
|
}
|
||||||
@@ -257,7 +294,7 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
|
|||||||
// version that does not know about format major versions
|
// version that does not know about format major versions
|
||||||
// attempts to open the database, it will error avoiding
|
// attempts to open the database, it will error avoiding
|
||||||
// accidental corruption.
|
// accidental corruption.
|
||||||
if err := setCurrentFile(d.mu.versions.dirname, d.mu.versions.fs, 0); err != nil {
|
if err := setCurrentFile(d.mu.versions.dirname, d.mu.versions.fs, base.FileNum(0).DiskFileNum()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return d.finalizeFormatVersUpgrade(FormatVersioned)
|
return d.finalizeFormatVersUpgrade(FormatVersioned)
|
||||||
@@ -301,9 +338,9 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
|
|||||||
}
|
}
|
||||||
return d.finalizeFormatVersUpgrade(FormatPrePebblev1Marked)
|
return d.finalizeFormatVersUpgrade(FormatPrePebblev1Marked)
|
||||||
},
|
},
|
||||||
FormatUnusedPrePebblev1MarkedCompacted: func(d *DB) error {
|
formatUnusedPrePebblev1MarkedCompacted: func(d *DB) error {
|
||||||
// Intentional no-op.
|
// Intentional no-op.
|
||||||
return d.finalizeFormatVersUpgrade(FormatUnusedPrePebblev1MarkedCompacted)
|
return d.finalizeFormatVersUpgrade(formatUnusedPrePebblev1MarkedCompacted)
|
||||||
},
|
},
|
||||||
FormatSSTableValueBlocks: func(d *DB) error {
|
FormatSSTableValueBlocks: func(d *DB) error {
|
||||||
return d.finalizeFormatVersUpgrade(FormatSSTableValueBlocks)
|
return d.finalizeFormatVersUpgrade(FormatSSTableValueBlocks)
|
||||||
@@ -320,6 +357,12 @@ var formatMajorVersionMigrations = map[FormatMajorVersion]func(*DB) error{
|
|||||||
}
|
}
|
||||||
return d.finalizeFormatVersUpgrade(FormatPrePebblev1MarkedCompacted)
|
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`
|
const formatVersionMarkerName = `format-version`
|
||||||
@@ -342,7 +385,7 @@ func lookupFormatMajorVersion(
|
|||||||
if vers == FormatDefault {
|
if vers == FormatDefault {
|
||||||
return 0, nil, errors.Newf("pebble: default format major version should not persisted", vers)
|
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 0, nil, errors.Newf("pebble: database %q written in format major version %d", dirname, vers)
|
||||||
}
|
}
|
||||||
return vers, m, nil
|
return vers, m, nil
|
||||||
@@ -376,8 +419,8 @@ func (d *DB) ratchetFormatMajorVersionLocked(formatVers FormatMajorVersion) erro
|
|||||||
if d.opts.ReadOnly {
|
if d.opts.ReadOnly {
|
||||||
return ErrReadOnly
|
return ErrReadOnly
|
||||||
}
|
}
|
||||||
if formatVers > FormatNewest {
|
if formatVers > internalFormatNewest {
|
||||||
// Guard against accidentally forgetting to update FormatNewest.
|
// Guard against accidentally forgetting to update internalFormatNewest.
|
||||||
return errors.Errorf("pebble: unknown format version %d", formatVers)
|
return errors.Errorf("pebble: unknown format version %d", formatVers)
|
||||||
}
|
}
|
||||||
if currentVers := d.FormatMajorVersion(); currentVers > formatVers {
|
if currentVers := d.FormatMajorVersion(); currentVers > formatVers {
|
||||||
@@ -519,17 +562,24 @@ var markFilesPrePebblev1 = func(tc *tableCacheContainer) findFilesFunc {
|
|||||||
for l := numLevels - 1; l > 0; l-- {
|
for l := numLevels - 1; l > 0; l-- {
|
||||||
iter := v.Levels[l].Iter()
|
iter := v.Levels[l].Iter()
|
||||||
for f := iter.First(); f != nil; f = iter.Next() {
|
for f := iter.First(); f != nil; f = iter.Next() {
|
||||||
err = tc.withReader(f, func(r *sstable.Reader) error {
|
if f.Virtual {
|
||||||
tf, err := r.TableFormat()
|
// Any physical sstable which has been virtualized must
|
||||||
if err != nil {
|
// have already undergone this migration, and we don't
|
||||||
return err
|
// need to worry about the virtual sstable themselves.
|
||||||
}
|
panic("pebble: unexpected virtual sstable during migration")
|
||||||
if tf < sstable.TableFormatPebblev1 {
|
}
|
||||||
found = true
|
err = tc.withReader(
|
||||||
files[l] = append(files[l], f)
|
f.PhysicalMeta(), func(r *sstable.Reader) error {
|
||||||
}
|
tf, err := r.TableFormat()
|
||||||
return nil
|
if err != nil {
|
||||||
})
|
return err
|
||||||
|
}
|
||||||
|
if tf < sstable.TableFormatPebblev1 {
|
||||||
|
found = true
|
||||||
|
files[l] = append(files[l], f)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
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.
|
// lazily.
|
||||||
type getIter struct {
|
type getIter struct {
|
||||||
logger Logger
|
logger Logger
|
||||||
cmp Compare
|
comparer *Comparer
|
||||||
equal Equal
|
|
||||||
newIters tableNewIters
|
newIters tableNewIters
|
||||||
snapshot uint64
|
snapshot uint64
|
||||||
key []byte
|
key []byte
|
||||||
@@ -83,7 +82,7 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
|||||||
// key. Every call to levelIter.Next() potentially switches to a new
|
// key. Every call to levelIter.Next() potentially switches to a new
|
||||||
// table and thus reinitializes rangeDelIter.
|
// table and thus reinitializes rangeDelIter.
|
||||||
if g.rangeDelIter != nil {
|
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 {
|
if g.err = g.rangeDelIter.Close(); g.err != nil {
|
||||||
return nil, base.LazyValue{}
|
return nil, base.LazyValue{}
|
||||||
}
|
}
|
||||||
@@ -101,7 +100,7 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
|||||||
g.iter = nil
|
g.iter = nil
|
||||||
return nil, base.LazyValue{}
|
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) {
|
if !key.Visible(g.snapshot, base.InternalKeySeqNumMax) {
|
||||||
g.iterKey, g.iterValue = g.iter.Next()
|
g.iterKey, g.iterValue = g.iter.Next()
|
||||||
continue
|
continue
|
||||||
@@ -158,12 +157,25 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
|||||||
if n := len(g.l0); n > 0 {
|
if n := len(g.l0); n > 0 {
|
||||||
files := g.l0[n-1].Iter()
|
files := g.l0[n-1].Iter()
|
||||||
g.l0 = g.l0[:n-1]
|
g.l0 = g.l0[:n-1]
|
||||||
iterOpts := IterOptions{logger: g.logger}
|
iterOpts := IterOptions{logger: g.logger, snapshotForHideObsoletePoints: g.snapshot}
|
||||||
g.levelIter.init(context.Background(), iterOpts, g.cmp, nil /* split */, g.newIters,
|
g.levelIter.init(context.Background(), iterOpts, g.comparer, g.newIters,
|
||||||
files, manifest.L0Sublevel(n), internalIterOpts{})
|
files, manifest.L0Sublevel(n), internalIterOpts{})
|
||||||
g.levelIter.initRangeDel(&g.rangeDelIter)
|
g.levelIter.initRangeDel(&g.rangeDelIter)
|
||||||
|
bc := levelIterBoundaryContext{}
|
||||||
|
g.levelIter.initBoundaryContext(&bc)
|
||||||
g.iter = &g.levelIter
|
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
|
continue
|
||||||
}
|
}
|
||||||
g.level++
|
g.level++
|
||||||
@@ -177,13 +189,26 @@ func (g *getIter) Next() (*InternalKey, base.LazyValue) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
iterOpts := IterOptions{logger: g.logger}
|
iterOpts := IterOptions{logger: g.logger, snapshotForHideObsoletePoints: g.snapshot}
|
||||||
g.levelIter.init(context.Background(), iterOpts, g.cmp, nil /* split */, g.newIters,
|
g.levelIter.init(context.Background(), iterOpts, g.comparer, g.newIters,
|
||||||
g.version.Levels[g.level].Iter(), manifest.Level(g.level), internalIterOpts{})
|
g.version.Levels[g.level].Iter(), manifest.Level(g.level), internalIterOpts{})
|
||||||
g.levelIter.initRangeDel(&g.rangeDelIter)
|
g.levelIter.initRangeDel(&g.rangeDelIter)
|
||||||
|
bc := levelIterBoundaryContext{}
|
||||||
|
g.levelIter.initBoundaryContext(&bc)
|
||||||
g.level++
|
g.level++
|
||||||
g.iter = &g.levelIter
|
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{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1458
vendor/github.com/cockroachdb/pebble/ingest.go
generated
vendored
1458
vendor/github.com/cockroachdb/pebble/ingest.go
generated
vendored
File diff suppressed because it is too large
Load Diff
3
vendor/github.com/cockroachdb/pebble/internal.go
generated
vendored
3
vendor/github.com/cockroachdb/pebble/internal.go
generated
vendored
@@ -22,7 +22,8 @@ const (
|
|||||||
InternalKeyKindRangeKeySet = base.InternalKeyKindRangeKeySet
|
InternalKeyKindRangeKeySet = base.InternalKeyKindRangeKeySet
|
||||||
InternalKeyKindRangeKeyUnset = base.InternalKeyKindRangeKeyUnset
|
InternalKeyKindRangeKeyUnset = base.InternalKeyKindRangeKeyUnset
|
||||||
InternalKeyKindRangeKeyDelete = base.InternalKeyKindRangeKeyDelete
|
InternalKeyKindRangeKeyDelete = base.InternalKeyKindRangeKeyDelete
|
||||||
InternalKeyKindIngestSST = base.InternalKeyIngestSST
|
InternalKeyKindIngestSST = base.InternalKeyKindIngestSST
|
||||||
|
InternalKeyKindDeleteSized = base.InternalKeyKindDeleteSized
|
||||||
InternalKeyKindInvalid = base.InternalKeyKindInvalid
|
InternalKeyKindInvalid = base.InternalKeyKindInvalid
|
||||||
InternalKeySeqNumBatch = base.InternalKeySeqNumBatch
|
InternalKeySeqNumBatch = base.InternalKeySeqNumBatch
|
||||||
InternalKeySeqNumMax = base.InternalKeySeqNumMax
|
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
|
package arenaskl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/cockroachdb/pebble/internal/constants"
|
||||||
|
"github.com/cockroachdb/pebble/internal/invariants"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Arena is lock-free.
|
// Arena is lock-free.
|
||||||
type Arena struct {
|
type Arena struct {
|
||||||
n uint64
|
n atomic.Uint64
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const nodeAlignment = 4
|
||||||
align4 = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrArenaFull indicates that the arena is full and cannot perform any more
|
// 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
|
// NewArena allocates a new arena using the specified buffer as the backing
|
||||||
// store.
|
// store.
|
||||||
func NewArena(buf []byte) *Arena {
|
func NewArena(buf []byte) *Arena {
|
||||||
// Don't store data at position 0 in order to reserve offset=0 as a kind
|
if len(buf) > constants.MaxUint32OrInt {
|
||||||
// of nil pointer.
|
if invariants.Enabled {
|
||||||
return &Arena{
|
panic(errors.AssertionFailedf("attempting to create arena of size %d", len(buf)))
|
||||||
n: 1,
|
}
|
||||||
|
buf = buf[:constants.MaxUint32OrInt]
|
||||||
|
}
|
||||||
|
a := &Arena{
|
||||||
buf: buf,
|
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.
|
// Size returns the number of bytes allocated by the arena.
|
||||||
func (a *Arena) Size() uint32 {
|
func (a *Arena) Size() uint32 {
|
||||||
s := atomic.LoadUint64(&a.n)
|
s := a.n.Load()
|
||||||
if s > math.MaxUint32 {
|
if s > constants.MaxUint32OrInt {
|
||||||
// Saturate at MaxUint32.
|
// The last failed allocation can push the size higher than len(a.buf).
|
||||||
return math.MaxUint32
|
// Saturate at the maximum representable offset.
|
||||||
|
return constants.MaxUint32OrInt
|
||||||
}
|
}
|
||||||
return uint32(s)
|
return uint32(s)
|
||||||
}
|
}
|
||||||
@@ -67,24 +74,33 @@ func (a *Arena) Capacity() uint32 {
|
|||||||
return uint32(len(a.buf))
|
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.
|
// Verify that the arena isn't already full.
|
||||||
origSize := atomic.LoadUint64(&a.n)
|
origSize := a.n.Load()
|
||||||
if int(origSize) > len(a.buf) {
|
if int(origSize) > len(a.buf) {
|
||||||
return 0, 0, ErrArenaFull
|
return 0, 0, ErrArenaFull
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pad the allocation with enough bytes to ensure the requested alignment.
|
// 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))
|
newSize := a.n.Add(padded)
|
||||||
if int(newSize)+int(overflow) > len(a.buf) {
|
if newSize+uint64(overflow) > uint64(len(a.buf)) {
|
||||||
return 0, 0, ErrArenaFull
|
return 0, 0, ErrArenaFull
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the aligned offset.
|
// Return the aligned offset.
|
||||||
offset := (uint32(newSize) - padded + align) & ^align
|
offset := (uint32(newSize) - size) & ^(alignment - 1)
|
||||||
return offset, padded, nil
|
return offset, uint32(padded), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Arena) getBytes(offset uint32, size uint32) []byte {
|
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
|
package arenaskl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
@@ -240,17 +239,8 @@ func (it *Iterator) SetBounds(lower, upper []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (it *Iterator) decodeKey() {
|
func (it *Iterator) decodeKey() {
|
||||||
b := it.list.arena.getBytes(it.nd.keyOffset, it.nd.keySize)
|
it.key.UserKey = it.list.arena.getBytes(it.nd.keyOffset, it.nd.keySize)
|
||||||
// This is a manual inline of base.DecodeInternalKey, because the Go compiler
|
it.key.Trailer = it.nd.keyTrailer
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *Iterator) seekForBaseSplice(key []byte) (prev, next *node, found bool) {
|
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
|
// is used here. If a key/value overflows a uint32, it should not be added to
|
||||||
// the skiplist.
|
// the skiplist.
|
||||||
func MaxNodeSize(keySize, valueSize uint32) uint64 {
|
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 {
|
type links struct {
|
||||||
nextOffset uint32
|
nextOffset atomic.Uint32
|
||||||
prevOffset uint32
|
prevOffset atomic.Uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *links) init(prevOffset, nextOffset uint32) {
|
func (l *links) init(prevOffset, nextOffset uint32) {
|
||||||
l.nextOffset = nextOffset
|
l.nextOffset.Store(nextOffset)
|
||||||
l.prevOffset = prevOffset
|
l.prevOffset.Store(prevOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
type node struct {
|
type node struct {
|
||||||
// Immutable fields, so no need to lock to access key.
|
// Immutable fields, so no need to lock to access key.
|
||||||
keyOffset uint32
|
keyOffset uint32
|
||||||
keySize uint32
|
keySize uint32
|
||||||
valueSize uint32
|
keyTrailer uint64
|
||||||
allocSize uint32
|
valueSize uint32
|
||||||
|
allocSize uint32
|
||||||
|
|
||||||
// Most nodes do not need to use the full height of the tower, since the
|
// Most nodes do not need to use the full height of the tower, since the
|
||||||
// probability of each successive level decreases exponentially. Because
|
// probability of each successive level decreases exponentially. Because
|
||||||
@@ -65,7 +67,7 @@ func newNode(
|
|||||||
if height < 1 || height > maxHeight {
|
if height < 1 || height > maxHeight {
|
||||||
panic("height cannot be less than one or greater than the max height")
|
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 {
|
if int64(keySize) > math.MaxUint32 {
|
||||||
panic("key is too large")
|
panic("key is too large")
|
||||||
}
|
}
|
||||||
@@ -81,8 +83,8 @@ func newNode(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
nd.keyTrailer = key.Trailer
|
||||||
key.Encode(nd.getKeyBytes(arena))
|
copy(nd.getKeyBytes(arena), key.UserKey)
|
||||||
copy(nd.getValue(arena), value)
|
copy(nd.getValue(arena), value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -93,7 +95,7 @@ func newRawNode(arena *Arena, height uint32, keySize, valueSize uint32) (nd *nod
|
|||||||
unusedSize := uint32((maxHeight - int(height)) * linksSize)
|
unusedSize := uint32((maxHeight - int(height)) * linksSize)
|
||||||
nodeSize := uint32(maxNodeSize) - unusedSize
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -115,17 +117,17 @@ func (n *node) getValue(arena *Arena) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) nextOffset(h int) uint32 {
|
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 {
|
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 {
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
53
vendor/github.com/cockroachdb/pebble/internal/arenaskl/skl.go
generated
vendored
53
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"
|
package arenaskl // import "github.com/cockroachdb/pebble/internal/arenaskl"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"math"
|
"math"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -68,7 +67,7 @@ const (
|
|||||||
// keys.
|
// keys.
|
||||||
var ErrRecordExists = errors.New("record with this key already exists")
|
var ErrRecordExists = errors.New("record with this key already exists")
|
||||||
|
|
||||||
// Skiplist is a fast, cocnurrent skiplist implementation that supports forward
|
// Skiplist is a fast, concurrent skiplist implementation that supports forward
|
||||||
// and backward iteration. See batchskl.Skiplist for a non-concurrent
|
// and backward iteration. See batchskl.Skiplist for a non-concurrent
|
||||||
// skiplist. Keys and values are immutable once added to the skiplist and
|
// skiplist. Keys and values are immutable once added to the skiplist and
|
||||||
// deletion is not supported. Instead, higher-level code is expected to add new
|
// deletion is not supported. Instead, higher-level code is expected to add new
|
||||||
@@ -80,7 +79,7 @@ type Skiplist struct {
|
|||||||
cmp base.Compare
|
cmp base.Compare
|
||||||
head *node
|
head *node
|
||||||
tail *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
|
// If set to true by tests, then extra delays are added to make it easier to
|
||||||
// detect unusual race conditions.
|
// detect unusual race conditions.
|
||||||
@@ -140,22 +139,22 @@ func (s *Skiplist) Reset(arena *Arena, cmp base.Compare) {
|
|||||||
headOffset := arena.getPointerOffset(unsafe.Pointer(head))
|
headOffset := arena.getPointerOffset(unsafe.Pointer(head))
|
||||||
tailOffset := arena.getPointerOffset(unsafe.Pointer(tail))
|
tailOffset := arena.getPointerOffset(unsafe.Pointer(tail))
|
||||||
for i := 0; i < maxHeight; i++ {
|
for i := 0; i < maxHeight; i++ {
|
||||||
head.tower[i].nextOffset = tailOffset
|
head.tower[i].nextOffset.Store(tailOffset)
|
||||||
tail.tower[i].prevOffset = headOffset
|
tail.tower[i].prevOffset.Store(headOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
*s = Skiplist{
|
*s = Skiplist{
|
||||||
arena: arena,
|
arena: arena,
|
||||||
cmp: cmp,
|
cmp: cmp,
|
||||||
head: head,
|
head: head,
|
||||||
tail: tail,
|
tail: tail,
|
||||||
height: 1,
|
|
||||||
}
|
}
|
||||||
|
s.height.Store(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Height returns the height of the highest tower within any of the nodes that
|
// Height returns the height of the highest tower within any of the nodes that
|
||||||
// have ever been allocated as part of this skiplist.
|
// 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.
|
// Arena returns the arena backing this skiplist.
|
||||||
func (s *Skiplist) Arena() *Arena { return s.arena }
|
func (s *Skiplist) Arena() *Arena { return s.arena }
|
||||||
@@ -327,7 +326,7 @@ func (s *Skiplist) newNode(
|
|||||||
// Try to increase s.height via CAS.
|
// Try to increase s.height via CAS.
|
||||||
listHeight := s.Height()
|
listHeight := s.Height()
|
||||||
for height > listHeight {
|
for height > listHeight {
|
||||||
if atomic.CompareAndSwapUint32(&s.height, listHeight, height) {
|
if s.height.CompareAndSwap(listHeight, height) {
|
||||||
// Successfully increased skiplist.height.
|
// Successfully increased skiplist.height.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -412,26 +411,19 @@ func (s *Skiplist) findSpliceForLevel(
|
|||||||
|
|
||||||
offset, size := next.keyOffset, next.keySize
|
offset, size := next.keyOffset, next.keySize
|
||||||
nextKey := s.arena.buf[offset : offset+size]
|
nextKey := s.arena.buf[offset : offset+size]
|
||||||
n := int32(size) - 8
|
cmp := s.cmp(key.UserKey, nextKey)
|
||||||
cmp := s.cmp(key.UserKey, nextKey[:n])
|
|
||||||
if cmp < 0 {
|
if cmp < 0 {
|
||||||
// We are done for this level, since prev.key < key < next.key.
|
// We are done for this level, since prev.key < key < next.key.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if cmp == 0 {
|
if cmp == 0 {
|
||||||
// User-key equality.
|
// User-key equality.
|
||||||
var nextTrailer uint64
|
if key.Trailer == next.keyTrailer {
|
||||||
if n >= 0 {
|
|
||||||
nextTrailer = binary.LittleEndian.Uint64(nextKey[n:])
|
|
||||||
} else {
|
|
||||||
nextTrailer = uint64(base.InternalKeyKindInvalid)
|
|
||||||
}
|
|
||||||
if key.Trailer == nextTrailer {
|
|
||||||
// Internal key equality.
|
// Internal key equality.
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if key.Trailer > nextTrailer {
|
if key.Trailer > next.keyTrailer {
|
||||||
// We are done for this level, since prev.key < key < next.key.
|
// We are done for this level, since prev.key < key < next.key.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -446,8 +438,7 @@ func (s *Skiplist) findSpliceForLevel(
|
|||||||
|
|
||||||
func (s *Skiplist) keyIsAfterNode(nd *node, key base.InternalKey) bool {
|
func (s *Skiplist) keyIsAfterNode(nd *node, key base.InternalKey) bool {
|
||||||
ndKey := s.arena.buf[nd.keyOffset : nd.keyOffset+nd.keySize]
|
ndKey := s.arena.buf[nd.keyOffset : nd.keyOffset+nd.keySize]
|
||||||
n := int32(nd.keySize) - 8
|
cmp := s.cmp(ndKey, key.UserKey)
|
||||||
cmp := s.cmp(ndKey[:n], key.UserKey)
|
|
||||||
if cmp < 0 {
|
if cmp < 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -455,25 +446,19 @@ func (s *Skiplist) keyIsAfterNode(nd *node, key base.InternalKey) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// User-key equality.
|
// User-key equality.
|
||||||
var ndTrailer uint64
|
if key.Trailer == nd.keyTrailer {
|
||||||
if n >= 0 {
|
|
||||||
ndTrailer = binary.LittleEndian.Uint64(ndKey[n:])
|
|
||||||
} else {
|
|
||||||
ndTrailer = uint64(base.InternalKeyKindInvalid)
|
|
||||||
}
|
|
||||||
if key.Trailer == ndTrailer {
|
|
||||||
// Internal key equality.
|
// Internal key equality.
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return key.Trailer < ndTrailer
|
return key.Trailer < nd.keyTrailer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Skiplist) getNext(nd *node, h int) *node {
|
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))
|
return (*node)(s.arena.getPointer(offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Skiplist) getPrev(nd *node, h int) *node {
|
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))
|
return (*node)(s.arena.getPointer(offset))
|
||||||
}
|
}
|
||||||
|
|||||||
89
vendor/github.com/cockroachdb/pebble/internal/base/filenames.go
generated
vendored
89
vendor/github.com/cockroachdb/pebble/internal/base/filenames.go
generated
vendored
@@ -20,6 +20,41 @@ type FileNum uint64
|
|||||||
// String returns a string representation of the file number.
|
// String returns a string representation of the file number.
|
||||||
func (fn FileNum) String() string { return fmt.Sprintf("%06d", fn) }
|
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
|
||||||
|
// options file, but not virtual sstables.
|
||||||
|
func (fn FileNum) DiskFileNum() DiskFileNum {
|
||||||
|
return DiskFileNum{fn}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DiskFileNum is just a FileNum belonging to a file which exists on disk.
|
||||||
|
// Note that a FileNum is an internal DB identifier and it could belong to files
|
||||||
|
// which don't exist on disk. An example would be virtual sstable FileNums.
|
||||||
|
// Converting a DiskFileNum to a FileNum is always valid, whereas converting a
|
||||||
|
// FileNum to DiskFileNum may not be valid and care should be taken to prove
|
||||||
|
// that the FileNum actually exists on disk.
|
||||||
|
type DiskFileNum struct {
|
||||||
|
fn FileNum
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// FileType enumerates the types of files found in a DB.
|
// FileType enumerates the types of files found in a DB.
|
||||||
type FileType int
|
type FileType int
|
||||||
|
|
||||||
@@ -36,92 +71,92 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// MakeFilename builds a filename from components.
|
// MakeFilename builds a filename from components.
|
||||||
func MakeFilename(fileType FileType, fileNum FileNum) string {
|
func MakeFilename(fileType FileType, dfn DiskFileNum) string {
|
||||||
switch fileType {
|
switch fileType {
|
||||||
case FileTypeLog:
|
case FileTypeLog:
|
||||||
return fmt.Sprintf("%s.log", fileNum)
|
return fmt.Sprintf("%s.log", dfn)
|
||||||
case FileTypeLock:
|
case FileTypeLock:
|
||||||
return "LOCK"
|
return "LOCK"
|
||||||
case FileTypeTable:
|
case FileTypeTable:
|
||||||
return fmt.Sprintf("%s.sst", fileNum)
|
return fmt.Sprintf("%s.sst", dfn)
|
||||||
case FileTypeManifest:
|
case FileTypeManifest:
|
||||||
return fmt.Sprintf("MANIFEST-%s", fileNum)
|
return fmt.Sprintf("MANIFEST-%s", dfn)
|
||||||
case FileTypeCurrent:
|
case FileTypeCurrent:
|
||||||
return "CURRENT"
|
return "CURRENT"
|
||||||
case FileTypeOptions:
|
case FileTypeOptions:
|
||||||
return fmt.Sprintf("OPTIONS-%s", fileNum)
|
return fmt.Sprintf("OPTIONS-%s", dfn)
|
||||||
case FileTypeOldTemp:
|
case FileTypeOldTemp:
|
||||||
return fmt.Sprintf("CURRENT.%s.dbtmp", fileNum)
|
return fmt.Sprintf("CURRENT.%s.dbtmp", dfn)
|
||||||
case FileTypeTemp:
|
case FileTypeTemp:
|
||||||
return fmt.Sprintf("temporary.%s.dbtmp", fileNum)
|
return fmt.Sprintf("temporary.%s.dbtmp", dfn)
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeFilepath builds a filepath from components.
|
// MakeFilepath builds a filepath from components.
|
||||||
func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, fileNum FileNum) string {
|
func MakeFilepath(fs vfs.FS, dirname string, fileType FileType, dfn DiskFileNum) string {
|
||||||
return fs.PathJoin(dirname, MakeFilename(fileType, fileNum))
|
return fs.PathJoin(dirname, MakeFilename(fileType, dfn))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFilename parses the components from a filename.
|
// ParseFilename parses the components from a filename.
|
||||||
func ParseFilename(fs vfs.FS, filename string) (fileType FileType, fileNum FileNum, ok bool) {
|
func ParseFilename(fs vfs.FS, filename string) (fileType FileType, dfn DiskFileNum, ok bool) {
|
||||||
filename = fs.PathBase(filename)
|
filename = fs.PathBase(filename)
|
||||||
switch {
|
switch {
|
||||||
case filename == "CURRENT":
|
case filename == "CURRENT":
|
||||||
return FileTypeCurrent, 0, true
|
return FileTypeCurrent, DiskFileNum{0}, true
|
||||||
case filename == "LOCK":
|
case filename == "LOCK":
|
||||||
return FileTypeLock, 0, true
|
return FileTypeLock, DiskFileNum{0}, true
|
||||||
case strings.HasPrefix(filename, "MANIFEST-"):
|
case strings.HasPrefix(filename, "MANIFEST-"):
|
||||||
fileNum, ok = parseFileNum(filename[len("MANIFEST-"):])
|
dfn, ok = parseDiskFileNum(filename[len("MANIFEST-"):])
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return FileTypeManifest, fileNum, true
|
return FileTypeManifest, dfn, true
|
||||||
case strings.HasPrefix(filename, "OPTIONS-"):
|
case strings.HasPrefix(filename, "OPTIONS-"):
|
||||||
fileNum, ok = parseFileNum(filename[len("OPTIONS-"):])
|
dfn, ok = parseDiskFileNum(filename[len("OPTIONS-"):])
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return FileTypeOptions, fileNum, ok
|
return FileTypeOptions, dfn, ok
|
||||||
case strings.HasPrefix(filename, "CURRENT.") && strings.HasSuffix(filename, ".dbtmp"):
|
case strings.HasPrefix(filename, "CURRENT.") && strings.HasSuffix(filename, ".dbtmp"):
|
||||||
s := strings.TrimSuffix(filename[len("CURRENT."):], ".dbtmp")
|
s := strings.TrimSuffix(filename[len("CURRENT."):], ".dbtmp")
|
||||||
fileNum, ok = parseFileNum(s)
|
dfn, ok = parseDiskFileNum(s)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return FileTypeOldTemp, fileNum, ok
|
return FileTypeOldTemp, dfn, ok
|
||||||
case strings.HasPrefix(filename, "temporary.") && strings.HasSuffix(filename, ".dbtmp"):
|
case strings.HasPrefix(filename, "temporary.") && strings.HasSuffix(filename, ".dbtmp"):
|
||||||
s := strings.TrimSuffix(filename[len("temporary."):], ".dbtmp")
|
s := strings.TrimSuffix(filename[len("temporary."):], ".dbtmp")
|
||||||
fileNum, ok = parseFileNum(s)
|
dfn, ok = parseDiskFileNum(s)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return FileTypeTemp, fileNum, ok
|
return FileTypeTemp, dfn, ok
|
||||||
default:
|
default:
|
||||||
i := strings.IndexByte(filename, '.')
|
i := strings.IndexByte(filename, '.')
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
fileNum, ok = parseFileNum(filename[:i])
|
dfn, ok = parseDiskFileNum(filename[:i])
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
switch filename[i+1:] {
|
switch filename[i+1:] {
|
||||||
case "sst":
|
case "sst":
|
||||||
return FileTypeTable, fileNum, true
|
return FileTypeTable, dfn, true
|
||||||
case "log":
|
case "log":
|
||||||
return FileTypeLog, fileNum, true
|
return FileTypeLog, dfn, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0, fileNum, false
|
return 0, dfn, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFileNum(s string) (fileNum FileNum, ok bool) {
|
func parseDiskFileNum(s string) (dfn DiskFileNum, ok bool) {
|
||||||
u, err := strconv.ParseUint(s, 10, 64)
|
u, err := strconv.ParseUint(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fileNum, false
|
return dfn, false
|
||||||
}
|
}
|
||||||
return FileNum(u), true
|
return DiskFileNum{FileNum(u)}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Fataler fatals a process with a message when called.
|
// A Fataler fatals a process with a message when called.
|
||||||
|
|||||||
71
vendor/github.com/cockroachdb/pebble/internal/base/internal.go
generated
vendored
71
vendor/github.com/cockroachdb/pebble/internal/base/internal.go
generated
vendored
@@ -9,6 +9,17 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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
|
// InternalKeyKind enumerates the kind of key: a deletion tombstone, a set
|
||||||
@@ -24,6 +35,14 @@ const (
|
|||||||
//InternalKeyKindColumnFamilyDeletion InternalKeyKind = 4
|
//InternalKeyKindColumnFamilyDeletion InternalKeyKind = 4
|
||||||
//InternalKeyKindColumnFamilyValue InternalKeyKind = 5
|
//InternalKeyKindColumnFamilyValue InternalKeyKind = 5
|
||||||
//InternalKeyKindColumnFamilyMerge InternalKeyKind = 6
|
//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
|
InternalKeyKindSingleDelete InternalKeyKind = 7
|
||||||
//InternalKeyKindColumnFamilySingleDelete InternalKeyKind = 8
|
//InternalKeyKindColumnFamilySingleDelete InternalKeyKind = 8
|
||||||
//InternalKeyKindBeginPrepareXID InternalKeyKind = 9
|
//InternalKeyKindBeginPrepareXID InternalKeyKind = 9
|
||||||
@@ -60,14 +79,21 @@ const (
|
|||||||
InternalKeyKindRangeKeyUnset InternalKeyKind = 20
|
InternalKeyKindRangeKeyUnset InternalKeyKind = 20
|
||||||
InternalKeyKindRangeKeySet InternalKeyKind = 21
|
InternalKeyKindRangeKeySet InternalKeyKind = 21
|
||||||
|
|
||||||
// InternalKeyIngestSST is used to distinguish a batch that corresponds to
|
// InternalKeyKindIngestSST is used to distinguish a batch that corresponds to
|
||||||
// the WAL entry for ingested sstables that are added to the flushable
|
// the WAL entry for ingested sstables that are added to the flushable
|
||||||
// queue. This InternalKeyKind cannot appear, amongst other key kinds in a
|
// queue. This InternalKeyKind cannot appear, amongst other key kinds in a
|
||||||
// batch, or in an sstable.
|
// batch, or in an sstable.
|
||||||
InternalKeyIngestSST InternalKeyKind = 22
|
InternalKeyKindIngestSST InternalKeyKind = 22
|
||||||
|
|
||||||
// This maximum value isn't part of the file format. It's unlikely,
|
// InternalKeyKindDeleteSized keys behave identically to
|
||||||
// but future extensions may increase this value.
|
// 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},
|
// When constructing an internal key to pass to DB.Seek{GE,LE},
|
||||||
// internalKeyComparer sorts decreasing by kind (after sorting increasing by
|
// 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
|
// 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
|
// searching for any kind of internal key formed by a certain user key and
|
||||||
// seqNum.
|
// 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
|
// InternalKeyZeroSeqnumMaxTrailer is the largest trailer with a
|
||||||
// zero sequence number.
|
// zero sequence number.
|
||||||
InternalKeyZeroSeqnumMaxTrailer = uint64(InternalKeyKindInvalid)
|
InternalKeyZeroSeqnumMaxTrailer = uint64(255)
|
||||||
|
|
||||||
// A marker for an invalid key.
|
// A marker for an invalid key.
|
||||||
InternalKeyKindInvalid InternalKeyKind = 255
|
InternalKeyKindInvalid InternalKeyKind = InternalKeyKindSSTableInternalObsoleteMask
|
||||||
|
|
||||||
// InternalKeySeqNumBatch is a bit that is set on batch sequence numbers
|
// InternalKeySeqNumBatch is a bit that is set on batch sequence numbers
|
||||||
// which prevents those entries from being excluded from iteration.
|
// which prevents those entries from being excluded from iteration.
|
||||||
@@ -105,6 +136,9 @@ const (
|
|||||||
InternalKeyBoundaryRangeKey = (InternalKeySeqNumMax << 8) | uint64(InternalKeyKindRangeKeySet)
|
InternalKeyBoundaryRangeKey = (InternalKeySeqNumMax << 8) | uint64(InternalKeyKindRangeKeySet)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Assert InternalKeyKindSSTableInternalObsoleteBit > InternalKeyKindMax
|
||||||
|
const _ = uint(InternalKeyKindSSTableInternalObsoleteBit - InternalKeyKindMax - 1)
|
||||||
|
|
||||||
var internalKeyKindNames = []string{
|
var internalKeyKindNames = []string{
|
||||||
InternalKeyKindDelete: "DEL",
|
InternalKeyKindDelete: "DEL",
|
||||||
InternalKeyKindSet: "SET",
|
InternalKeyKindSet: "SET",
|
||||||
@@ -117,7 +151,8 @@ var internalKeyKindNames = []string{
|
|||||||
InternalKeyKindRangeKeySet: "RANGEKEYSET",
|
InternalKeyKindRangeKeySet: "RANGEKEYSET",
|
||||||
InternalKeyKindRangeKeyUnset: "RANGEKEYUNSET",
|
InternalKeyKindRangeKeyUnset: "RANGEKEYUNSET",
|
||||||
InternalKeyKindRangeKeyDelete: "RANGEKEYDEL",
|
InternalKeyKindRangeKeyDelete: "RANGEKEYDEL",
|
||||||
InternalKeyIngestSST: "INGESTSST",
|
InternalKeyKindIngestSST: "INGESTSST",
|
||||||
|
InternalKeyKindDeleteSized: "DELSIZED",
|
||||||
InternalKeyKindInvalid: "INVALID",
|
InternalKeyKindInvalid: "INVALID",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,6 +163,11 @@ func (k InternalKeyKind) String() string {
|
|||||||
return fmt.Sprintf("UNKNOWN:%d", k)
|
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
|
// InternalKey is a key used for the in-memory and on-disk partial DBs that
|
||||||
// make up a pebble DB.
|
// make up a pebble DB.
|
||||||
//
|
//
|
||||||
@@ -194,6 +234,7 @@ var kindsMap = map[string]InternalKeyKind{
|
|||||||
"DEL": InternalKeyKindDelete,
|
"DEL": InternalKeyKindDelete,
|
||||||
"SINGLEDEL": InternalKeyKindSingleDelete,
|
"SINGLEDEL": InternalKeyKindSingleDelete,
|
||||||
"RANGEDEL": InternalKeyKindRangeDelete,
|
"RANGEDEL": InternalKeyKindRangeDelete,
|
||||||
|
"LOGDATA": InternalKeyKindLogData,
|
||||||
"SET": InternalKeyKindSet,
|
"SET": InternalKeyKindSet,
|
||||||
"MERGE": InternalKeyKindMerge,
|
"MERGE": InternalKeyKindMerge,
|
||||||
"INVALID": InternalKeyKindInvalid,
|
"INVALID": InternalKeyKindInvalid,
|
||||||
@@ -202,6 +243,8 @@ var kindsMap = map[string]InternalKeyKind{
|
|||||||
"RANGEKEYSET": InternalKeyKindRangeKeySet,
|
"RANGEKEYSET": InternalKeyKindRangeKeySet,
|
||||||
"RANGEKEYUNSET": InternalKeyKindRangeKeyUnset,
|
"RANGEKEYUNSET": InternalKeyKindRangeKeyUnset,
|
||||||
"RANGEKEYDEL": InternalKeyKindRangeKeyDelete,
|
"RANGEKEYDEL": InternalKeyKindRangeKeyDelete,
|
||||||
|
"INGESTSST": InternalKeyKindIngestSST,
|
||||||
|
"DELSIZED": InternalKeyKindDeleteSized,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseInternalKey parses the string representation of an internal key. The
|
// ParseInternalKey parses the string representation of an internal key. The
|
||||||
@@ -338,6 +381,11 @@ func (k InternalKey) SeqNum() uint64 {
|
|||||||
return k.Trailer >> 8
|
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
|
// Visible returns true if the key is visible at the specified snapshot
|
||||||
// sequence number.
|
// sequence number.
|
||||||
func (k InternalKey) Visible(snapshot, batchSnapshot uint64) bool {
|
func (k InternalKey) Visible(snapshot, batchSnapshot uint64) bool {
|
||||||
@@ -395,6 +443,13 @@ func (k InternalKey) Clone() InternalKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CopyFrom converts this InternalKey into a clone of the passed-in InternalKey,
|
||||||
|
// reusing any space already used for the current UserKey.
|
||||||
|
func (k *InternalKey) CopyFrom(k2 InternalKey) {
|
||||||
|
k.UserKey = append(k.UserKey[:0], k2.UserKey...)
|
||||||
|
k.Trailer = k2.Trailer
|
||||||
|
}
|
||||||
|
|
||||||
// String returns a string representation of the key.
|
// String returns a string representation of the key.
|
||||||
func (k InternalKey) String() string {
|
func (k InternalKey) String() string {
|
||||||
return fmt.Sprintf("%s#%d,%d", FormatBytes(k.UserKey), k.SeqNum(), k.Kind())
|
return fmt.Sprintf("%s#%d,%d", FormatBytes(k.UserKey), k.SeqNum(), k.Kind())
|
||||||
|
|||||||
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))
|
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
|
// GaugeSampleMetric is used to measure a gauge value (e.g. queue length) by
|
||||||
// accumulating samples of that gauge.
|
// accumulating samples of that gauge.
|
||||||
type GaugeSampleMetric struct {
|
type GaugeSampleMetric struct {
|
||||||
|
|||||||
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/errors"
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
|
"github.com/cockroachdb/pebble/internal/constants"
|
||||||
"golang.org/x/exp/rand"
|
"golang.org/x/exp/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxHeight = 20
|
maxHeight = 20
|
||||||
maxNodeSize = int(unsafe.Sizeof(node{}))
|
maxNodeSize = uint64(unsafe.Sizeof(node{}))
|
||||||
linksSize = int(unsafe.Sizeof(links{}))
|
linksSize = uint64(unsafe.Sizeof(links{}))
|
||||||
maxNodesSize = math.MaxUint32
|
maxNodesSize = constants.MaxUint32OrInt
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -281,7 +282,7 @@ func (s *Skiplist) newNode(
|
|||||||
panic("height cannot be less than one or greater than the max height")
|
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))
|
nodeOffset, err := s.alloc(uint32(maxNodeSize - unusedSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -296,13 +297,13 @@ func (s *Skiplist) newNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Skiplist) alloc(size uint32) (uint32, error) {
|
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
|
// 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.
|
// to allocate a node whose tail points into unallocated memory.
|
||||||
minAllocSize := offset + maxNodeSize
|
minAllocSize := offset + maxNodeSize
|
||||||
if cap(s.nodes) < minAllocSize {
|
if uint64(cap(s.nodes)) < minAllocSize {
|
||||||
allocSize := cap(s.nodes) * 2
|
allocSize := uint64(cap(s.nodes)) * 2
|
||||||
if allocSize < minAllocSize {
|
if allocSize < minAllocSize {
|
||||||
allocSize = minAllocSize
|
allocSize = minAllocSize
|
||||||
}
|
}
|
||||||
|
|||||||
208
vendor/github.com/cockroachdb/pebble/internal/cache/clockpro.go
generated
vendored
208
vendor/github.com/cockroachdb/pebble/internal/cache/clockpro.go
generated
vendored
@@ -33,7 +33,7 @@ import (
|
|||||||
type fileKey struct {
|
type fileKey struct {
|
||||||
// id is the namespace for fileNums.
|
// id is the namespace for fileNums.
|
||||||
id uint64
|
id uint64
|
||||||
fileNum base.FileNum
|
fileNum base.DiskFileNum
|
||||||
}
|
}
|
||||||
|
|
||||||
type key struct {
|
type key struct {
|
||||||
@@ -71,14 +71,12 @@ func (h Handle) Get() []byte {
|
|||||||
|
|
||||||
// Release releases the reference to the cache entry.
|
// Release releases the reference to the cache entry.
|
||||||
func (h Handle) Release() {
|
func (h Handle) Release() {
|
||||||
if h.value != nil {
|
h.value.release()
|
||||||
h.value.release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type shard struct {
|
type shard struct {
|
||||||
hits int64
|
hits atomic.Int64
|
||||||
misses int64
|
misses atomic.Int64
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
@@ -113,25 +111,25 @@ type shard struct {
|
|||||||
countTest int64
|
countTest int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shard) Get(id uint64, fileNum base.FileNum, offset uint64) Handle {
|
func (c *shard) Get(id uint64, fileNum base.DiskFileNum, offset uint64) Handle {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
var value *Value
|
var value *Value
|
||||||
if e := c.blocks.Get(key{fileKey{id, fileNum}, offset}); e != nil {
|
if e := c.blocks.Get(key{fileKey{id, fileNum}, offset}); e != nil {
|
||||||
value = e.acquireValue()
|
value = e.acquireValue()
|
||||||
if value != nil {
|
if value != nil {
|
||||||
atomic.StoreInt32(&e.referenced, 1)
|
e.referenced.Store(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.mu.RUnlock()
|
c.mu.RUnlock()
|
||||||
if value == nil {
|
if value == nil {
|
||||||
atomic.AddInt64(&c.misses, 1)
|
c.misses.Add(1)
|
||||||
return Handle{}
|
return Handle{}
|
||||||
}
|
}
|
||||||
atomic.AddInt64(&c.hits, 1)
|
c.hits.Add(1)
|
||||||
return Handle{value: value}
|
return Handle{value: value}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shard) Set(id uint64, fileNum base.FileNum, offset uint64, value *Value) Handle {
|
func (c *shard) Set(id uint64, fileNum base.DiskFileNum, offset uint64, value *Value) Handle {
|
||||||
if n := value.refs(); n != 1 {
|
if n := value.refs(); n != 1 {
|
||||||
panic(fmt.Sprintf("pebble: Value has already been added to the cache: refs=%d", n))
|
panic(fmt.Sprintf("pebble: Value has already been added to the cache: refs=%d", n))
|
||||||
}
|
}
|
||||||
@@ -160,7 +158,7 @@ func (c *shard) Set(id uint64, fileNum base.FileNum, offset uint64, value *Value
|
|||||||
case e.peekValue() != nil:
|
case e.peekValue() != nil:
|
||||||
// cache entry was a hot or cold page
|
// cache entry was a hot or cold page
|
||||||
e.setValue(value)
|
e.setValue(value)
|
||||||
atomic.StoreInt32(&e.referenced, 1)
|
e.referenced.Store(true)
|
||||||
delta := int64(len(value.buf)) - e.size
|
delta := int64(len(value.buf)) - e.size
|
||||||
e.size = int64(len(value.buf))
|
e.size = int64(len(value.buf))
|
||||||
if e.ptype == etHot {
|
if e.ptype == etHot {
|
||||||
@@ -176,7 +174,7 @@ func (c *shard) Set(id uint64, fileNum base.FileNum, offset uint64, value *Value
|
|||||||
// cache entry was a test page
|
// cache entry was a test page
|
||||||
c.sizeTest -= e.size
|
c.sizeTest -= e.size
|
||||||
c.countTest--
|
c.countTest--
|
||||||
c.metaDel(e)
|
c.metaDel(e).release()
|
||||||
c.metaCheck(e)
|
c.metaCheck(e)
|
||||||
|
|
||||||
e.size = int64(len(value.buf))
|
e.size = int64(len(value.buf))
|
||||||
@@ -185,7 +183,7 @@ func (c *shard) Set(id uint64, fileNum base.FileNum, offset uint64, value *Value
|
|||||||
c.coldTarget = c.targetSize()
|
c.coldTarget = c.targetSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
atomic.StoreInt32(&e.referenced, 0)
|
e.referenced.Store(false)
|
||||||
e.setValue(value)
|
e.setValue(value)
|
||||||
e.ptype = etHot
|
e.ptype = etHot
|
||||||
if c.metaAdd(k, e) {
|
if c.metaAdd(k, e) {
|
||||||
@@ -222,7 +220,7 @@ func (c *shard) checkConsistency() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the cached value for the specified file and offset.
|
// Delete deletes the cached value for the specified file and offset.
|
||||||
func (c *shard) Delete(id uint64, fileNum base.FileNum, offset uint64) {
|
func (c *shard) Delete(id uint64, fileNum base.DiskFileNum, offset uint64) {
|
||||||
// The common case is there is nothing to delete, so do a quick check with
|
// The common case is there is nothing to delete, so do a quick check with
|
||||||
// shared lock.
|
// shared lock.
|
||||||
k := key{fileKey{id, fileNum}, offset}
|
k := key{fileKey{id, fileNum}, offset}
|
||||||
@@ -233,37 +231,75 @@ func (c *shard) Delete(id uint64, fileNum base.FileNum, offset uint64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mu.Lock()
|
var deletedValue *Value
|
||||||
defer c.mu.Unlock()
|
func() {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
e := c.blocks.Get(k)
|
e := c.blocks.Get(k)
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.metaEvict(e)
|
deletedValue = c.metaEvict(e)
|
||||||
|
c.checkConsistency()
|
||||||
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.
|
// EvictFile evicts all of the cache values for the specified file.
|
||||||
func (c *shard) EvictFile(id uint64, fileNum base.FileNum) {
|
func (c *shard) EvictFile(id uint64, fileNum base.DiskFileNum) {
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
fkey := key{fileKey{id, fileNum}, 0}
|
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)
|
blocks := c.files.Get(fkey)
|
||||||
if blocks == nil {
|
if blocks == nil {
|
||||||
return
|
// No blocks for this file.
|
||||||
}
|
return false
|
||||||
for b, n := blocks, (*entry)(nil); ; b = n {
|
|
||||||
n = b.fileLink.next
|
|
||||||
c.metaEvict(b)
|
|
||||||
if b == n {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
func (c *shard) Free() {
|
||||||
@@ -274,7 +310,7 @@ func (c *shard) Free() {
|
|||||||
// metaCheck call when the "invariants" build tag is specified.
|
// metaCheck call when the "invariants" build tag is specified.
|
||||||
for c.handHot != nil {
|
for c.handHot != nil {
|
||||||
e := c.handHot
|
e := c.handHot
|
||||||
c.metaDel(c.handHot)
|
c.metaDel(c.handHot).release()
|
||||||
e.free()
|
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,
|
// 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
|
// the files map, and ensures that hand{Hot,Cold,Test} are not pointing at the
|
||||||
// entry.
|
// entry. Returns the deleted value that must be released, if any.
|
||||||
func (c *shard) metaDel(e *entry) {
|
func (c *shard) metaDel(e *entry) (deletedValue *Value) {
|
||||||
if value := e.peekValue(); value != nil {
|
if value := e.peekValue(); value != nil {
|
||||||
value.ref.trace("metaDel")
|
value.ref.trace("metaDel")
|
||||||
}
|
}
|
||||||
e.setValue(nil)
|
// Remove the pointer to the value.
|
||||||
|
deletedValue = e.val
|
||||||
|
e.val = nil
|
||||||
|
|
||||||
c.blocks.Delete(e.key)
|
c.blocks.Delete(e.key)
|
||||||
if entriesGoAllocated {
|
if entriesGoAllocated {
|
||||||
@@ -396,6 +434,7 @@ func (c *shard) metaDel(e *entry) {
|
|||||||
} else {
|
} else {
|
||||||
c.files.Put(fkey, next)
|
c.files.Put(fkey, next)
|
||||||
}
|
}
|
||||||
|
return deletedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the specified entry is not referenced by the cache.
|
// 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 {
|
switch e.ptype {
|
||||||
case etHot:
|
case etHot:
|
||||||
c.sizeHot -= e.size
|
c.sizeHot -= e.size
|
||||||
@@ -467,9 +506,10 @@ func (c *shard) metaEvict(e *entry) {
|
|||||||
c.sizeTest -= e.size
|
c.sizeTest -= e.size
|
||||||
c.countTest--
|
c.countTest--
|
||||||
}
|
}
|
||||||
c.metaDel(e)
|
evictedValue = c.metaDel(e)
|
||||||
c.metaCheck(e)
|
c.metaCheck(e)
|
||||||
e.free()
|
e.free()
|
||||||
|
return evictedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *shard) evict() {
|
func (c *shard) evict() {
|
||||||
@@ -491,8 +531,8 @@ func (c *shard) runHandCold(countColdDebug, sizeColdDebug int64) {
|
|||||||
|
|
||||||
e := c.handCold
|
e := c.handCold
|
||||||
if e.ptype == etCold {
|
if e.ptype == etCold {
|
||||||
if atomic.LoadInt32(&e.referenced) == 1 {
|
if e.referenced.Load() {
|
||||||
atomic.StoreInt32(&e.referenced, 0)
|
e.referenced.Store(false)
|
||||||
e.ptype = etHot
|
e.ptype = etHot
|
||||||
c.sizeCold -= e.size
|
c.sizeCold -= e.size
|
||||||
c.countCold--
|
c.countCold--
|
||||||
@@ -528,8 +568,8 @@ func (c *shard) runHandHot() {
|
|||||||
|
|
||||||
e := c.handHot
|
e := c.handHot
|
||||||
if e.ptype == etHot {
|
if e.ptype == etHot {
|
||||||
if atomic.LoadInt32(&e.referenced) == 1 {
|
if e.referenced.Load() {
|
||||||
atomic.StoreInt32(&e.referenced, 0)
|
e.referenced.Store(false)
|
||||||
} else {
|
} else {
|
||||||
e.ptype = etCold
|
e.ptype = etCold
|
||||||
c.sizeHot -= e.size
|
c.sizeHot -= e.size
|
||||||
@@ -564,7 +604,7 @@ func (c *shard) runHandTest() {
|
|||||||
if c.coldTarget < 0 {
|
if c.coldTarget < 0 {
|
||||||
c.coldTarget = 0
|
c.coldTarget = 0
|
||||||
}
|
}
|
||||||
c.metaDel(e)
|
c.metaDel(e).release()
|
||||||
c.metaCheck(e)
|
c.metaCheck(e)
|
||||||
e.free()
|
e.free()
|
||||||
}
|
}
|
||||||
@@ -587,7 +627,7 @@ type Metrics struct {
|
|||||||
// Cache implements Pebble's sharded block cache. The Clock-PRO algorithm is
|
// Cache implements Pebble's sharded block cache. The Clock-PRO algorithm is
|
||||||
// used for page replacement
|
// used for page replacement
|
||||||
// (http://static.usenix.org/event/usenix05/tech/general/full_papers/jiang/jiang_html/html.html). In
|
// (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
|
// each shard being given 1/n of the target cache size. The Clock-PRO algorithm
|
||||||
// is run independently on each shard.
|
// is run independently on each shard.
|
||||||
//
|
//
|
||||||
@@ -621,9 +661,9 @@ type Metrics struct {
|
|||||||
// used in combination by specifying `-tags invariants,tracing`. Note that
|
// used in combination by specifying `-tags invariants,tracing`. Note that
|
||||||
// "tracing" produces a significant slowdown, while "invariants" does not.
|
// "tracing" produces a significant slowdown, while "invariants" does not.
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
refs int64
|
refs atomic.Int64
|
||||||
maxSize int64
|
maxSize int64
|
||||||
idAlloc uint64
|
idAlloc atomic.Uint64
|
||||||
shards []shard
|
shards []shard
|
||||||
|
|
||||||
// Traces recorded by Cache.trace. Used for debugging.
|
// Traces recorded by Cache.trace. Used for debugging.
|
||||||
@@ -643,17 +683,44 @@ type Cache struct {
|
|||||||
// defer c.Unref()
|
// defer c.Unref()
|
||||||
// d, err := pebble.Open(pebble.Options{Cache: c})
|
// d, err := pebble.Open(pebble.Options{Cache: c})
|
||||||
func New(size int64) *Cache {
|
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 {
|
func newShards(size int64, shards int) *Cache {
|
||||||
c := &Cache{
|
c := &Cache{
|
||||||
refs: 1,
|
|
||||||
maxSize: size,
|
maxSize: size,
|
||||||
idAlloc: 1,
|
|
||||||
shards: make([]shard, shards),
|
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 {
|
for i := range c.shards {
|
||||||
c.shards[i] = shard{
|
c.shards[i] = shard{
|
||||||
maxSize: size / int64(len(c.shards)),
|
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.
|
// Note: this is a no-op if invariants are disabled or race is enabled.
|
||||||
invariants.SetFinalizer(c, func(obj interface{}) {
|
invariants.SetFinalizer(c, func(obj interface{}) {
|
||||||
c := obj.(*Cache)
|
c := obj.(*Cache)
|
||||||
if v := atomic.LoadInt64(&c.refs); v != 0 {
|
if v := c.refs.Load(); v != 0 {
|
||||||
c.tr.Lock()
|
c.tr.Lock()
|
||||||
fmt.Fprintf(os.Stderr,
|
fmt.Fprintf(os.Stderr,
|
||||||
"pebble: cache (%p) has non-zero reference count: %d\n", c, v)
|
"pebble: cache (%p) has non-zero reference count: %d\n", c, v)
|
||||||
@@ -683,7 +750,7 @@ func newShards(size int64, shards int) *Cache {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cache) getShard(id uint64, fileNum base.FileNum, offset uint64) *shard {
|
func (c *Cache) getShard(id uint64, fileNum base.DiskFileNum, offset uint64) *shard {
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
panic("pebble: 0 cache ID is invalid")
|
panic("pebble: 0 cache ID is invalid")
|
||||||
}
|
}
|
||||||
@@ -698,10 +765,11 @@ func (c *Cache) getShard(id uint64, fileNum base.FileNum, offset uint64) *shard
|
|||||||
h ^= uint64(id & 0xff)
|
h ^= uint64(id & 0xff)
|
||||||
id >>= 8
|
id >>= 8
|
||||||
}
|
}
|
||||||
|
fileNumVal := uint64(fileNum.FileNum())
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
h *= prime64
|
h *= prime64
|
||||||
h ^= uint64(fileNum & 0xff)
|
h ^= uint64(fileNumVal) & 0xff
|
||||||
fileNum >>= 8
|
fileNumVal >>= 8
|
||||||
}
|
}
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
h *= prime64
|
h *= prime64
|
||||||
@@ -715,7 +783,7 @@ func (c *Cache) getShard(id uint64, fileNum base.FileNum, offset uint64) *shard
|
|||||||
// Ref adds a reference to the cache. The cache only remains valid as long a
|
// Ref adds a reference to the cache. The cache only remains valid as long a
|
||||||
// reference is maintained to it.
|
// reference is maintained to it.
|
||||||
func (c *Cache) Ref() {
|
func (c *Cache) Ref() {
|
||||||
v := atomic.AddInt64(&c.refs, 1)
|
v := c.refs.Add(1)
|
||||||
if v <= 1 {
|
if v <= 1 {
|
||||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||||
}
|
}
|
||||||
@@ -724,7 +792,7 @@ func (c *Cache) Ref() {
|
|||||||
|
|
||||||
// Unref releases a reference on the cache.
|
// Unref releases a reference on the cache.
|
||||||
func (c *Cache) Unref() {
|
func (c *Cache) Unref() {
|
||||||
v := atomic.AddInt64(&c.refs, -1)
|
v := c.refs.Add(-1)
|
||||||
c.trace("unref", v)
|
c.trace("unref", v)
|
||||||
switch {
|
switch {
|
||||||
case v < 0:
|
case v < 0:
|
||||||
@@ -738,7 +806,7 @@ func (c *Cache) Unref() {
|
|||||||
|
|
||||||
// Get retrieves the cache value for the specified file and offset, returning
|
// Get retrieves the cache value for the specified file and offset, returning
|
||||||
// nil if no value is present.
|
// nil if no value is present.
|
||||||
func (c *Cache) Get(id uint64, fileNum base.FileNum, offset uint64) Handle {
|
func (c *Cache) Get(id uint64, fileNum base.DiskFileNum, offset uint64) Handle {
|
||||||
return c.getShard(id, fileNum, offset).Get(id, fileNum, offset)
|
return c.getShard(id, fileNum, offset).Get(id, fileNum, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -746,17 +814,17 @@ func (c *Cache) Get(id uint64, fileNum base.FileNum, offset uint64) Handle {
|
|||||||
// existing value if present. A Handle is returned which provides faster
|
// existing value if present. A Handle is returned which provides faster
|
||||||
// retrieval of the cached value than Get (lock-free and avoidance of the map
|
// retrieval of the cached value than Get (lock-free and avoidance of the map
|
||||||
// lookup). The value must have been allocated by Cache.Alloc.
|
// lookup). The value must have been allocated by Cache.Alloc.
|
||||||
func (c *Cache) Set(id uint64, fileNum base.FileNum, offset uint64, value *Value) Handle {
|
func (c *Cache) Set(id uint64, fileNum base.DiskFileNum, offset uint64, value *Value) Handle {
|
||||||
return c.getShard(id, fileNum, offset).Set(id, fileNum, offset, value)
|
return c.getShard(id, fileNum, offset).Set(id, fileNum, offset, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the cached value for the specified file and offset.
|
// Delete deletes the cached value for the specified file and offset.
|
||||||
func (c *Cache) Delete(id uint64, fileNum base.FileNum, offset uint64) {
|
func (c *Cache) Delete(id uint64, fileNum base.DiskFileNum, offset uint64) {
|
||||||
c.getShard(id, fileNum, offset).Delete(id, fileNum, offset)
|
c.getShard(id, fileNum, offset).Delete(id, fileNum, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvictFile evicts all of the cache values for the specified file.
|
// EvictFile evicts all of the cache values for the specified file.
|
||||||
func (c *Cache) EvictFile(id uint64, fileNum base.FileNum) {
|
func (c *Cache) EvictFile(id uint64, fileNum base.DiskFileNum) {
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
panic("pebble: 0 cache ID is invalid")
|
panic("pebble: 0 cache ID is invalid")
|
||||||
}
|
}
|
||||||
@@ -784,14 +852,14 @@ func (c *Cache) Size() int64 {
|
|||||||
// manually managed. The caller MUST either add the value to the cache (via
|
// 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
|
// Cache.Set), or release the value (via Cache.Free). Failure to do so will
|
||||||
// result in a memory leak.
|
// result in a memory leak.
|
||||||
func (c *Cache) Alloc(n int) *Value {
|
func Alloc(n int) *Value {
|
||||||
return newValue(n)
|
return newValue(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free frees the specified value. The buffer associated with the value will
|
// Free frees the specified value. The buffer associated with the value will
|
||||||
// possibly be reused, making it invalid to use the buffer after calling
|
// 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.
|
// 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 {
|
if n := v.refs(); n > 1 {
|
||||||
panic(fmt.Sprintf("pebble: Value has been added to the cache: refs=%d", n))
|
panic(fmt.Sprintf("pebble: Value has been added to the cache: refs=%d", n))
|
||||||
}
|
}
|
||||||
@@ -828,8 +896,8 @@ func (c *Cache) Metrics() Metrics {
|
|||||||
m.Count += int64(s.blocks.Count())
|
m.Count += int64(s.blocks.Count())
|
||||||
m.Size += s.sizeHot + s.sizeCold
|
m.Size += s.sizeHot + s.sizeCold
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
m.Hits += atomic.LoadInt64(&s.hits)
|
m.Hits += s.hits.Load()
|
||||||
m.Misses += atomic.LoadInt64(&s.misses)
|
m.Misses += s.misses.Load()
|
||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@@ -837,5 +905,5 @@ func (c *Cache) Metrics() Metrics {
|
|||||||
// NewID returns a new ID to be used as a namespace for cached file
|
// NewID returns a new ID to be used as a namespace for cached file
|
||||||
// blocks.
|
// blocks.
|
||||||
func (c *Cache) NewID() uint64 {
|
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
|
package cache
|
||||||
|
|
||||||
|
import "sync/atomic"
|
||||||
|
|
||||||
type entryType int8
|
type entryType int8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -56,7 +58,7 @@ type entry struct {
|
|||||||
ptype entryType
|
ptype entryType
|
||||||
// referenced is atomically set to indicate that this entry has been accessed
|
// referenced is atomically set to indicate that this entry has been accessed
|
||||||
// since the last time one of the clock hands swept it.
|
// since the last time one of the clock hands swept it.
|
||||||
referenced int32
|
referenced atomic.Bool
|
||||||
shard *shard
|
shard *shard
|
||||||
// Reference count for the entry. The entry is freed when the reference count
|
// Reference count for the entry. The entry is freed when the reference count
|
||||||
// drops to zero.
|
// drops to zero.
|
||||||
@@ -137,9 +139,7 @@ func (e *entry) setValue(v *Value) {
|
|||||||
}
|
}
|
||||||
old := e.val
|
old := e.val
|
||||||
e.val = v
|
e.val = v
|
||||||
if old != nil {
|
old.release()
|
||||||
old.release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *entry) peekValue() *Value {
|
func (e *entry) peekValue() *Value {
|
||||||
|
|||||||
2
vendor/github.com/cockroachdb/pebble/internal/cache/robin_hood.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/internal/cache/robin_hood.go
generated
vendored
@@ -24,7 +24,7 @@ func robinHoodHash(k key, shift uint32) uint32 {
|
|||||||
const m = 11400714819323198485
|
const m = 11400714819323198485
|
||||||
h := hashSeed
|
h := hashSeed
|
||||||
h ^= k.id * m
|
h ^= k.id * m
|
||||||
h ^= uint64(k.fileNum) * m
|
h ^= uint64(k.fileNum.FileNum()) * m
|
||||||
h ^= k.offset * m
|
h ^= k.offset * m
|
||||||
return uint32(h >> shift)
|
return uint32(h >> shift)
|
||||||
}
|
}
|
||||||
|
|||||||
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() {
|
func (v *Value) release() {
|
||||||
if v.ref.release() {
|
if v != nil && v.ref.release() {
|
||||||
v.free()
|
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
|
suffix []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// IEC produces human readable representations of integer values in IEC units.
|
// Bytes produces human readable representations of byte values in IEC units.
|
||||||
var IEC = config{1024, []string{" B", " K", " M", " G", " T", " P", " E"}}
|
var Bytes = config{1024, []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}}
|
||||||
|
|
||||||
// SI produces human readable representations of integer values in SI units.
|
// Count produces human readable representations of unitless values in SI units.
|
||||||
var SI = config{1000, []string{"", " K", " M", " G", " T", " P", " E"}}
|
var Count = config{1000, []string{"", "K", "M", "G", "T", "P", "E"}}
|
||||||
|
|
||||||
// Int64 produces a human readable representation of the value.
|
// Int64 produces a human readable representation of the value.
|
||||||
func (c *config) Int64(s int64) FormattedString {
|
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))
|
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
|
// FormattedString represents a human readable representation of a value. It
|
||||||
// implements the redact.SafeValue interface to signal that it represents a
|
// implements the redact.SafeValue interface to signal that it represents a
|
||||||
// a string that does not need to be redacted.
|
// 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
|
// fragments before this span. Defragment backward to find the start of the
|
||||||
// defragmented span.
|
// defragmented span.
|
||||||
i.defragmentBackward()
|
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 {
|
if i.iterPos == iterPosPrev {
|
||||||
// Next once back onto the span.
|
// Next once back onto the span.
|
||||||
i.iterSpan = i.iter.Next()
|
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
|
// fragments after this span. Defragment forward to find the end of the
|
||||||
// defragmented span.
|
// defragmented span.
|
||||||
i.defragmentForward()
|
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 {
|
if i.iterPos == iterPosNext {
|
||||||
// Prev once back onto the span.
|
// Prev once back onto the span.
|
||||||
i.iterSpan = i.iter.Prev()
|
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
|
// Next once to move onto y, defragment forward to land on the first z
|
||||||
// position.
|
// position.
|
||||||
i.iterSpan = i.iter.Next()
|
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")
|
panic("pebble: invariant violation: no next span while switching directions")
|
||||||
}
|
}
|
||||||
// We're now positioned on the first span that was defragmented into the
|
// 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
|
// Prev once to move onto y, defragment backward to land on the last x
|
||||||
// position.
|
// position.
|
||||||
i.iterSpan = i.iter.Prev()
|
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")
|
panic("pebble: invariant violation: no previous span while switching directions")
|
||||||
}
|
}
|
||||||
// We're now positioned on the last span that was defragmented into the
|
// 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.keysBuf = i.reduce(i.keysBuf, i.iterSpan.Keys)
|
||||||
i.iterSpan = i.iter.Next()
|
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
|
i.curr.Keys = i.keysBuf
|
||||||
return &i.curr
|
return &i.curr
|
||||||
}
|
}
|
||||||
@@ -470,6 +495,15 @@ func (i *DefragmentingIter) defragmentBackward() *Span {
|
|||||||
i.keysBuf = i.reduce(i.keysBuf, i.iterSpan.Keys)
|
i.keysBuf = i.reduce(i.keysBuf, i.iterSpan.Keys)
|
||||||
i.iterSpan = i.iter.Prev()
|
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
|
i.curr.Keys = i.keysBuf
|
||||||
return &i.curr
|
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
|
package keyspan
|
||||||
|
|
||||||
|
import "github.com/cockroachdb/pebble/internal/base"
|
||||||
|
|
||||||
// FilterFunc defines a transform from the input Span into the output Span. The
|
// 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
|
// 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
|
// 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 {
|
type filteringIter struct {
|
||||||
iter FragmentIterator
|
iter FragmentIterator
|
||||||
filterFn FilterFunc
|
filterFn FilterFunc
|
||||||
|
cmp base.Compare
|
||||||
|
|
||||||
// span is a mutable Span passed to the filterFn. The filterFn is free to
|
// 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
|
// 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
|
// Filter returns a new filteringIter that will filter the Spans from the
|
||||||
// provided child iterator using the provided FilterFunc.
|
// provided child iterator using the provided FilterFunc.
|
||||||
func Filter(iter FragmentIterator, filter FilterFunc) FragmentIterator {
|
func Filter(iter FragmentIterator, filter FilterFunc, cmp base.Compare) FragmentIterator {
|
||||||
return &filteringIter{iter: iter, filterFn: filter}
|
return &filteringIter{iter: iter, filterFn: filter, cmp: cmp}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeekGE implements FragmentIterator.
|
// SeekGE implements FragmentIterator.
|
||||||
func (i *filteringIter) SeekGE(key []byte) *Span {
|
func (i *filteringIter) SeekGE(key []byte) *Span {
|
||||||
return i.filter(i.iter.SeekGE(key), +1)
|
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.
|
// SeekLT implements FragmentIterator.
|
||||||
func (i *filteringIter) SeekLT(key []byte) *Span {
|
func (i *filteringIter) SeekLT(key []byte) *Span {
|
||||||
return i.filter(i.iter.SeekLT(key), -1)
|
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.
|
// 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
|
Prev() *Span
|
||||||
|
|
||||||
// Error returns any accumulated error.
|
// Error returns any accumulated error.
|
||||||
|
//
|
||||||
|
// TODO(jackson): Lift errors into return values on the positioning methods.
|
||||||
Error() error
|
Error() error
|
||||||
|
|
||||||
// Close closes the iterator and returns any accumulated error. Exhausting
|
// 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
|
// TableNewSpanIter creates a new iterator for range key spans for the given
|
||||||
// file.
|
// 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
|
// SpanIterOptions is a subset of IterOptions that are necessary to instantiate
|
||||||
// per-sstable span iterators.
|
// 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.err = nil
|
||||||
l.level = level
|
l.level = level
|
||||||
l.tableOpts.RangeKeyFilters = opts.RangeKeyFilters
|
l.tableOpts = opts
|
||||||
l.cmp = cmp
|
l.cmp = cmp
|
||||||
l.iterFile = nil
|
l.iterFile = nil
|
||||||
l.newIter = newIter
|
l.newIter = newIter
|
||||||
@@ -179,7 +179,7 @@ func (l *LevelIter) loadFile(file *manifest.FileMetadata, dir int) loadFileRetur
|
|||||||
return noFileLoaded
|
return noFileLoaded
|
||||||
}
|
}
|
||||||
if indicator != fileAlreadyLoaded {
|
if indicator != fileAlreadyLoaded {
|
||||||
l.iter, l.err = l.newIter(file, &l.tableOpts)
|
l.iter, l.err = l.newIter(file, l.tableOpts)
|
||||||
indicator = newFileLoaded
|
indicator = newFileLoaded
|
||||||
}
|
}
|
||||||
if l.err != nil {
|
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
|
// MergingIter implementation, but will require a bit of plumbing to thread the
|
||||||
// Equal function.
|
// 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
|
// MergingIter merges spans across levels of the LSM, exposing an iterator over
|
||||||
// spans that yields sets of spans fragmented at unique user key boundaries.
|
// spans that yields sets of spans fragmented at unique user key boundaries.
|
||||||
//
|
//
|
||||||
|
|||||||
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
|
||||||
|
})
|
||||||
|
}
|
||||||
19
vendor/github.com/cockroachdb/pebble/internal/keyspan/truncate.go
generated
vendored
19
vendor/github.com/cockroachdb/pebble/internal/keyspan/truncate.go
generated
vendored
@@ -10,13 +10,18 @@ import "github.com/cockroachdb/pebble/internal/base"
|
|||||||
// truncated to be contained within the range [lower, upper). If start and end
|
// truncated to be contained within the range [lower, upper). If start and end
|
||||||
// are specified, filter out any spans that are completely outside those bounds.
|
// are specified, filter out any spans that are completely outside those bounds.
|
||||||
func Truncate(
|
func Truncate(
|
||||||
cmp base.Compare, iter FragmentIterator, lower, upper []byte, start, end *base.InternalKey,
|
cmp base.Compare,
|
||||||
|
iter FragmentIterator,
|
||||||
|
lower, upper []byte,
|
||||||
|
start, end *base.InternalKey,
|
||||||
|
panicOnUpperTruncate bool,
|
||||||
) FragmentIterator {
|
) FragmentIterator {
|
||||||
return Filter(iter, func(in *Span, out *Span) (keep bool) {
|
return Filter(iter, func(in *Span, out *Span) (keep bool) {
|
||||||
out.Start, out.End = in.Start, in.End
|
out.Start, out.End = in.Start, in.End
|
||||||
out.Keys = append(out.Keys[:0], in.Keys...)
|
out.Keys = append(out.Keys[:0], in.Keys...)
|
||||||
|
|
||||||
// Ignore this span if it lies completely outside [start, end].
|
// Ignore this span if it lies completely outside start, end. Note that
|
||||||
|
// end endInclusive indicated whether end is inclusive.
|
||||||
//
|
//
|
||||||
// The comparison between s.End and start is by user key only, as
|
// The comparison between s.End and start is by user key only, as
|
||||||
// the span is exclusive at s.End, so comparing by user keys
|
// the span is exclusive at s.End, so comparing by user keys
|
||||||
@@ -48,13 +53,21 @@ func Truncate(
|
|||||||
// Wholly within the end bound. Keep it.
|
// Wholly within the end bound. Keep it.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var truncated bool
|
||||||
// Truncate the bounds to lower and upper.
|
// Truncate the bounds to lower and upper.
|
||||||
if cmp(in.Start, lower) < 0 {
|
if cmp(in.Start, lower) < 0 {
|
||||||
out.Start = lower
|
out.Start = lower
|
||||||
}
|
}
|
||||||
if cmp(in.End, upper) > 0 {
|
if cmp(in.End, upper) > 0 {
|
||||||
|
truncated = true
|
||||||
out.End = upper
|
out.End = upper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if panicOnUpperTruncate && truncated {
|
||||||
|
panic("pebble: upper bound should not be truncated")
|
||||||
|
}
|
||||||
|
|
||||||
return !out.Empty() && cmp(out.Start, out.End) < 0
|
return !out.Empty() && cmp(out.Start, out.End) < 0
|
||||||
})
|
}, cmp)
|
||||||
}
|
}
|
||||||
|
|||||||
34
vendor/github.com/cockroachdb/pebble/internal/manifest/btree.go
generated
vendored
34
vendor/github.com/cockroachdb/pebble/internal/manifest/btree.go
generated
vendored
@@ -108,7 +108,7 @@ type annotation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type leafNode struct {
|
type leafNode struct {
|
||||||
ref int32
|
ref atomic.Int32
|
||||||
count int16
|
count int16
|
||||||
leaf bool
|
leaf bool
|
||||||
// subtreeCount holds the count of files in the entire subtree formed by
|
// subtreeCount holds the count of files in the entire subtree formed by
|
||||||
@@ -139,13 +139,13 @@ func leafToNode(ln *leafNode) *node {
|
|||||||
func newLeafNode() *node {
|
func newLeafNode() *node {
|
||||||
n := leafToNode(new(leafNode))
|
n := leafToNode(new(leafNode))
|
||||||
n.leaf = true
|
n.leaf = true
|
||||||
n.ref = 1
|
n.ref.Store(1)
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNode() *node {
|
func newNode() *node {
|
||||||
n := new(node)
|
n := new(node)
|
||||||
n.ref = 1
|
n.ref.Store(1)
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ func newNode() *node {
|
|||||||
// When a node is cloned, the provided pointer will be redirected to the new
|
// When a node is cloned, the provided pointer will be redirected to the new
|
||||||
// mutable node.
|
// mutable node.
|
||||||
func mut(n **node) *node {
|
func mut(n **node) *node {
|
||||||
if atomic.LoadInt32(&(*n).ref) == 1 {
|
if (*n).ref.Load() == 1 {
|
||||||
// Exclusive ownership. Can mutate in place.
|
// Exclusive ownership. Can mutate in place.
|
||||||
|
|
||||||
// Whenever a node will be mutated, reset its annotations to be marked
|
// 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.
|
// incRef acquires a reference to the node.
|
||||||
func (n *node) incRef() {
|
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
|
// 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
|
// new nodes pass contentsToo=false to preserve existing reference counts during
|
||||||
// operations that should yield a net-zero change to descendant refcounts.
|
// operations that should yield a net-zero change to descendant refcounts.
|
||||||
// When a node is released, its contained files are dereferenced.
|
// When a node is released, its contained files are dereferenced.
|
||||||
func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
func (n *node) decRef(contentsToo bool, obsolete *[]*FileBacking) {
|
||||||
if atomic.AddInt32(&n.ref, -1) > 0 {
|
if n.ref.Add(-1) > 0 {
|
||||||
// Other references remain. Can't free.
|
// Other references remain. Can't free.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -207,7 +207,7 @@ func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
|||||||
// nodes, and they want to preserve the existing reference count.
|
// nodes, and they want to preserve the existing reference count.
|
||||||
if contentsToo {
|
if contentsToo {
|
||||||
for _, f := range n.items[:n.count] {
|
for _, f := range n.items[:n.count] {
|
||||||
if atomic.AddInt32(&f.Refs, -1) == 0 {
|
if f.Unref() == 0 {
|
||||||
// There are two sources of node dereferences: tree mutations
|
// There are two sources of node dereferences: tree mutations
|
||||||
// and Version dereferences. Files should only be made obsolete
|
// and Version dereferences. Files should only be made obsolete
|
||||||
// during Version dereferences, during which `obsolete` will be
|
// during Version dereferences, during which `obsolete` will be
|
||||||
@@ -215,7 +215,13 @@ func (n *node) decRef(contentsToo bool, obsolete *[]*FileMetadata) {
|
|||||||
if obsolete == nil {
|
if obsolete == nil {
|
||||||
panic(fmt.Sprintf("file metadata %s dereferenced to zero during tree mutation", f.FileNum))
|
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 {
|
if !n.leaf {
|
||||||
@@ -241,7 +247,7 @@ func (n *node) clone() *node {
|
|||||||
c.subtreeCount = n.subtreeCount
|
c.subtreeCount = n.subtreeCount
|
||||||
// Increase the refcount of each contained item.
|
// Increase the refcount of each contained item.
|
||||||
for _, f := range n.items[:n.count] {
|
for _, f := range n.items[:n.count] {
|
||||||
atomic.AddInt32(&f.Refs, 1)
|
f.Ref()
|
||||||
}
|
}
|
||||||
if !c.leaf {
|
if !c.leaf {
|
||||||
// Copy children and increase each refcount.
|
// Copy children and increase each refcount.
|
||||||
@@ -761,8 +767,8 @@ type btree struct {
|
|||||||
|
|
||||||
// Release dereferences and clears the root node of the btree, removing all
|
// Release dereferences and clears the root node of the btree, removing all
|
||||||
// items from the btree. In doing so, it decrements contained file counts.
|
// items from the btree. In doing so, it decrements contained file counts.
|
||||||
// It returns a slice of newly obsolete files, if any.
|
// It returns a slice of newly obsolete backing files, if any.
|
||||||
func (t *btree) Release() (obsolete []*FileMetadata) {
|
func (t *btree) Release() (obsolete []*FileBacking) {
|
||||||
if t.root != nil {
|
if t.root != nil {
|
||||||
t.root.decRef(true /* contentsToo */, &obsolete)
|
t.root.decRef(true /* contentsToo */, &obsolete)
|
||||||
t.root = nil
|
t.root = nil
|
||||||
@@ -800,7 +806,7 @@ func (t *btree) Delete(item *FileMetadata) (obsolete bool) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if out := mut(&t.root).Remove(t.cmp, item); out != nil {
|
if out := mut(&t.root).Remove(t.cmp, item); out != nil {
|
||||||
obsolete = atomic.AddInt32(&out.Refs, -1) == 0
|
obsolete = out.Unref() == 0
|
||||||
}
|
}
|
||||||
if invariants.Enabled {
|
if invariants.Enabled {
|
||||||
t.root.verifyInvariants()
|
t.root.verifyInvariants()
|
||||||
@@ -832,7 +838,7 @@ func (t *btree) Insert(item *FileMetadata) error {
|
|||||||
newRoot.subtreeCount = t.root.subtreeCount + splitNode.subtreeCount + 1
|
newRoot.subtreeCount = t.root.subtreeCount + splitNode.subtreeCount + 1
|
||||||
t.root = newRoot
|
t.root = newRoot
|
||||||
}
|
}
|
||||||
atomic.AddInt32(&item.Refs, 1)
|
item.Ref()
|
||||||
err := mut(&t.root).Insert(t.cmp, item)
|
err := mut(&t.root).Insert(t.cmp, item)
|
||||||
if invariants.Enabled {
|
if invariants.Enabled {
|
||||||
t.root.verifyInvariants()
|
t.root.verifyInvariants()
|
||||||
|
|||||||
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
|
// LevelMetadata contains metadata for all of the files within
|
||||||
// a level of the LSM.
|
// a level of the LSM.
|
||||||
type LevelMetadata struct {
|
type LevelMetadata struct {
|
||||||
level int
|
level int
|
||||||
tree btree
|
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
|
// clone makes a copy of the level metadata, implicitly increasing the ref
|
||||||
// count of every file contained within lm.
|
// count of every file contained within lm.
|
||||||
func (lm *LevelMetadata) clone() LevelMetadata {
|
func (lm *LevelMetadata) clone() LevelMetadata {
|
||||||
return LevelMetadata{
|
return LevelMetadata{
|
||||||
level: lm.level,
|
level: lm.level,
|
||||||
tree: lm.tree.Clone(),
|
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()
|
return lm.tree.Release()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +48,13 @@ func makeLevelMetadata(cmp Compare, level int, files []*FileMetadata) LevelMetad
|
|||||||
var lm LevelMetadata
|
var lm LevelMetadata
|
||||||
lm.level = level
|
lm.level = level
|
||||||
lm.tree, _ = makeBTree(bcmp, files)
|
lm.tree, _ = makeBTree(bcmp, files)
|
||||||
|
for _, f := range files {
|
||||||
|
lm.totalSize += f.Size
|
||||||
|
if f.Virtual {
|
||||||
|
lm.NumVirtual++
|
||||||
|
lm.VirtualSize += f.Size
|
||||||
|
}
|
||||||
|
}
|
||||||
return lm
|
return lm
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +67,27 @@ func makeBTree(cmp btreeCmp, files []*FileMetadata) (btree, LevelSlice) {
|
|||||||
return t, newLevelSlice(t.Iter())
|
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.
|
// Empty indicates whether there are any files in the level.
|
||||||
func (lm *LevelMetadata) Empty() bool {
|
func (lm *LevelMetadata) Empty() bool {
|
||||||
return lm.tree.Count() == 0
|
return lm.tree.Count() == 0
|
||||||
@@ -62,6 +98,11 @@ func (lm *LevelMetadata) Len() int {
|
|||||||
return lm.tree.Count()
|
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.
|
// Iter constructs a LevelIterator over the entire level.
|
||||||
func (lm *LevelMetadata) Iter() LevelIterator {
|
func (lm *LevelMetadata) Iter() LevelIterator {
|
||||||
return LevelIterator{iter: lm.tree.Iter()}
|
return LevelIterator{iter: lm.tree.Iter()}
|
||||||
@@ -280,6 +321,32 @@ func (ls *LevelSlice) SizeSum() uint64 {
|
|||||||
return sum
|
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
|
// Reslice constructs a new slice backed by the same underlying level, with
|
||||||
// new start and end positions. Reslice invokes the provided function, passing
|
// new start and end positions. Reslice invokes the provided function, passing
|
||||||
// two LevelIterators: one positioned to i's inclusive start and one
|
// two LevelIterators: one positioned to i's inclusive start and one
|
||||||
|
|||||||
464
vendor/github.com/cockroachdb/pebble/internal/manifest/version.go
generated
vendored
464
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.
|
// The number of point and range deletion entries in the table.
|
||||||
NumDeletions uint64
|
NumDeletions uint64
|
||||||
// NumRangeKeySets is the total number of range key sets in the table.
|
// 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
|
NumRangeKeySets uint64
|
||||||
// Estimate of the total disk space that may be dropped by this table's
|
// Estimate of the total disk space that may be dropped by this table's
|
||||||
// point deletions by compacting them.
|
// point deletions by compacting them.
|
||||||
@@ -60,9 +63,11 @@ type TableStats struct {
|
|||||||
// overlapping data in L0 and ignores L0 sublevels, but the error that
|
// overlapping data in L0 and ignores L0 sublevels, but the error that
|
||||||
// introduces is expected to be small.
|
// introduces is expected to be small.
|
||||||
//
|
//
|
||||||
// Tables in the bottommost level of the LSM may have a nonzero estimate
|
// Tables in the bottommost level of the LSM may have a nonzero estimate if
|
||||||
// if snapshots or move compactions prevented the elision of their range
|
// snapshots or move compactions prevented the elision of their range
|
||||||
// tombstones.
|
// tombstones. A table in the bottommost level that was ingested into L6
|
||||||
|
// will have a zero estimate, because the file's sequence numbers indicate
|
||||||
|
// that the tombstone cannot drop any data contained within the file itself.
|
||||||
RangeDeletionsBytesEstimate uint64
|
RangeDeletionsBytesEstimate uint64
|
||||||
// Total size of value blocks and value index block.
|
// Total size of value blocks and value index block.
|
||||||
ValueBlocksSize uint64
|
ValueBlocksSize uint64
|
||||||
@@ -119,43 +124,72 @@ func (s CompactionState) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileMetadata holds the metadata for an on-disk table.
|
// FileMetadata is maintained for leveled-ssts, i.e., they belong to a level of
|
||||||
|
// some version. FileMetadata does not contain the actual level of the sst,
|
||||||
|
// since such leveled-ssts can move across levels in different versions, while
|
||||||
|
// sharing the same FileMetadata. There are two kinds of leveled-ssts, physical
|
||||||
|
// and virtual. Underlying both leveled-ssts is a backing-sst, for which the
|
||||||
|
// only state is FileBacking. A backing-sst is level-less. It is possible for a
|
||||||
|
// backing-sst to be referred to by a physical sst in one version and by one or
|
||||||
|
// more virtual ssts in one or more versions. A backing-sst becomes obsolete
|
||||||
|
// and can be deleted once it is no longer required by any physical or virtual
|
||||||
|
// sst in any version.
|
||||||
|
//
|
||||||
|
// We maintain some invariants:
|
||||||
|
//
|
||||||
|
// 1. Each physical and virtual sst will have a unique FileMetadata.FileNum,
|
||||||
|
// and there will be exactly one FileMetadata associated with the FileNum.
|
||||||
|
//
|
||||||
|
// 2. Within a version, a backing-sst is either only referred to by one
|
||||||
|
// physical sst or one or more virtual ssts.
|
||||||
|
//
|
||||||
|
// 3. Once a backing-sst is referred to by a virtual sst in the latest version,
|
||||||
|
// it cannot go back to being referred to by a physical sst in any future
|
||||||
|
// version.
|
||||||
|
//
|
||||||
|
// Once a physical sst is no longer needed by any version, we will no longer
|
||||||
|
// maintain the file metadata associated with it. We will still maintain the
|
||||||
|
// FileBacking associated with the physical sst if the backing sst is required
|
||||||
|
// by any virtual ssts in any version.
|
||||||
type FileMetadata struct {
|
type FileMetadata struct {
|
||||||
// Atomic contains fields which are accessed atomically. Go allocations
|
// AllowedSeeks is used to determine if a file should be picked for
|
||||||
// are guaranteed to be 64-bit aligned which we take advantage of by
|
// a read triggered compaction. It is decremented when read sampling
|
||||||
// placing the 64-bit fields which we access atomically at the beginning
|
// in pebble.Iterator after every after every positioning operation
|
||||||
// of the FileMetadata struct. For more information, see
|
// that returns a user key (eg. Next, Prev, SeekGE, SeekLT, etc).
|
||||||
// https://golang.org/pkg/sync/atomic/#pkg-note-BUG.
|
AllowedSeeks atomic.Int64
|
||||||
Atomic struct {
|
|
||||||
// AllowedSeeks is used to determine if a file should be picked for
|
|
||||||
// a read triggered compaction. It is decremented when read sampling
|
|
||||||
// in pebble.Iterator after every after every positioning operation
|
|
||||||
// that returns a user key (eg. Next, Prev, SeekGE, SeekLT, etc).
|
|
||||||
AllowedSeeks int64
|
|
||||||
|
|
||||||
// statsValid is 1 if stats have been loaded for the table. The
|
// statsValid indicates if stats have been loaded for the table. The
|
||||||
// TableStats structure is populated only if valid is 1.
|
// TableStats structure is populated only if valid is true.
|
||||||
statsValid uint32
|
statsValid atomic.Bool
|
||||||
}
|
|
||||||
|
// FileBacking is the state which backs either a physical or virtual
|
||||||
|
// sstables.
|
||||||
|
FileBacking *FileBacking
|
||||||
|
|
||||||
// InitAllowedSeeks is the inital value of allowed seeks. This is used
|
// InitAllowedSeeks is the inital value of allowed seeks. This is used
|
||||||
// to re-set allowed seeks on a file once it hits 0.
|
// to re-set allowed seeks on a file once it hits 0.
|
||||||
InitAllowedSeeks int64
|
InitAllowedSeeks int64
|
||||||
|
|
||||||
// Reference count for the file: incremented when a file is added to a
|
|
||||||
// version and decremented when the version is unreferenced. The file is
|
|
||||||
// obsolete when the reference count falls to zero.
|
|
||||||
Refs int32
|
|
||||||
// FileNum is the file number.
|
// FileNum is the file number.
|
||||||
|
//
|
||||||
|
// INVARIANT: when !FileMetadata.Virtual, FileNum == FileBacking.DiskFileNum.
|
||||||
FileNum base.FileNum
|
FileNum base.FileNum
|
||||||
// Size is the size of the file, in bytes.
|
// Size is the size of the file, in bytes. Size is an approximate value for
|
||||||
|
// virtual sstables.
|
||||||
|
//
|
||||||
|
// INVARIANTS:
|
||||||
|
// - When !FileMetadata.Virtual, Size == FileBacking.Size.
|
||||||
|
// - Size should be non-zero. Size 0 virtual sstables must not be created.
|
||||||
Size uint64
|
Size uint64
|
||||||
// File creation time in seconds since the epoch (1970-01-01 00:00:00
|
// 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
|
// UTC). For ingested sstables, this corresponds to the time the file was
|
||||||
// ingested.
|
// ingested. For virtual sstables, this corresponds to the wall clock time
|
||||||
|
// when the FileMetadata for the virtual sstable was first created.
|
||||||
CreationTime int64
|
CreationTime int64
|
||||||
// Smallest and largest sequence numbers in the table, across both point and
|
// Lower and upper bounds for the smallest and largest sequence numbers in
|
||||||
// range keys.
|
// the table, across both point and range keys. For physical sstables, these
|
||||||
|
// values are tight bounds. For virtual sstables, there is no guarantee that
|
||||||
|
// there will be keys with SmallestSeqNum or LargestSeqNum within virtual
|
||||||
|
// sstable bounds.
|
||||||
SmallestSeqNum uint64
|
SmallestSeqNum uint64
|
||||||
LargestSeqNum uint64
|
LargestSeqNum uint64
|
||||||
// SmallestPointKey and LargestPointKey are the inclusive bounds for the
|
// SmallestPointKey and LargestPointKey are the inclusive bounds for the
|
||||||
@@ -178,8 +212,17 @@ type FileMetadata struct {
|
|||||||
Smallest InternalKey
|
Smallest InternalKey
|
||||||
Largest InternalKey
|
Largest InternalKey
|
||||||
// Stats describe table statistics. Protected by DB.mu.
|
// Stats describe table statistics. Protected by DB.mu.
|
||||||
|
//
|
||||||
|
// For virtual sstables, set stats upon virtual sstable creation as
|
||||||
|
// asynchronous computation of stats is not currently supported.
|
||||||
|
//
|
||||||
|
// TODO(bananabrick): To support manifest replay for virtual sstables, we
|
||||||
|
// probably need to compute virtual sstable stats asynchronously. Otherwise,
|
||||||
|
// we'd have to write virtual sstable stats to the version edit.
|
||||||
Stats TableStats
|
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
|
SubLevel int
|
||||||
L0Index int
|
L0Index int
|
||||||
minIntervalIndex int
|
minIntervalIndex int
|
||||||
@@ -188,9 +231,6 @@ type FileMetadata struct {
|
|||||||
// NB: the alignment of this struct is 8 bytes. We pack all the bools to
|
// NB: the alignment of this struct is 8 bytes. We pack all the bools to
|
||||||
// ensure an optimal packing.
|
// 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
|
// 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
|
// compaction. When it's true, IsCompacting must also return true. If
|
||||||
// Compacting is true and IsIntraL0Compacting is false for an L0 file, the
|
// Compacting is true and IsIntraL0Compacting is false for an L0 file, the
|
||||||
@@ -226,6 +266,212 @@ type FileMetadata struct {
|
|||||||
// key type (point or range) corresponds to the smallest and largest overall
|
// key type (point or range) corresponds to the smallest and largest overall
|
||||||
// table bounds.
|
// table bounds.
|
||||||
boundTypeSmallest, boundTypeLargest boundType
|
boundTypeSmallest, boundTypeLargest boundType
|
||||||
|
// Virtual is true if the FileMetadata belongs to a virtual sstable.
|
||||||
|
Virtual bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhysicalFileMeta is used by functions which want a guarantee that their input
|
||||||
|
// belongs to a physical sst and not a virtual sst.
|
||||||
|
//
|
||||||
|
// NB: This type should only be constructed by calling
|
||||||
|
// FileMetadata.PhysicalMeta.
|
||||||
|
type PhysicalFileMeta struct {
|
||||||
|
*FileMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// PhysicalMeta should be the only source of creating the PhysicalFileMeta
|
||||||
|
// wrapper type.
|
||||||
|
func (m *FileMetadata) PhysicalMeta() PhysicalFileMeta {
|
||||||
|
if m.Virtual {
|
||||||
|
panic("pebble: file metadata does not belong to a physical sstable")
|
||||||
|
}
|
||||||
|
return PhysicalFileMeta{
|
||||||
|
m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VirtualMeta should be the only source of creating the VirtualFileMeta wrapper
|
||||||
|
// type.
|
||||||
|
func (m *FileMetadata) VirtualMeta() VirtualFileMeta {
|
||||||
|
if !m.Virtual {
|
||||||
|
panic("pebble: file metadata does not belong to a virtual sstable")
|
||||||
|
}
|
||||||
|
return VirtualFileMeta{
|
||||||
|
m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileBacking either backs a single physical sstable, or one or more virtual
|
||||||
|
// sstables.
|
||||||
|
//
|
||||||
|
// See the comment above the FileMetadata type for sstable terminology.
|
||||||
|
type FileBacking struct {
|
||||||
|
// Reference count for the backing file on disk: incremented when a
|
||||||
|
// physical or virtual sstable which is backed by the FileBacking is
|
||||||
|
// added to a version and decremented when the version is unreferenced.
|
||||||
|
// We ref count in order to determine when it is safe to delete a
|
||||||
|
// backing sst file from disk. The backing file is obsolete when the
|
||||||
|
// reference count falls to zero.
|
||||||
|
refs atomic.Int32
|
||||||
|
// latestVersionRefs are the references to the FileBacking in the
|
||||||
|
// latest version. This reference can be through a single physical
|
||||||
|
// sstable in the latest version, or one or more virtual sstables in the
|
||||||
|
// latest version.
|
||||||
|
//
|
||||||
|
// INVARIANT: latestVersionRefs <= refs.
|
||||||
|
latestVersionRefs atomic.Int32
|
||||||
|
// VirtualizedSize is set iff the backing sst is only referred to by
|
||||||
|
// virtual ssts in the latest version. VirtualizedSize is the sum of the
|
||||||
|
// virtual sstable sizes of all of the virtual sstables in the latest
|
||||||
|
// version which are backed by the physical sstable. When a virtual
|
||||||
|
// sstable is removed from the latest version, we will decrement the
|
||||||
|
// VirtualizedSize. During compaction picking, we'll compensate a
|
||||||
|
// virtual sstable file size by
|
||||||
|
// (FileBacking.Size - FileBacking.VirtualizedSize) / latestVersionRefs.
|
||||||
|
// The intuition is that if FileBacking.Size - FileBacking.VirtualizedSize
|
||||||
|
// is high, then the space amplification due to virtual sstables is
|
||||||
|
// high, and we should pick the virtual sstable with a higher priority.
|
||||||
|
//
|
||||||
|
// TODO(bananabrick): Compensate the virtual sstable file size using
|
||||||
|
// the VirtualizedSize during compaction picking and test.
|
||||||
|
VirtualizedSize atomic.Uint64
|
||||||
|
DiskFileNum base.DiskFileNum
|
||||||
|
Size uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitPhysicalBacking allocates and sets the FileBacking which is required by a
|
||||||
|
// physical sstable FileMetadata.
|
||||||
|
//
|
||||||
|
// Ensure that the state required by FileBacking, such as the FileNum, is
|
||||||
|
// already set on the FileMetadata before InitPhysicalBacking is called.
|
||||||
|
// Calling InitPhysicalBacking only after the relevant state has been set in the
|
||||||
|
// FileMetadata is not necessary in tests which don't rely on FileBacking.
|
||||||
|
func (m *FileMetadata) InitPhysicalBacking() {
|
||||||
|
if m.Virtual {
|
||||||
|
panic("pebble: virtual sstables should use a pre-existing FileBacking")
|
||||||
|
}
|
||||||
|
if m.FileBacking == nil {
|
||||||
|
m.FileBacking = &FileBacking{Size: m.Size, DiskFileNum: m.FileNum.DiskFileNum()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
if !m.Virtual {
|
||||||
|
panic("pebble: invalid virtual sstable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdFrom.SmallestSeqNum != m.SmallestSeqNum {
|
||||||
|
panic("pebble: invalid smallest sequence number for virtual sstable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdFrom.LargestSeqNum != m.LargestSeqNum {
|
||||||
|
panic("pebble: invalid largest sequence number for virtual sstable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if createdFrom.FileBacking != nil && createdFrom.FileBacking != m.FileBacking {
|
||||||
|
panic("pebble: invalid physical sstable state for virtual sstable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Size == 0 {
|
||||||
|
panic("pebble: virtual sstable size must be set upon creation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refs returns the refcount of backing sstable.
|
||||||
|
func (m *FileMetadata) Refs() int32 {
|
||||||
|
return m.FileBacking.refs.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref increments the ref count associated with the backing sstable.
|
||||||
|
func (m *FileMetadata) Ref() {
|
||||||
|
m.FileBacking.refs.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unref decrements the ref count associated with the backing sstable.
|
||||||
|
func (m *FileMetadata) Unref() int32 {
|
||||||
|
v := m.FileBacking.refs.Add(-1)
|
||||||
|
if invariants.Enabled && v < 0 {
|
||||||
|
panic("pebble: invalid FileMetadata refcounting")
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestRef increments the latest ref count associated with the backing
|
||||||
|
// sstable.
|
||||||
|
func (m *FileMetadata) LatestRef() {
|
||||||
|
m.FileBacking.latestVersionRefs.Add(1)
|
||||||
|
|
||||||
|
if m.Virtual {
|
||||||
|
m.FileBacking.VirtualizedSize.Add(m.Size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestUnref decrements the latest ref count associated with the backing
|
||||||
|
// sstable.
|
||||||
|
func (m *FileMetadata) LatestUnref() int32 {
|
||||||
|
if m.Virtual {
|
||||||
|
m.FileBacking.VirtualizedSize.Add(-m.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := m.FileBacking.latestVersionRefs.Add(-1)
|
||||||
|
if invariants.Enabled && v < 0 {
|
||||||
|
panic("pebble: invalid FileMetadata latest refcounting")
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestRefs returns the latest ref count associated with the backing sstable.
|
||||||
|
func (m *FileMetadata) LatestRefs() int32 {
|
||||||
|
return m.FileBacking.latestVersionRefs.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCompactionState transitions this file's compaction state to the given
|
// SetCompactionState transitions this file's compaction state to the given
|
||||||
@@ -263,22 +509,14 @@ func (m *FileMetadata) IsCompacting() bool {
|
|||||||
// returns true, the Stats field may be read (with or without holding the
|
// returns true, the Stats field may be read (with or without holding the
|
||||||
// database mutex).
|
// database mutex).
|
||||||
func (m *FileMetadata) StatsValid() bool {
|
func (m *FileMetadata) StatsValid() bool {
|
||||||
return atomic.LoadUint32(&m.Atomic.statsValid) == 1
|
return m.statsValid.Load()
|
||||||
}
|
|
||||||
|
|
||||||
// StatsValidLocked returns true if the table stats have been populated.
|
|
||||||
// StatsValidLocked requires DB.mu is held when it's invoked, and it avoids the
|
|
||||||
// overhead of an atomic load. This is possible because table stats validity is
|
|
||||||
// only set while DB.mu is held.
|
|
||||||
func (m *FileMetadata) StatsValidLocked() bool {
|
|
||||||
return m.Atomic.statsValid == 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatsMarkValid marks the TableStats as valid. The caller must hold DB.mu
|
// StatsMarkValid marks the TableStats as valid. The caller must hold DB.mu
|
||||||
// while populating TableStats and calling StatsMarkValud. Once stats are
|
// while populating TableStats and calling StatsMarkValud. Once stats are
|
||||||
// populated, they must not be mutated.
|
// populated, they must not be mutated.
|
||||||
func (m *FileMetadata) StatsMarkValid() {
|
func (m *FileMetadata) StatsMarkValid() {
|
||||||
atomic.StoreUint32(&m.Atomic.statsValid, 1)
|
m.statsValid.Store(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtendPointKeyBounds attempts to extend the lower and upper point key bounds
|
// ExtendPointKeyBounds attempts to extend the lower and upper point key bounds
|
||||||
@@ -369,6 +607,13 @@ func (m *FileMetadata) Overlaps(cmp Compare, start []byte, end []byte, exclusive
|
|||||||
return true
|
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
|
// ContainsKeyType returns whether or not the file contains keys of the provided
|
||||||
// type.
|
// type.
|
||||||
func (m *FileMetadata) ContainsKeyType(kt KeyType) bool {
|
func (m *FileMetadata) ContainsKeyType(kt KeyType) bool {
|
||||||
@@ -466,6 +711,7 @@ func (m *FileMetadata) DebugString(format base.FormatKey, verbose bool) string {
|
|||||||
if !verbose {
|
if !verbose {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(&b, " seqnums:[%d-%d]", m.SmallestSeqNum, m.LargestSeqNum)
|
||||||
if m.HasPointKeys {
|
if m.HasPointKeys {
|
||||||
fmt.Fprintf(&b, " points:[%s-%s]",
|
fmt.Fprintf(&b, " points:[%s-%s]",
|
||||||
m.SmallestPointKey.Pretty(format), m.LargestPointKey.Pretty(format))
|
m.SmallestPointKey.Pretty(format), m.LargestPointKey.Pretty(format))
|
||||||
@@ -479,9 +725,9 @@ func (m *FileMetadata) DebugString(format base.FormatKey, verbose bool) string {
|
|||||||
|
|
||||||
// ParseFileMetadataDebug parses a FileMetadata from its DebugString
|
// ParseFileMetadataDebug parses a FileMetadata from its DebugString
|
||||||
// representation.
|
// representation.
|
||||||
func ParseFileMetadataDebug(s string) (m FileMetadata, err error) {
|
func ParseFileMetadataDebug(s string) (*FileMetadata, error) {
|
||||||
// Split lines of the form:
|
// 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 {
|
fields := strings.FieldsFunc(s, func(c rune) bool {
|
||||||
switch c {
|
switch c {
|
||||||
case ':', '[', '-', ']':
|
case ':', '[', '-', ']':
|
||||||
@@ -491,10 +737,24 @@ func ParseFileMetadataDebug(s string) (m FileMetadata, err error) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
if len(fields)%3 != 0 {
|
if len(fields)%3 != 0 {
|
||||||
return m, errors.Newf("malformed input: %s", s)
|
return nil, errors.Newf("malformed input: %s", s)
|
||||||
}
|
}
|
||||||
|
m := &FileMetadata{}
|
||||||
for len(fields) > 0 {
|
for len(fields) > 0 {
|
||||||
prefix := 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])
|
smallest := base.ParsePrettyInternalKey(fields[1])
|
||||||
largest := base.ParsePrettyInternalKey(fields[2])
|
largest := base.ParsePrettyInternalKey(fields[2])
|
||||||
switch prefix {
|
switch prefix {
|
||||||
@@ -522,7 +782,8 @@ func ParseFileMetadataDebug(s string) (m FileMetadata, err error) {
|
|||||||
m.SmallestPointKey, m.LargestPointKey = m.Smallest, m.Largest
|
m.SmallestPointKey, m.LargestPointKey = m.Smallest, m.Largest
|
||||||
m.HasPointKeys = true
|
m.HasPointKeys = true
|
||||||
}
|
}
|
||||||
return
|
m.InitPhysicalBacking()
|
||||||
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the metadata for consistency with itself, returning an
|
// Validate validates the metadata for consistency with itself, returning an
|
||||||
@@ -584,6 +845,11 @@ func (m *FileMetadata) Validate(cmp Compare, formatKey base.FormatKey) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that FileMetadata.Init was called.
|
||||||
|
if m.FileBacking == nil {
|
||||||
|
return base.CorruptionErrorf("file metadata FileBacking not set")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,6 +1022,9 @@ func NewVersion(
|
|||||||
} else {
|
} else {
|
||||||
v.Levels[l].tree.cmp = btreeCmpSmallestKey(cmp)
|
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 {
|
if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -788,7 +1057,7 @@ func NewVersion(
|
|||||||
// key in a higher level table that has both the same user key and a higher
|
// key in a higher level table that has both the same user key and a higher
|
||||||
// sequence number.
|
// sequence number.
|
||||||
type Version struct {
|
type Version struct {
|
||||||
refs int32
|
refs atomic.Int32
|
||||||
|
|
||||||
// The level 0 sstables are organized in a series of sublevels. Similar to
|
// 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
|
// the seqnum invariant in normal levels, there is no internal key in a
|
||||||
@@ -805,7 +1074,7 @@ type Version struct {
|
|||||||
// usage.
|
// usage.
|
||||||
//
|
//
|
||||||
// L0Sublevels.Levels contains L0 files ordered by sublevels. All the files
|
// L0Sublevels.Levels contains L0 files ordered by sublevels. All the files
|
||||||
// in Files[0] are in L0Sublevels.Levels. L0SublevelFiles is also set to
|
// in Levels[0] are in L0Sublevels.Levels. L0SublevelFiles is also set to
|
||||||
// a reference to that slice, as that slice is necessary for iterator
|
// a reference to that slice, as that slice is necessary for iterator
|
||||||
// creation and needs to outlast L0Sublevels.
|
// creation and needs to outlast L0Sublevels.
|
||||||
L0Sublevels *L0Sublevels
|
L0Sublevels *L0Sublevels
|
||||||
@@ -820,7 +1089,7 @@ type Version struct {
|
|||||||
|
|
||||||
// The callback to invoke when the last reference to a version is
|
// The callback to invoke when the last reference to a version is
|
||||||
// removed. Will be called with list.mu held.
|
// removed. Will be called with list.mu held.
|
||||||
Deleted func(obsolete []*FileMetadata)
|
Deleted func(obsolete []*FileBacking)
|
||||||
|
|
||||||
// Stats holds aggregated stats about the version maintained from
|
// Stats holds aggregated stats about the version maintained from
|
||||||
// version to version.
|
// version to version.
|
||||||
@@ -849,15 +1118,21 @@ func (v *Version) DebugString(format base.FormatKey) string {
|
|||||||
return v.string(format, true)
|
return v.string(format, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func describeSublevels(format base.FormatKey, verbose bool, sublevels []LevelSlice) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for sublevel := len(sublevels) - 1; sublevel >= 0; sublevel-- {
|
||||||
|
fmt.Fprintf(&buf, "0.%d:\n", sublevel)
|
||||||
|
sublevels[sublevel].Each(func(f *FileMetadata) {
|
||||||
|
fmt.Fprintf(&buf, " %s\n", f.DebugString(format, verbose))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Version) string(format base.FormatKey, verbose bool) string {
|
func (v *Version) string(format base.FormatKey, verbose bool) string {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if len(v.L0SublevelFiles) > 0 {
|
if len(v.L0SublevelFiles) > 0 {
|
||||||
for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- {
|
fmt.Fprintf(&buf, "%s", describeSublevels(format, verbose, v.L0SublevelFiles))
|
||||||
fmt.Fprintf(&buf, "0.%d:\n", sublevel)
|
|
||||||
v.L0SublevelFiles[sublevel].Each(func(f *FileMetadata) {
|
|
||||||
fmt.Fprintf(&buf, " %s\n", f.DebugString(format, verbose))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for level := 1; level < NumLevels; level++ {
|
for level := 1; level < NumLevels; level++ {
|
||||||
if v.Levels[level].Empty() {
|
if v.Levels[level].Empty() {
|
||||||
@@ -898,7 +1173,7 @@ func ParseVersionDebug(
|
|||||||
m.SmallestPointKey, m.LargestPointKey = m.Smallest, m.Largest
|
m.SmallestPointKey, m.LargestPointKey = m.Smallest, m.Largest
|
||||||
m.HasPointKeys = true
|
m.HasPointKeys = true
|
||||||
}
|
}
|
||||||
files[level] = append(files[level], &m)
|
files[level] = append(files[level], m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Reverse the order of L0 files. This ensures we construct the same
|
// Reverse the order of L0 files. This ensures we construct the same
|
||||||
@@ -912,12 +1187,12 @@ func ParseVersionDebug(
|
|||||||
|
|
||||||
// Refs returns the number of references to the version.
|
// Refs returns the number of references to the version.
|
||||||
func (v *Version) Refs() int32 {
|
func (v *Version) Refs() int32 {
|
||||||
return atomic.LoadInt32(&v.refs)
|
return v.refs.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ref increments the version refcount.
|
// Ref increments the version refcount.
|
||||||
func (v *Version) Ref() {
|
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
|
// Unref decrements the version refcount. If the last reference to the version
|
||||||
@@ -925,12 +1200,11 @@ func (v *Version) Ref() {
|
|||||||
// Deleted callback is invoked. Requires that the VersionList mutex is NOT
|
// Deleted callback is invoked. Requires that the VersionList mutex is NOT
|
||||||
// locked.
|
// locked.
|
||||||
func (v *Version) Unref() {
|
func (v *Version) Unref() {
|
||||||
if atomic.AddInt32(&v.refs, -1) == 0 {
|
if v.refs.Add(-1) == 0 {
|
||||||
obsolete := v.unrefFiles()
|
|
||||||
l := v.list
|
l := v.list
|
||||||
l.mu.Lock()
|
l.mu.Lock()
|
||||||
l.Remove(v)
|
l.Remove(v)
|
||||||
v.Deleted(obsolete)
|
v.Deleted(v.unrefFiles())
|
||||||
l.mu.Unlock()
|
l.mu.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -940,14 +1214,14 @@ func (v *Version) Unref() {
|
|||||||
// the Deleted callback is invoked. Requires that the VersionList mutex is
|
// the Deleted callback is invoked. Requires that the VersionList mutex is
|
||||||
// already locked.
|
// already locked.
|
||||||
func (v *Version) UnrefLocked() {
|
func (v *Version) UnrefLocked() {
|
||||||
if atomic.AddInt32(&v.refs, -1) == 0 {
|
if v.refs.Add(-1) == 0 {
|
||||||
v.list.Remove(v)
|
v.list.Remove(v)
|
||||||
v.Deleted(v.unrefFiles())
|
v.Deleted(v.unrefFiles())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Version) unrefFiles() []*FileMetadata {
|
func (v *Version) unrefFiles() []*FileBacking {
|
||||||
var obsolete []*FileMetadata
|
var obsolete []*FileBacking
|
||||||
for _, lm := range v.Levels {
|
for _, lm := range v.Levels {
|
||||||
obsolete = append(obsolete, lm.release()...)
|
obsolete = append(obsolete, lm.release()...)
|
||||||
}
|
}
|
||||||
@@ -1079,16 +1353,20 @@ func (v *Version) Overlaps(
|
|||||||
// CheckOrdering checks that the files are consistent with respect to
|
// CheckOrdering checks that the files are consistent with respect to
|
||||||
// increasing file numbers (for level 0 files) and increasing and non-
|
// increasing file numbers (for level 0 files) and increasing and non-
|
||||||
// overlapping internal key ranges (for level non-0 files).
|
// 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-- {
|
for sublevel := len(v.L0SublevelFiles) - 1; sublevel >= 0; sublevel-- {
|
||||||
sublevelIter := v.L0SublevelFiles[sublevel].Iter()
|
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))
|
return base.CorruptionErrorf("%s\n%s", err, v.DebugString(format))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for level, lm := range v.Levels {
|
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))
|
return base.CorruptionErrorf("%s\n%s", err, v.DebugString(format))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1157,10 +1435,34 @@ func (l *VersionList) Remove(v *Version) {
|
|||||||
v.list = nil // avoid memory leaks
|
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
|
// CheckOrdering checks that the files are consistent with respect to
|
||||||
// seqnums (for level 0 files -- see detailed comment below) and increasing and non-
|
// seqnums (for level 0 files -- see detailed comment below) and increasing and non-
|
||||||
// overlapping internal key ranges (for non-level 0 files).
|
// 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
|
// 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
|
// 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
|
// sublevels, or if all L0 files are being passed in, we do the legacy L0
|
||||||
@@ -1238,11 +1540,29 @@ func CheckOrdering(cmp Compare, format base.FormatKey, level Level, files LevelI
|
|||||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
||||||
f.Smallest.Pretty(format), f.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]",
|
// What's considered "overlapping" is dependent on the format
|
||||||
errors.Safe(level), errors.Safe(prev.FileNum), errors.Safe(f.FileNum),
|
// major version. If ordering=ProhibitSplitUserKeys, then both
|
||||||
prev.Smallest.Pretty(format), prev.Largest.Pretty(format),
|
// files cannot contain keys with the same user keys. If the
|
||||||
f.Smallest.Pretty(format), f.Largest.Pretty(format))
|
// 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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
403
vendor/github.com/cockroachdb/pebble/internal/manifest/version_edit.go
generated
vendored
403
vendor/github.com/cockroachdb/pebble/internal/manifest/version_edit.go
generated
vendored
@@ -11,11 +11,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
|
"github.com/cockroachdb/pebble/internal/invariants"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(peter): describe the MANIFEST file format, independently of the C++
|
// TODO(peter): describe the MANIFEST file format, independently of the C++
|
||||||
@@ -51,7 +51,9 @@ const (
|
|||||||
tagMaxColumnFamily = 203
|
tagMaxColumnFamily = 203
|
||||||
|
|
||||||
// Pebble tags.
|
// Pebble tags.
|
||||||
tagNewFile5 = 104 // Range keys.
|
tagNewFile5 = 104 // Range keys.
|
||||||
|
tagCreatedBackingTable = 105
|
||||||
|
tagRemovedBackingTable = 106
|
||||||
|
|
||||||
// The custom tags sub-format used by tagNewFile4 and above.
|
// The custom tags sub-format used by tagNewFile4 and above.
|
||||||
customTagTerminate = 1
|
customTagTerminate = 1
|
||||||
@@ -59,6 +61,7 @@ const (
|
|||||||
customTagCreationTime = 6
|
customTagCreationTime = 6
|
||||||
customTagPathID = 65
|
customTagPathID = 65
|
||||||
customTagNonSafeIgnoreMask = 1 << 6
|
customTagNonSafeIgnoreMask = 1 << 6
|
||||||
|
customTagVirtual = 66
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeletedFileEntry holds the state for a file deletion from a level. The file
|
// DeletedFileEntry holds the state for a file deletion from a level. The file
|
||||||
@@ -73,6 +76,9 @@ type DeletedFileEntry struct {
|
|||||||
type NewFileEntry struct {
|
type NewFileEntry struct {
|
||||||
Level int
|
Level int
|
||||||
Meta *FileMetadata
|
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
|
// VersionEdit holds the state for an edit to a Version along with other
|
||||||
@@ -110,9 +116,42 @@ type VersionEdit struct {
|
|||||||
// found that there was no overlapping file at the higher level).
|
// found that there was no overlapping file at the higher level).
|
||||||
DeletedFiles map[DeletedFileEntry]*FileMetadata
|
DeletedFiles map[DeletedFileEntry]*FileMetadata
|
||||||
NewFiles []NewFileEntry
|
NewFiles []NewFileEntry
|
||||||
|
// CreatedBackingTables can be used to preserve the FileBacking associated
|
||||||
|
// with a physical sstable. This is useful when virtual sstables in the
|
||||||
|
// latest version are reconstructed during manifest replay, and we also need
|
||||||
|
// to reconstruct the FileBacking which is required by these virtual
|
||||||
|
// sstables.
|
||||||
|
//
|
||||||
|
// INVARIANT: The FileBacking associated with a physical sstable must only
|
||||||
|
// be added as a backing file in the same version edit where the physical
|
||||||
|
// sstable is first virtualized. This means that the physical sstable must
|
||||||
|
// be present in DeletedFiles and that there must be at least one virtual
|
||||||
|
// sstable with the same FileBacking as the physical sstable in NewFiles. A
|
||||||
|
// file must be present in CreatedBackingTables in exactly one version edit.
|
||||||
|
// The physical sstable associated with the FileBacking must also not be
|
||||||
|
// present in NewFiles.
|
||||||
|
CreatedBackingTables []*FileBacking
|
||||||
|
// RemovedBackingTables is used to remove the FileBacking associated with a
|
||||||
|
// virtual sstable. Note that a backing sstable can be removed as soon as
|
||||||
|
// there are no virtual sstables in the latest version which are using the
|
||||||
|
// backing sstable, but the backing sstable doesn't necessarily have to be
|
||||||
|
// removed atomically with the version edit which removes the last virtual
|
||||||
|
// sstable associated with the backing sstable. The removal can happen in a
|
||||||
|
// future version edit.
|
||||||
|
//
|
||||||
|
// INVARIANT: A file must only be added to RemovedBackingTables if it was
|
||||||
|
// added to CreateBackingTables in a prior version edit. The same version
|
||||||
|
// edit also cannot have the same file present in both CreateBackingTables
|
||||||
|
// and RemovedBackingTables. A file must be present in RemovedBackingTables
|
||||||
|
// in exactly one version edit.
|
||||||
|
RemovedBackingTables []base.DiskFileNum
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode decodes an edit from the specified reader.
|
// Decode decodes an edit from the specified reader.
|
||||||
|
//
|
||||||
|
// 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 {
|
func (v *VersionEdit) Decode(r io.Reader) error {
|
||||||
br, ok := r.(byteReader)
|
br, ok := r.(byteReader)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -165,6 +204,28 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
// NB: RocksDB does not use compaction pointers anymore.
|
// 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:
|
case tagDeletedFile:
|
||||||
level, err := d.readLevel()
|
level, err := d.readLevel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -270,6 +331,10 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
var markedForCompaction bool
|
var markedForCompaction bool
|
||||||
var creationTime uint64
|
var creationTime uint64
|
||||||
|
virtualState := struct {
|
||||||
|
virtual bool
|
||||||
|
backingFileNum uint64
|
||||||
|
}{}
|
||||||
if tag == tagNewFile4 || tag == tagNewFile5 {
|
if tag == tagNewFile4 || tag == tagNewFile5 {
|
||||||
for {
|
for {
|
||||||
customTag, err := d.readUvarint()
|
customTag, err := d.readUvarint()
|
||||||
@@ -278,7 +343,16 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
if customTag == customTagTerminate {
|
if customTag == customTagTerminate {
|
||||||
break
|
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()
|
field, err := d.readBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -314,6 +388,7 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
|||||||
SmallestSeqNum: smallestSeqNum,
|
SmallestSeqNum: smallestSeqNum,
|
||||||
LargestSeqNum: largestSeqNum,
|
LargestSeqNum: largestSeqNum,
|
||||||
MarkedForCompaction: markedForCompaction,
|
MarkedForCompaction: markedForCompaction,
|
||||||
|
Virtual: virtualState.virtual,
|
||||||
}
|
}
|
||||||
if tag != tagNewFile5 { // no range keys present
|
if tag != tagNewFile5 { // no range keys present
|
||||||
m.SmallestPointKey = base.DecodeInternalKey(smallestPointKey)
|
m.SmallestPointKey = base.DecodeInternalKey(smallestPointKey)
|
||||||
@@ -345,10 +420,18 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.boundsSet = true
|
m.boundsSet = true
|
||||||
v.NewFiles = append(v.NewFiles, NewFileEntry{
|
if !virtualState.virtual {
|
||||||
|
m.InitPhysicalBacking()
|
||||||
|
}
|
||||||
|
|
||||||
|
nfe := NewFileEntry{
|
||||||
Level: level,
|
Level: level,
|
||||||
Meta: m,
|
Meta: m,
|
||||||
})
|
}
|
||||||
|
if virtualState.virtual {
|
||||||
|
nfe.BackingFileNum = base.FileNum(virtualState.backingFileNum).DiskFileNum()
|
||||||
|
}
|
||||||
|
v.NewFiles = append(v.NewFiles, nfe)
|
||||||
|
|
||||||
case tagPrevLogNumber:
|
case tagPrevLogNumber:
|
||||||
n, err := d.readUvarint()
|
n, err := d.readUvarint()
|
||||||
@@ -367,8 +450,7 @@ func (v *VersionEdit) Decode(r io.Reader) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements fmt.Stringer for a VersionEdit.
|
func (v *VersionEdit) string(verbose bool, fmtKey base.FormatKey) string {
|
||||||
func (v *VersionEdit) String() string {
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if v.ComparerName != "" {
|
if v.ComparerName != "" {
|
||||||
fmt.Fprintf(&buf, " comparer: %s", v.ComparerName)
|
fmt.Fprintf(&buf, " comparer: %s", v.ComparerName)
|
||||||
@@ -399,7 +481,12 @@ func (v *VersionEdit) String() string {
|
|||||||
fmt.Fprintf(&buf, " deleted: L%d %s\n", df.Level, df.FileNum)
|
fmt.Fprintf(&buf, " deleted: L%d %s\n", df.Level, df.FileNum)
|
||||||
}
|
}
|
||||||
for _, nf := range v.NewFiles {
|
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 {
|
if nf.Meta.CreationTime != 0 {
|
||||||
fmt.Fprintf(&buf, " (%s)",
|
fmt.Fprintf(&buf, " (%s)",
|
||||||
time.Unix(nf.Meta.CreationTime, 0).UTC().Format(time.RFC3339))
|
time.Unix(nf.Meta.CreationTime, 0).UTC().Format(time.RFC3339))
|
||||||
@@ -409,6 +496,16 @@ func (v *VersionEdit) String() string {
|
|||||||
return buf.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.
|
// Encode encodes an edit to the specified writer.
|
||||||
func (v *VersionEdit) Encode(w io.Writer) error {
|
func (v *VersionEdit) Encode(w io.Writer) error {
|
||||||
e := versionEditEncoder{new(bytes.Buffer)}
|
e := versionEditEncoder{new(bytes.Buffer)}
|
||||||
@@ -429,6 +526,15 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
|||||||
e.writeUvarint(tagNextFileNumber)
|
e.writeUvarint(tagNextFileNumber)
|
||||||
e.writeUvarint(uint64(v.NextFileNum))
|
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,
|
// RocksDB requires LastSeqNum to be encoded for the first MANIFEST entry,
|
||||||
// even though its value is zero. We detect this by encoding LastSeqNum when
|
// even though its value is zero. We detect this by encoding LastSeqNum when
|
||||||
// ComparerName is set.
|
// ComparerName is set.
|
||||||
@@ -442,7 +548,7 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
|||||||
e.writeUvarint(uint64(x.FileNum))
|
e.writeUvarint(uint64(x.FileNum))
|
||||||
}
|
}
|
||||||
for _, x := range v.NewFiles {
|
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
|
var tag uint64
|
||||||
switch {
|
switch {
|
||||||
case x.Meta.HasRangeKeys:
|
case x.Meta.HasRangeKeys:
|
||||||
@@ -495,6 +601,10 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
|||||||
e.writeUvarint(customTagNeedsCompaction)
|
e.writeUvarint(customTagNeedsCompaction)
|
||||||
e.writeBytes([]byte{1})
|
e.writeBytes([]byte{1})
|
||||||
}
|
}
|
||||||
|
if x.Meta.Virtual {
|
||||||
|
e.writeUvarint(customTagVirtual)
|
||||||
|
e.writeUvarint(uint64(x.Meta.FileBacking.DiskFileNum.FileNum()))
|
||||||
|
}
|
||||||
e.writeUvarint(customTagTerminate)
|
e.writeUvarint(customTagTerminate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,6 +612,7 @@ func (v *VersionEdit) Encode(w io.Writer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// versionEditDecoder should be used to decode version edits.
|
||||||
type versionEditDecoder struct {
|
type versionEditDecoder struct {
|
||||||
byteReader
|
byteReader
|
||||||
}
|
}
|
||||||
@@ -581,10 +692,30 @@ func (e versionEditEncoder) writeUvarint(u uint64) {
|
|||||||
|
|
||||||
// BulkVersionEdit summarizes the files added and deleted from a set of version
|
// BulkVersionEdit summarizes the files added and deleted from a set of version
|
||||||
// edits.
|
// edits.
|
||||||
|
//
|
||||||
|
// INVARIANTS:
|
||||||
|
// No file can be added to a level more than once. This is true globally, and
|
||||||
|
// also true for all of the calls to Accumulate for a single bulk version edit.
|
||||||
|
//
|
||||||
|
// No file can be removed from a level more than once. This is true globally,
|
||||||
|
// and also true for all of the calls to Accumulate for a single bulk version
|
||||||
|
// edit.
|
||||||
|
//
|
||||||
|
// A file must not be added and removed from a given level in the same version
|
||||||
|
// edit.
|
||||||
|
//
|
||||||
|
// A file that is being removed from a level must have been added to that level
|
||||||
|
// before (in a prior version edit). Note that a given file can be deleted from
|
||||||
|
// a level and added to another level in a single version edit
|
||||||
type BulkVersionEdit struct {
|
type BulkVersionEdit struct {
|
||||||
Added [NumLevels][]*FileMetadata
|
Added [NumLevels]map[base.FileNum]*FileMetadata
|
||||||
Deleted [NumLevels]map[base.FileNum]*FileMetadata
|
Deleted [NumLevels]map[base.FileNum]*FileMetadata
|
||||||
|
|
||||||
|
// 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
|
// AddedByFileNum maps file number to file metadata for all added files
|
||||||
// from accumulated version edits. AddedByFileNum is only populated if set
|
// from accumulated version edits. AddedByFileNum is only populated if set
|
||||||
// to non-nil by a caller. It must be set to non-nil when replaying
|
// to non-nil by a caller. It must be set to non-nil when replaying
|
||||||
@@ -603,6 +734,19 @@ type BulkVersionEdit struct {
|
|||||||
|
|
||||||
// Accumulate adds the file addition and deletions in the specified version
|
// Accumulate adds the file addition and deletions in the specified version
|
||||||
// edit to the bulk edit's internal state.
|
// edit to the bulk edit's internal state.
|
||||||
|
//
|
||||||
|
// INVARIANTS:
|
||||||
|
// If a file is added to a given level in a call to Accumulate and then removed
|
||||||
|
// from that level in a subsequent call, the file will not be present in the
|
||||||
|
// resulting BulkVersionEdit.Deleted for that level.
|
||||||
|
//
|
||||||
|
// After accumulation of version edits, the bulk version edit may have
|
||||||
|
// information about a file which has been deleted from a level, but it may
|
||||||
|
// not have information about the same file added to the same level. The add
|
||||||
|
// could've occurred as part of a previous bulk version edit. In this case,
|
||||||
|
// the deleted file must be present in BulkVersionEdit.Deleted, at the end
|
||||||
|
// of the accumulation, because we need to decrease the refcount of the
|
||||||
|
// deleted file in Apply.
|
||||||
func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
||||||
for df, m := range ve.DeletedFiles {
|
for df, m := range ve.DeletedFiles {
|
||||||
dmap := b.Deleted[df.Level]
|
dmap := b.Deleted[df.Level]
|
||||||
@@ -624,7 +768,28 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
|||||||
if m.MarkedForCompaction {
|
if m.MarkedForCompaction {
|
||||||
b.MarkedForCompactionCountDiff--
|
b.MarkedForCompactionCountDiff--
|
||||||
}
|
}
|
||||||
dmap[df.FileNum] = m
|
if _, ok := b.Added[df.Level][df.FileNum]; !ok {
|
||||||
|
dmap[df.FileNum] = m
|
||||||
|
} else {
|
||||||
|
// Present in b.Added for the same level.
|
||||||
|
delete(b.Added[df.Level], df.FileNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
for _, nf := range ve.NewFiles {
|
||||||
@@ -635,7 +800,21 @@ 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)
|
return base.CorruptionErrorf("pebble: file deleted L%d.%s before it was inserted", nf.Level, nf.Meta.FileNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b.Added[nf.Level] = append(b.Added[nf.Level], nf.Meta)
|
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)
|
||||||
|
}
|
||||||
|
b.Added[nf.Level][nf.Meta.FileNum] = nf.Meta
|
||||||
if b.AddedByFileNum != nil {
|
if b.AddedByFileNum != nil {
|
||||||
b.AddedByFileNum[nf.Meta.FileNum] = nf.Meta
|
b.AddedByFileNum[nf.Meta.FileNum] = nf.Meta
|
||||||
}
|
}
|
||||||
@@ -643,36 +822,101 @@ func (b *BulkVersionEdit) Accumulate(ve *VersionEdit) error {
|
|||||||
b.MarkedForCompactionCountDiff++
|
b.MarkedForCompactionCountDiff++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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...)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AccumulateIncompleteAndApplySingleVE should be called if a single version edit
|
||||||
|
// is to be applied to the provided curr Version and if the caller needs to
|
||||||
|
// update the versionSet.zombieTables map. This function exists separately from
|
||||||
|
// BulkVersionEdit.Apply because it is easier to reason about properties
|
||||||
|
// regarding BulkVersionedit.Accumulate/Apply and zombie table generation, if we
|
||||||
|
// know that exactly one version edit is being accumulated.
|
||||||
|
//
|
||||||
|
// Note that the version edit passed into this function may be incomplete
|
||||||
|
// because compactions don't have the ref counting information necessary to
|
||||||
|
// populate VersionEdit.RemovedBackingTables. This function will complete such a
|
||||||
|
// version edit by populating RemovedBackingTables.
|
||||||
|
//
|
||||||
|
// Invariant: Any file being deleted through ve must belong to the curr Version.
|
||||||
|
// We can't have a delete for some arbitrary file which does not exist in curr.
|
||||||
|
func AccumulateIncompleteAndApplySingleVE(
|
||||||
|
ve *VersionEdit,
|
||||||
|
curr *Version,
|
||||||
|
cmp Compare,
|
||||||
|
formatKey base.FormatKey,
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
var b BulkVersionEdit
|
||||||
|
err := b.Accumulate(ve)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
zombies = make(map[base.DiskFileNum]uint64)
|
||||||
|
v, err := b.Apply(
|
||||||
|
curr, cmp, formatKey, flushSplitBytes, readCompactionRate, zombies, orderingInvariants,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range b.AddedFileBacking {
|
||||||
|
addBackingFunc(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
for fileNum := range zombies {
|
||||||
|
if _, ok := backingStateMap[fileNum]; ok {
|
||||||
|
// This table was backing some virtual sstable in the latest version,
|
||||||
|
// but is now a zombie. We add RemovedBackingTables entries for
|
||||||
|
// these, before the version edit is written to disk.
|
||||||
|
ve.RemovedBackingTables = append(
|
||||||
|
ve.RemovedBackingTables, fileNum,
|
||||||
|
)
|
||||||
|
removeBackingFunc(fileNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v, zombies, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Apply applies the delta b to the current version to produce a new
|
// Apply applies the delta b to the current version to produce a new
|
||||||
// version. The new version is consistent with respect to the comparer cmp.
|
// version. The new version is consistent with respect to the comparer cmp.
|
||||||
//
|
//
|
||||||
// curr may be nil, which is equivalent to a pointer to a zero version.
|
// curr may be nil, which is equivalent to a pointer to a zero version.
|
||||||
//
|
//
|
||||||
// On success, a map of zombie files containing the file numbers and sizes of
|
// On success, if a non-nil zombies map is provided to Apply, the map is updated
|
||||||
// deleted files is returned. These files are considered zombies because they
|
// with file numbers and files sizes of deleted files. These files are
|
||||||
// are no longer referenced by the returned Version, but cannot be deleted from
|
// considered zombies because they are no longer referenced by the returned
|
||||||
// disk as they are still in use by the incoming Version.
|
// Version, but cannot be deleted from disk as they are still in use by the
|
||||||
|
// incoming Version.
|
||||||
func (b *BulkVersionEdit) Apply(
|
func (b *BulkVersionEdit) Apply(
|
||||||
curr *Version,
|
curr *Version,
|
||||||
cmp Compare,
|
cmp Compare,
|
||||||
formatKey base.FormatKey,
|
formatKey base.FormatKey,
|
||||||
flushSplitBytes int64,
|
flushSplitBytes int64,
|
||||||
readCompactionRate int64,
|
readCompactionRate int64,
|
||||||
) (_ *Version, zombies map[base.FileNum]uint64, _ error) {
|
zombies map[base.DiskFileNum]uint64,
|
||||||
addZombie := func(fileNum base.FileNum, size uint64) {
|
orderingInvariants OrderingInvariants,
|
||||||
if zombies == nil {
|
) (*Version, error) {
|
||||||
zombies = make(map[base.FileNum]uint64)
|
addZombie := func(state *FileBacking) {
|
||||||
}
|
|
||||||
zombies[fileNum] = size
|
|
||||||
}
|
|
||||||
// The remove zombie function is used to handle tables that are moved from
|
|
||||||
// one level to another during a version edit (i.e. a "move" compaction).
|
|
||||||
removeZombie := func(fileNum base.FileNum) {
|
|
||||||
if zombies != nil {
|
if zombies != nil {
|
||||||
delete(zombies, fileNum)
|
zombies[state.DiskFileNum] = state.Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
removeZombie := func(state *FileBacking) {
|
||||||
|
if zombies != nil {
|
||||||
|
delete(zombies, state.DiskFileNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -684,7 +928,7 @@ func (b *BulkVersionEdit) Apply(
|
|||||||
}
|
}
|
||||||
v.Stats.MarkedForCompaction += b.MarkedForCompactionCountDiff
|
v.Stats.MarkedForCompaction += b.MarkedForCompactionCountDiff
|
||||||
if v.Stats.MarkedForCompaction < 0 {
|
if v.Stats.MarkedForCompaction < 0 {
|
||||||
return nil, nil, base.CorruptionErrorf("pebble: version marked for compaction count negative")
|
return nil, base.CorruptionErrorf("pebble: version marked for compaction count negative")
|
||||||
}
|
}
|
||||||
|
|
||||||
for level := range v.Levels {
|
for level := range v.Levels {
|
||||||
@@ -705,7 +949,7 @@ func (b *BulkVersionEdit) Apply(
|
|||||||
// Initialize L0Sublevels.
|
// Initialize L0Sublevels.
|
||||||
if curr == nil || curr.L0Sublevels == nil {
|
if curr == nil || curr.L0Sublevels == nil {
|
||||||
if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
|
if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "pebble: internal error")
|
return nil, errors.Wrap(err, "pebble: internal error")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v.L0Sublevels = curr.L0Sublevels
|
v.L0Sublevels = curr.L0Sublevels
|
||||||
@@ -718,47 +962,70 @@ func (b *BulkVersionEdit) Apply(
|
|||||||
// Some edits on this level.
|
// Some edits on this level.
|
||||||
lm := &v.Levels[level]
|
lm := &v.Levels[level]
|
||||||
lmRange := &v.RangeKeyLevels[level]
|
lmRange := &v.RangeKeyLevels[level]
|
||||||
addedFiles := b.Added[level]
|
|
||||||
deletedMap := b.Deleted[level]
|
addedFilesMap := b.Added[level]
|
||||||
if n := v.Levels[level].Len() + len(addedFiles); n == 0 {
|
deletedFilesMap := b.Deleted[level]
|
||||||
return nil, nil, base.CorruptionErrorf(
|
if n := v.Levels[level].Len() + len(addedFilesMap); n == 0 {
|
||||||
|
return nil, base.CorruptionErrorf(
|
||||||
"pebble: internal error: No current or added files but have deleted files: %d",
|
"pebble: internal error: No current or added files but have deleted files: %d",
|
||||||
errors.Safe(len(deletedMap)))
|
errors.Safe(len(deletedFilesMap)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NB: addedFiles may be empty and it also is not necessarily
|
// NB: addedFilesMap may be empty. If a file is present in addedFilesMap
|
||||||
// internally consistent: it does not reflect deletions in deletedMap.
|
// for a level, it won't be present in deletedFilesMap for the same
|
||||||
|
// level.
|
||||||
|
|
||||||
for _, f := range deletedMap {
|
for _, f := range deletedFilesMap {
|
||||||
addZombie(f.FileNum, f.Size)
|
if obsolete := v.Levels[level].remove(f); obsolete {
|
||||||
if obsolete := v.Levels[level].tree.Delete(f); obsolete {
|
|
||||||
// Deleting a file from the B-Tree may decrement its
|
// Deleting a file from the B-Tree may decrement its
|
||||||
// reference count. However, because we cloned the
|
// reference count. However, because we cloned the
|
||||||
// previous level's B-Tree, this should never result in a
|
// previous level's B-Tree, this should never result in a
|
||||||
// file's reference count dropping to zero.
|
// file's reference count dropping to zero.
|
||||||
err := errors.Errorf("pebble: internal error: file L%d.%s obsolete during B-Tree removal", level, f.FileNum)
|
err := errors.Errorf("pebble: internal error: file L%d.%s obsolete during B-Tree removal", level, f.FileNum)
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if f.HasRangeKeys {
|
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
|
// Deleting a file from the B-Tree may decrement its
|
||||||
// reference count. However, because we cloned the
|
// reference count. However, because we cloned the
|
||||||
// previous level's B-Tree, this should never result in a
|
// previous level's B-Tree, this should never result in a
|
||||||
// file's reference count dropping to zero.
|
// file's reference count dropping to zero.
|
||||||
err := errors.Errorf("pebble: internal error: file L%d.%s obsolete during range-key B-Tree removal", level, f.FileNum)
|
err := errors.Errorf("pebble: internal error: file L%d.%s obsolete during range-key B-Tree removal", level, f.FileNum)
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that a backing sst will only become a zombie if the
|
||||||
|
// references to it in the latest version is 0. We will remove the
|
||||||
|
// backing sst from the zombie list in the next loop if one of the
|
||||||
|
// addedFiles in any of the levels is referencing the backing sst.
|
||||||
|
// This is possible if a physical sstable is virtualized, or if it
|
||||||
|
// is moved.
|
||||||
|
latestRefCount := f.LatestRefs()
|
||||||
|
if latestRefCount <= 0 {
|
||||||
|
// If a file is present in deletedFilesMap for a level, then it
|
||||||
|
// must have already been added to the level previously, which
|
||||||
|
// means that its latest ref count cannot be 0.
|
||||||
|
err := errors.Errorf("pebble: internal error: incorrect latestRefs reference counting for file", f.FileNum)
|
||||||
|
return nil, err
|
||||||
|
} else if f.LatestUnref() == 0 {
|
||||||
|
addZombie(f.FileBacking)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addedFiles := make([]*FileMetadata, 0, len(addedFilesMap))
|
||||||
|
for _, f := range addedFilesMap {
|
||||||
|
addedFiles = append(addedFiles, f)
|
||||||
|
}
|
||||||
|
// Sort addedFiles by file number. This isn't necessary, but tests which
|
||||||
|
// replay invalid manifests check the error output, and the error output
|
||||||
|
// depends on the order in which files are added to the btree.
|
||||||
|
sort.Slice(addedFiles, func(i, j int) bool {
|
||||||
|
return addedFiles[i].FileNum < addedFiles[j].FileNum
|
||||||
|
})
|
||||||
|
|
||||||
var sm, la *FileMetadata
|
var sm, la *FileMetadata
|
||||||
for _, f := range addedFiles {
|
for _, f := range addedFiles {
|
||||||
if _, ok := deletedMap[f.FileNum]; ok {
|
|
||||||
// Already called addZombie on this file in the preceding
|
|
||||||
// loop, so we don't need to do it here.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// NB: allowedSeeks is used for read triggered compactions. It is set using
|
// NB: allowedSeeks is used for read triggered compactions. It is set using
|
||||||
// Options.Experimental.ReadCompactionRate which defaults to 32KB.
|
// Options.Experimental.ReadCompactionRate which defaults to 32KB.
|
||||||
var allowedSeeks int64
|
var allowedSeeks int64
|
||||||
@@ -768,20 +1035,23 @@ func (b *BulkVersionEdit) Apply(
|
|||||||
if allowedSeeks < 100 {
|
if allowedSeeks < 100 {
|
||||||
allowedSeeks = 100
|
allowedSeeks = 100
|
||||||
}
|
}
|
||||||
atomic.StoreInt64(&f.Atomic.AllowedSeeks, allowedSeeks)
|
f.AllowedSeeks.Store(allowedSeeks)
|
||||||
f.InitAllowedSeeks = 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()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "pebble")
|
return nil, errors.Wrap(err, "pebble")
|
||||||
}
|
}
|
||||||
if f.HasRangeKeys {
|
if f.HasRangeKeys {
|
||||||
err = lmRange.tree.Insert(f)
|
err = lmRange.insert(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "pebble")
|
return nil, errors.Wrap(err, "pebble")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
removeZombie(f.FileNum)
|
removeZombie(f.FileBacking)
|
||||||
// Track the keys with the smallest and largest keys, so that we can
|
// Track the keys with the smallest and largest keys, so that we can
|
||||||
// check consistency of the modified span.
|
// check consistency of the modified span.
|
||||||
if sm == nil || base.InternalCompare(cmp, sm.Smallest, f.Smallest) > 0 {
|
if sm == nil || base.InternalCompare(cmp, sm.Smallest, f.Smallest) > 0 {
|
||||||
@@ -793,27 +1063,36 @@ func (b *BulkVersionEdit) Apply(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if level == 0 {
|
if level == 0 {
|
||||||
if curr != nil && curr.L0Sublevels != nil && len(deletedMap) == 0 {
|
if curr != nil && curr.L0Sublevels != nil && len(deletedFilesMap) == 0 {
|
||||||
// Flushes and ingestions that do not delete any L0 files do not require
|
// Flushes and ingestions that do not delete any L0 files do not require
|
||||||
// a regeneration of L0Sublevels from scratch. We can instead generate
|
// a regeneration of L0Sublevels from scratch. We can instead generate
|
||||||
// it incrementally.
|
// it incrementally.
|
||||||
var err error
|
var err error
|
||||||
// AddL0Files requires addedFiles to be sorted in seqnum order.
|
// AddL0Files requires addedFiles to be sorted in seqnum order.
|
||||||
addedFiles = append([]*FileMetadata(nil), addedFiles...)
|
|
||||||
SortBySeqNum(addedFiles)
|
SortBySeqNum(addedFiles)
|
||||||
v.L0Sublevels, err = curr.L0Sublevels.AddL0Files(addedFiles, flushSplitBytes, &v.Levels[0])
|
v.L0Sublevels, err = curr.L0Sublevels.AddL0Files(addedFiles, flushSplitBytes, &v.Levels[0])
|
||||||
if errors.Is(err, errInvalidL0SublevelsOpt) {
|
if errors.Is(err, errInvalidL0SublevelsOpt) {
|
||||||
err = v.InitL0Sublevels(cmp, formatKey, flushSplitBytes)
|
err = v.InitL0Sublevels(cmp, formatKey, flushSplitBytes)
|
||||||
|
} else if invariants.Enabled && err == nil {
|
||||||
|
copyOfSublevels, err := NewL0Sublevels(&v.Levels[0], cmp, formatKey, flushSplitBytes)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("error when regenerating sublevels: %s", err))
|
||||||
|
}
|
||||||
|
s1 := describeSublevels(base.DefaultFormatter, false /* verbose */, copyOfSublevels.Levels)
|
||||||
|
s2 := describeSublevels(base.DefaultFormatter, false /* verbose */, v.L0Sublevels.Levels)
|
||||||
|
if s1 != s2 {
|
||||||
|
panic(fmt.Sprintf("incremental L0 sublevel generation produced different output than regeneration: %s != %s", s1, s2))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "pebble: internal error")
|
return nil, errors.Wrap(err, "pebble: internal error")
|
||||||
}
|
}
|
||||||
v.L0SublevelFiles = v.L0Sublevels.Levels
|
v.L0SublevelFiles = v.L0Sublevels.Levels
|
||||||
} else if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
|
} else if err := v.InitL0Sublevels(cmp, formatKey, flushSplitBytes); err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "pebble: internal error")
|
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, nil, errors.Wrap(err, "pebble: internal error")
|
return nil, errors.Wrap(err, "pebble: internal error")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -833,10 +1112,10 @@ func (b *BulkVersionEdit) Apply(
|
|||||||
end.Prev()
|
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, nil, errors.Wrap(err, "pebble: internal error")
|
return nil, errors.Wrap(err, "pebble: internal error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return v, zombies, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|||||||
2
vendor/github.com/cockroachdb/pebble/internal/private/sstable.go
generated
vendored
2
vendor/github.com/cockroachdb/pebble/internal/private/sstable.go
generated
vendored
@@ -8,7 +8,7 @@ import "github.com/cockroachdb/pebble/internal/base"
|
|||||||
|
|
||||||
// SSTableCacheOpts is a hook for specifying cache options to
|
// SSTableCacheOpts is a hook for specifying cache options to
|
||||||
// sstable.NewReader.
|
// sstable.NewReader.
|
||||||
var SSTableCacheOpts func(cacheID uint64, fileNum base.FileNum) interface{}
|
var SSTableCacheOpts func(cacheID uint64, fileNum base.DiskFileNum) interface{}
|
||||||
|
|
||||||
// SSTableRawTombstonesOpt is a sstable.Reader option for disabling
|
// SSTableRawTombstonesOpt is a sstable.Reader option for disabling
|
||||||
// fragmentation of the range tombstones returned by
|
// fragmentation of the range tombstones returned by
|
||||||
|
|||||||
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
|
// ╰── <?>.Next
|
||||||
type UserIteratorConfig struct {
|
type UserIteratorConfig struct {
|
||||||
snapshot uint64
|
snapshot uint64
|
||||||
comparer *base.Comparer
|
comparer *base.Comparer
|
||||||
miter keyspan.MergingIter
|
miter keyspan.MergingIter
|
||||||
biter keyspan.BoundedIter
|
biter keyspan.BoundedIter
|
||||||
diter keyspan.DefragmentingIter
|
diter keyspan.DefragmentingIter
|
||||||
liters [manifest.NumLevels]keyspan.LevelIter
|
liters [manifest.NumLevels]keyspan.LevelIter
|
||||||
litersUsed int
|
litersUsed int
|
||||||
onlySets bool
|
internalKeys bool
|
||||||
bufs *Buffers
|
bufs *Buffers
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffers holds various buffers used for range key iteration. They're exposed
|
// 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
|
// Init initializes the range key iterator stack for user iteration. The
|
||||||
// resulting fragment iterator applies range key semantics, defragments spans
|
// 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
|
// 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
|
// The snapshot sequence number parameter determines which keys are visible. Any
|
||||||
// keys not visible at the provided snapshot are ignored.
|
// keys not visible at the provided snapshot are ignored.
|
||||||
@@ -91,16 +92,20 @@ func (ui *UserIteratorConfig) Init(
|
|||||||
lower, upper []byte,
|
lower, upper []byte,
|
||||||
hasPrefix *bool,
|
hasPrefix *bool,
|
||||||
prefix *[]byte,
|
prefix *[]byte,
|
||||||
onlySets bool,
|
internalKeys bool,
|
||||||
bufs *Buffers,
|
bufs *Buffers,
|
||||||
iters ...keyspan.FragmentIterator,
|
iters ...keyspan.FragmentIterator,
|
||||||
) keyspan.FragmentIterator {
|
) keyspan.FragmentIterator {
|
||||||
ui.snapshot = snapshot
|
ui.snapshot = snapshot
|
||||||
ui.comparer = comparer
|
ui.comparer = comparer
|
||||||
ui.onlySets = onlySets
|
ui.internalKeys = internalKeys
|
||||||
ui.miter.Init(comparer.Compare, ui, &bufs.merging, iters...)
|
ui.miter.Init(comparer.Compare, ui, &bufs.merging, iters...)
|
||||||
ui.biter.Init(comparer.Compare, comparer.Split, &ui.miter, lower, upper, hasPrefix, prefix)
|
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.litersUsed = 0
|
||||||
ui.bufs = bufs
|
ui.bufs = bufs
|
||||||
return &ui.diter
|
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)
|
// 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
|
// and then non-RangeKeySet keys are removed. The resulting transformed spans
|
||||||
// only contain RangeKeySets describing the state visible at the provided
|
// 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 {
|
func (ui *UserIteratorConfig) Transform(cmp base.Compare, s keyspan.Span, dst *keyspan.Span) error {
|
||||||
// Apply shadowing of keys.
|
// Apply shadowing of keys.
|
||||||
dst.Start = s.Start
|
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 {
|
if err := coalesce(ui.comparer.Equal, &ui.bufs.sortBuf, ui.snapshot, s.Keys); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// During user iteration over range keys, unsets and deletes don't matter.
|
if ui.internalKeys {
|
||||||
// Remove them if onlySets = true. This step helps logical defragmentation
|
if s.KeysOrder != keyspan.ByTrailerDesc {
|
||||||
// during iteration.
|
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
|
keys := ui.bufs.sortBuf.Keys
|
||||||
dst.Keys = dst.Keys[:0]
|
dst.Keys = dst.Keys[:0]
|
||||||
for i := range keys {
|
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 {
|
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")
|
panic("pebble: keys unexpectedly not in ascending suffix order")
|
||||||
}
|
}
|
||||||
if ui.onlySets {
|
// Skip.
|
||||||
// Skip.
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
dst.Keys = append(dst.Keys, keys[i])
|
|
||||||
case base.InternalKeyKindRangeKeyDelete:
|
case base.InternalKeyKindRangeKeyDelete:
|
||||||
if ui.onlySets {
|
// Skip.
|
||||||
// Skip.
|
continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
dst.Keys = append(dst.Keys, keys[i])
|
|
||||||
default:
|
default:
|
||||||
return base.CorruptionErrorf("pebble: unrecognized range key kind %s", keys[i].Kind())
|
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
|
// sequence numbers). It's intended for use during user iteration, when the
|
||||||
// wrapped keyspan iterator is merging spans across all levels of the LSM.
|
// wrapped keyspan iterator is merging spans across all levels of the LSM.
|
||||||
func (ui *UserIteratorConfig) ShouldDefragment(equal base.Equal, a, b *keyspan.Span) bool {
|
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
|
// This implementation must only be used on spans that have transformed by
|
||||||
// ui.Transform. The transform applies shadowing, removes all keys besides
|
// ui.Transform. The transform applies shadowing, removes all keys besides
|
||||||
// the resulting Sets and sorts the keys by suffix. Since shadowing has been
|
// 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
|
ret := true
|
||||||
for i := range a.Keys {
|
for i := range a.Keys {
|
||||||
if invariants.Enabled {
|
if invariants.Enabled {
|
||||||
if ui.onlySets && (a.Keys[i].Kind() != base.InternalKeyKindRangeKeySet ||
|
if a.Keys[i].Kind() != base.InternalKeyKindRangeKeySet ||
|
||||||
b.Keys[i].Kind() != base.InternalKeyKindRangeKeySet) {
|
b.Keys[i].Kind() != base.InternalKeyKindRangeKeySet {
|
||||||
panic("pebble: unexpected non-RangeKeySet during defragmentation")
|
panic("pebble: unexpected non-RangeKeySet during defragmentation")
|
||||||
}
|
}
|
||||||
if i > 0 && (ui.comparer.Compare(a.Keys[i].Suffix, a.Keys[i-1].Suffix) < 0 ||
|
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"
|
"strings"
|
||||||
|
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
"golang.org/x/exp/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
const alpha = "abcdefghijklmnopqrstuvwxyz"
|
const alpha = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
|
||||||
const suffixDelim = '@'
|
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.
|
// 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.
|
// Comparer is the comparer for test keys generated by this package.
|
||||||
var Comparer *base.Comparer = &base.Comparer{
|
var Comparer = &base.Comparer{
|
||||||
Compare: compare,
|
Compare: compare,
|
||||||
Equal: func(a, b []byte) bool { return compare(a, b) == 0 },
|
Equal: func(a, b []byte) bool { return compare(a, b) == 0 },
|
||||||
AbbreviatedKey: func(k []byte) uint64 {
|
AbbreviatedKey: func(k []byte) uint64 {
|
||||||
@@ -136,7 +146,7 @@ func compareTimestamps(a, b []byte) int {
|
|||||||
// Keyspace describes a finite keyspace of unsuffixed test keys.
|
// Keyspace describes a finite keyspace of unsuffixed test keys.
|
||||||
type Keyspace interface {
|
type Keyspace interface {
|
||||||
// Count returns the number of keys that exist within this keyspace.
|
// 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
|
// MaxLen returns the maximum length, in bytes, of a key within this
|
||||||
// keyspace. This is only guaranteed to return an upper bound.
|
// 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,
|
// Slice returns the sub-keyspace from index i, inclusive, to index j,
|
||||||
// exclusive. The receiver is unmodified.
|
// 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
|
// EveryN returns a key space that includes 1 key for every N keys in the
|
||||||
// original keyspace. The receiver is unmodified.
|
// 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
|
// Divvy divides the provided keyspace into N equal portions, containing
|
||||||
// disjoint keys evenly distributed across the keyspace.
|
// 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)
|
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)
|
ret[i] = ks.Slice(i, ks.Count()).EveryN(n)
|
||||||
}
|
}
|
||||||
return ret
|
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
|
// KeyAt returns the i-th key within the keyspace with a suffix encoding the
|
||||||
// timestamp t.
|
// 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)
|
b := make([]byte, k.MaxLen()+MaxSuffixLen)
|
||||||
return b[:WriteKeyAt(b, k, i, t)]
|
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
|
// 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
|
// suffix encoding the timestamp t suffix. It returns the number of bytes
|
||||||
// written.
|
// 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 := WriteKey(dst, k, i)
|
||||||
n += WriteSuffix(dst[n:], t)
|
n += WriteSuffix(dst[n:], t)
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suffix returns the test keys suffix representation of timestamp t.
|
// Suffix returns the test keys suffix representation of timestamp t.
|
||||||
func Suffix(t int) []byte {
|
func Suffix(t int64) []byte {
|
||||||
b := make([]byte, MaxSuffixLen)
|
b := make([]byte, MaxSuffixLen)
|
||||||
return b[:WriteSuffix(b, t)]
|
return b[:WriteSuffix(b, t)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuffixLen returns the exact length of the given suffix when encoded.
|
// 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.
|
// Begin at 1 for the '@' delimiter, 1 for a single digit.
|
||||||
n := 2
|
n := 2
|
||||||
t /= 10
|
t /= 10
|
||||||
@@ -208,42 +219,42 @@ func SuffixLen(t int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseSuffix returns the integer representation of the encoded suffix.
|
// ParseSuffix returns the integer representation of the encoded suffix.
|
||||||
func ParseSuffix(s []byte) (int, error) {
|
func ParseSuffix(s []byte) (int64, error) {
|
||||||
return strconv.Atoi(strings.TrimPrefix(string(s), string(suffixDelim)))
|
return strconv.ParseInt(strings.TrimPrefix(string(s), string(suffixDelim)), 10, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteSuffix writes the test keys suffix representation of timestamp t to dst,
|
// WriteSuffix writes the test keys suffix representation of timestamp t to dst,
|
||||||
// returning the number of bytes written.
|
// returning the number of bytes written.
|
||||||
func WriteSuffix(dst []byte, t int) int {
|
func WriteSuffix(dst []byte, t int64) int {
|
||||||
dst[0] = suffixDelim
|
dst[0] = suffixDelim
|
||||||
n := 1
|
n := 1
|
||||||
n += len(strconv.AppendInt(dst[n:n], int64(t), 10))
|
n += len(strconv.AppendInt(dst[n:n], t, 10))
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key returns the i-th unsuffixed key within the keyspace.
|
// 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())
|
b := make([]byte, k.MaxLen())
|
||||||
return b[:k.key(b, i)]
|
return b[:k.key(b, i)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteKey writes the i-th unsuffixed key within the keyspace to the buffer dst. It
|
// WriteKey writes the i-th unsuffixed key within the keyspace to the buffer dst. It
|
||||||
// returns the number of bytes written.
|
// 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)
|
return k.key(dst, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
type alphabet struct {
|
type alphabet struct {
|
||||||
alphabet []byte
|
alphabet []byte
|
||||||
maxLength int
|
maxLength int
|
||||||
headSkip int
|
headSkip int64
|
||||||
tailSkip int
|
tailSkip int64
|
||||||
increment int
|
increment int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a alphabet) Count() int {
|
func (a alphabet) Count() int64 {
|
||||||
// Calculate the total number of keys, ignoring the increment.
|
// 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.
|
// The increment dictates that we take every N keys, where N = a.increment.
|
||||||
// Consider a total containing the 5 keys:
|
// Consider a total containing the 5 keys:
|
||||||
@@ -263,20 +274,25 @@ func (a alphabet) MaxLen() int {
|
|||||||
return a.maxLength
|
return a.maxLength
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a alphabet) Slice(i, j int) Keyspace {
|
func (a alphabet) Slice(i, j int64) Keyspace {
|
||||||
s := a
|
s := a
|
||||||
s.headSkip += i
|
s.headSkip += i
|
||||||
s.tailSkip += a.Count() - j
|
s.tailSkip += a.Count() - j
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a alphabet) EveryN(n int) Keyspace {
|
func (a alphabet) EveryN(n int64) Keyspace {
|
||||||
s := a
|
s := a
|
||||||
s.increment *= n
|
s.increment *= n
|
||||||
return s
|
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
|
// 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
|
// length of the alphabet n and the max key length l. Consider how the
|
||||||
// number of representable keys grows as l increases:
|
// 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
|
// l = 3: n + n^2 + n^3
|
||||||
// ...
|
// ...
|
||||||
// Σ i=(1...l) n^i = n*(n^l - 1)/(n-1)
|
// Σ 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
|
// This function generates keys of length 1..maxKeyLength, pulling
|
||||||
// characters from the alphabet. The idx determines which key to generate,
|
// characters from the alphabet. The idx determines which key to generate,
|
||||||
// generating the i-th lexicographically next key.
|
// 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))
|
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 {
|
if keyCount == 0 || i > keyCount || i < 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Of the keyCount keys in the generative keyspace, how many are there
|
// Of the keyCount keys in the generative keyspace, how many are there
|
||||||
// starting with a particular character?
|
// 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.
|
// Find the character that the key at index i starts with and set it.
|
||||||
characterIdx := i / keysPerCharacter
|
characterIdx := i / keysPerCharacter
|
||||||
@@ -347,3 +363,156 @@ func generateAlphabetKey(buf, alphabet []byte, i, keyCount int) int {
|
|||||||
i = i - keysPerCharacter*characterIdx - 1
|
i = i - keysPerCharacter*characterIdx - 1
|
||||||
return 1 + generateAlphabetKey(buf[1:], alphabet, i, keysPerCharacter-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
|
||||||
|
}
|
||||||
|
|||||||
332
vendor/github.com/cockroachdb/pebble/internal_iterator.go
generated
vendored
332
vendor/github.com/cockroachdb/pebble/internal_iterator.go
generated
vendored
@@ -1,332 +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 pebble
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
|
||||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
|
||||||
"github.com/cockroachdb/pebble/internal/manifest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// scanInternalIterator is an iterator that returns all internal keys instead of
|
|
||||||
// collapsing them by user keys. For instance, an InternalKeyKindDelete would be
|
|
||||||
// returned as an InternalKeyKindDelete instead of the iterator skipping over to
|
|
||||||
// the next key. Useful if an external user of Pebble needs to observe and
|
|
||||||
// rebuild Pebble's history of internal keys, such as in node-to-node
|
|
||||||
// replication. For use with {db,snapshot}.ScanInternal().
|
|
||||||
//
|
|
||||||
// scanInternalIterator is allowed to ignorepoint keys deleted by range deletions,
|
|
||||||
// and range keys shadowed by a range key unset or delete, however it *must*
|
|
||||||
// return the range delete as well as the range key unset/delete that did the
|
|
||||||
// shadowing.
|
|
||||||
type scanInternalIterator struct {
|
|
||||||
opts IterOptions
|
|
||||||
comparer *base.Comparer
|
|
||||||
iter internalIterator
|
|
||||||
readState *readState
|
|
||||||
rangeKey *iteratorRangeKeyState
|
|
||||||
pointKeyIter keyspan.InterleavingIter
|
|
||||||
iterKey *InternalKey
|
|
||||||
iterValue LazyValue
|
|
||||||
alloc *iterAlloc
|
|
||||||
newIters tableNewIters
|
|
||||||
newIterRangeKey keyspan.TableNewSpanIter
|
|
||||||
seqNum uint64
|
|
||||||
|
|
||||||
// boundsBuf holds two buffers used to store the lower and upper bounds.
|
|
||||||
// Whenever the InternalIterator's bounds change, the new bounds are copied
|
|
||||||
// into boundsBuf[boundsBufIdx]. The two bounds share a slice to reduce
|
|
||||||
// allocations. opts.LowerBound and opts.UpperBound point into this slice.
|
|
||||||
boundsBuf [2][]byte
|
|
||||||
boundsBufIdx int
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(sumeer): scanInternalImpl is the implementation for the user-facing
|
|
||||||
// ScanInternal. Make it accept a context parameter, and plumb it through for
|
|
||||||
// tracing support.
|
|
||||||
|
|
||||||
func scanInternalImpl(
|
|
||||||
lower []byte,
|
|
||||||
iter *scanInternalIterator,
|
|
||||||
visitPointKey func(key *InternalKey, value LazyValue) error,
|
|
||||||
visitRangeDel func(start, end []byte, seqNum uint64) error,
|
|
||||||
visitRangeKey func(start, end []byte, keys []keyspan.Key) error,
|
|
||||||
) error {
|
|
||||||
for valid := iter.seekGE(lower); valid && iter.error() == nil; valid = iter.next() {
|
|
||||||
key := iter.unsafeKey()
|
|
||||||
|
|
||||||
switch key.Kind() {
|
|
||||||
case InternalKeyKindRangeKeyDelete, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeySet:
|
|
||||||
span := iter.unsafeSpan()
|
|
||||||
if err := visitRangeKey(span.Start, span.End, span.Keys); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case InternalKeyKindRangeDelete:
|
|
||||||
rangeDel := iter.unsafeRangeDel()
|
|
||||||
if err := visitRangeDel(rangeDel.Start, rangeDel.End, rangeDel.LargestSeqNum()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
val := iter.lazyValue()
|
|
||||||
if s := iter.pointKeyIter.Span(); s != nil && s.CoversAt(iter.seqNum, key.SeqNum()) {
|
|
||||||
// Key deleted by a range deletion. Skip it.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := visitPointKey(key, val); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructPointIter constructs a merging iterator and sets i.iter to it.
|
|
||||||
func (i *scanInternalIterator) constructPointIter(memtables flushableList, buf *iterAlloc) {
|
|
||||||
// Merging levels and levels from iterAlloc.
|
|
||||||
mlevels := buf.mlevels[:0]
|
|
||||||
levels := buf.levels[:0]
|
|
||||||
|
|
||||||
// We compute the number of levels needed ahead of time and reallocate a slice if
|
|
||||||
// the array from the iterAlloc isn't large enough. Doing this allocation once
|
|
||||||
// should improve the performance.
|
|
||||||
numMergingLevels := len(memtables)
|
|
||||||
numLevelIters := 0
|
|
||||||
|
|
||||||
current := i.readState.current
|
|
||||||
numMergingLevels += len(current.L0SublevelFiles)
|
|
||||||
numLevelIters += len(current.L0SublevelFiles)
|
|
||||||
for level := 1; level < len(current.Levels); level++ {
|
|
||||||
if current.Levels[level].Empty() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
numMergingLevels++
|
|
||||||
numLevelIters++
|
|
||||||
}
|
|
||||||
|
|
||||||
if numMergingLevels > cap(mlevels) {
|
|
||||||
mlevels = make([]mergingIterLevel, 0, numMergingLevels)
|
|
||||||
}
|
|
||||||
if numLevelIters > cap(levels) {
|
|
||||||
levels = make([]levelIter, 0, numLevelIters)
|
|
||||||
}
|
|
||||||
// TODO(bilal): Push these into the iterAlloc buf.
|
|
||||||
var rangeDelMiter keyspan.MergingIter
|
|
||||||
rangeDelIters := make([]keyspan.FragmentIterator, 0, numMergingLevels)
|
|
||||||
rangeDelLevels := make([]keyspan.LevelIter, 0, numLevelIters)
|
|
||||||
|
|
||||||
// Next are the memtables.
|
|
||||||
for j := len(memtables) - 1; j >= 0; j-- {
|
|
||||||
mem := memtables[j]
|
|
||||||
mlevels = append(mlevels, mergingIterLevel{
|
|
||||||
iter: mem.newIter(&i.opts),
|
|
||||||
})
|
|
||||||
if rdi := mem.newRangeDelIter(&i.opts); rdi != nil {
|
|
||||||
rangeDelIters = append(rangeDelIters, rdi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next are the file levels: L0 sub-levels followed by lower levels.
|
|
||||||
mlevelsIndex := len(mlevels)
|
|
||||||
levelsIndex := len(levels)
|
|
||||||
mlevels = mlevels[:numMergingLevels]
|
|
||||||
levels = levels[:numLevelIters]
|
|
||||||
rangeDelLevels = rangeDelLevels[:numLevelIters]
|
|
||||||
addLevelIterForFiles := func(files manifest.LevelIterator, level manifest.Level) {
|
|
||||||
li := &levels[levelsIndex]
|
|
||||||
rli := &rangeDelLevels[levelsIndex]
|
|
||||||
|
|
||||||
li.init(
|
|
||||||
context.Background(), i.opts, i.comparer.Compare, i.comparer.Split, i.newIters, files, level,
|
|
||||||
internalIterOpts{})
|
|
||||||
li.initBoundaryContext(&mlevels[mlevelsIndex].levelIterBoundaryContext)
|
|
||||||
mlevels[mlevelsIndex].iter = li
|
|
||||||
rli.Init(keyspan.SpanIterOptions{RangeKeyFilters: i.opts.RangeKeyFilters},
|
|
||||||
i.comparer.Compare, tableNewRangeDelIter(context.Background(), i.newIters), files, level,
|
|
||||||
manifest.KeyTypePoint)
|
|
||||||
rangeDelIters = append(rangeDelIters, rli)
|
|
||||||
|
|
||||||
levelsIndex++
|
|
||||||
mlevelsIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add level iterators for the L0 sublevels, iterating from newest to
|
|
||||||
// oldest.
|
|
||||||
for i := len(current.L0SublevelFiles) - 1; i >= 0; i-- {
|
|
||||||
addLevelIterForFiles(current.L0SublevelFiles[i].Iter(), manifest.L0Sublevel(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add level iterators for the non-empty non-L0 levels.
|
|
||||||
for level := 1; level < numLevels; level++ {
|
|
||||||
if current.Levels[level].Empty() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
addLevelIterForFiles(current.Levels[level].Iter(), manifest.Level(level))
|
|
||||||
}
|
|
||||||
buf.merging.init(&i.opts, &InternalIteratorStats{}, i.comparer.Compare, i.comparer.Split, mlevels...)
|
|
||||||
buf.merging.snapshot = i.seqNum
|
|
||||||
rangeDelMiter.Init(i.comparer.Compare, keyspan.VisibleTransform(i.seqNum), new(keyspan.MergingBuffers), rangeDelIters...)
|
|
||||||
i.pointKeyIter.Init(i.comparer, &buf.merging, &rangeDelMiter, nil /* mask */, i.opts.LowerBound, i.opts.UpperBound)
|
|
||||||
i.iter = &i.pointKeyIter
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructRangeKeyIter constructs the range-key iterator stack, populating
|
|
||||||
// i.rangeKey.rangeKeyIter with the resulting iterator. This is similar to
|
|
||||||
// Iterator.constructRangeKeyIter, except it doesn't handle batches and ensures
|
|
||||||
// iterConfig does *not* elide unsets/deletes.
|
|
||||||
func (i *scanInternalIterator) constructRangeKeyIter() {
|
|
||||||
// We want the bounded iter from iterConfig, but not the collapsing of
|
|
||||||
// RangeKeyUnsets and RangeKeyDels.
|
|
||||||
i.rangeKey.rangeKeyIter = i.rangeKey.iterConfig.Init(
|
|
||||||
i.comparer, i.seqNum, i.opts.LowerBound, i.opts.UpperBound,
|
|
||||||
nil /* hasPrefix */, nil /* prefix */, false, /* onlySets */
|
|
||||||
&i.rangeKey.rangeKeyBuffers.internal)
|
|
||||||
|
|
||||||
// Next are the flushables: memtables and large batches.
|
|
||||||
for j := len(i.readState.memtables) - 1; j >= 0; j-- {
|
|
||||||
mem := i.readState.memtables[j]
|
|
||||||
// We only need to read from memtables which contain sequence numbers older
|
|
||||||
// than seqNum.
|
|
||||||
if logSeqNum := mem.logSeqNum; logSeqNum >= i.seqNum {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if rki := mem.newRangeKeyIter(&i.opts); rki != nil {
|
|
||||||
i.rangeKey.iterConfig.AddLevel(rki)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
current := i.readState.current
|
|
||||||
// Next are the file levels: L0 sub-levels followed by lower levels.
|
|
||||||
//
|
|
||||||
// Add file-specific iterators for L0 files containing range keys. This is less
|
|
||||||
// efficient than using levelIters for sublevels of L0 files containing
|
|
||||||
// range keys, but range keys are expected to be sparse anyway, reducing the
|
|
||||||
// cost benefit of maintaining a separate L0Sublevels instance for range key
|
|
||||||
// files and then using it here.
|
|
||||||
//
|
|
||||||
// NB: We iterate L0's files in reverse order. They're sorted by
|
|
||||||
// LargestSeqNum ascending, and we need to add them to the merging iterator
|
|
||||||
// in LargestSeqNum descending to preserve the merging iterator's invariants
|
|
||||||
// around Key Trailer order.
|
|
||||||
iter := current.RangeKeyLevels[0].Iter()
|
|
||||||
for f := iter.Last(); f != nil; f = iter.Prev() {
|
|
||||||
spanIterOpts := &keyspan.SpanIterOptions{RangeKeyFilters: i.opts.RangeKeyFilters}
|
|
||||||
spanIter, err := i.newIterRangeKey(f, spanIterOpts)
|
|
||||||
if err != nil {
|
|
||||||
i.rangeKey.iterConfig.AddLevel(&errorKeyspanIter{err: err})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
i.rangeKey.iterConfig.AddLevel(spanIter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add level iterators for the non-empty non-L0 levels.
|
|
||||||
for level := 1; level < len(current.RangeKeyLevels); level++ {
|
|
||||||
if current.RangeKeyLevels[level].Empty() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
li := i.rangeKey.iterConfig.NewLevelIter()
|
|
||||||
spanIterOpts := keyspan.SpanIterOptions{RangeKeyFilters: i.opts.RangeKeyFilters}
|
|
||||||
li.Init(spanIterOpts, i.comparer.Compare, i.newIterRangeKey, current.RangeKeyLevels[level].Iter(),
|
|
||||||
manifest.Level(level), manifest.KeyTypeRange)
|
|
||||||
i.rangeKey.iterConfig.AddLevel(li)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// seekGE seeks this iterator to the first key that's greater than or equal
|
|
||||||
// to the specified user key.
|
|
||||||
func (i *scanInternalIterator) seekGE(key []byte) bool {
|
|
||||||
i.iterKey, i.iterValue = i.iter.SeekGE(key, base.SeekGEFlagsNone)
|
|
||||||
return i.iterKey != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafeKey returns the unsafe InternalKey at the current position. The value
|
|
||||||
// is nil if the iterator is invalid or exhausted.
|
|
||||||
func (i *scanInternalIterator) unsafeKey() *InternalKey {
|
|
||||||
return i.iterKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// lazyValue returns a value pointer to the value at the current iterator
|
|
||||||
// position. Behaviour undefined if unsafeKey() returns a Range key or Rangedel
|
|
||||||
// kind key.
|
|
||||||
func (i *scanInternalIterator) lazyValue() LazyValue {
|
|
||||||
return i.iterValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafeRangeDel returns a range key span. Behaviour undefined if UnsafeKey returns
|
|
||||||
// a non-rangedel kind.
|
|
||||||
func (i *scanInternalIterator) unsafeRangeDel() *keyspan.Span {
|
|
||||||
return i.pointKeyIter.Span()
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafeSpan returns a range key span. Behaviour undefined if UnsafeKey returns
|
|
||||||
// a non-rangekey type.
|
|
||||||
func (i *scanInternalIterator) unsafeSpan() *keyspan.Span {
|
|
||||||
return i.rangeKey.iiter.Span()
|
|
||||||
}
|
|
||||||
|
|
||||||
// next advances the iterator in the forward direction, and returns the
|
|
||||||
// iterator's new validity state.
|
|
||||||
func (i *scanInternalIterator) next() bool {
|
|
||||||
i.iterKey, i.iterValue = i.iter.Next()
|
|
||||||
return i.iterKey != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// error returns an error from the internal iterator, if there's any.
|
|
||||||
func (i *scanInternalIterator) error() error {
|
|
||||||
return i.iter.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// close closes this iterator, and releases any pooled objects.
|
|
||||||
func (i *scanInternalIterator) close() error {
|
|
||||||
if err := i.iter.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.readState.unref()
|
|
||||||
if i.rangeKey != nil {
|
|
||||||
i.rangeKey.PrepareForReuse()
|
|
||||||
*i.rangeKey = iteratorRangeKeyState{
|
|
||||||
rangeKeyBuffers: i.rangeKey.rangeKeyBuffers,
|
|
||||||
}
|
|
||||||
iterRangeKeyStateAllocPool.Put(i.rangeKey)
|
|
||||||
i.rangeKey = nil
|
|
||||||
}
|
|
||||||
if alloc := i.alloc; alloc != nil {
|
|
||||||
for j := range i.boundsBuf {
|
|
||||||
if cap(i.boundsBuf[j]) >= maxKeyBufCacheSize {
|
|
||||||
alloc.boundsBuf[j] = nil
|
|
||||||
} else {
|
|
||||||
alloc.boundsBuf[j] = i.boundsBuf[j]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*alloc = iterAlloc{
|
|
||||||
keyBuf: alloc.keyBuf[:0],
|
|
||||||
boundsBuf: alloc.boundsBuf,
|
|
||||||
prefixOrFullSeekKey: alloc.prefixOrFullSeekKey[:0],
|
|
||||||
}
|
|
||||||
iterAllocPool.Put(alloc)
|
|
||||||
i.alloc = nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *scanInternalIterator) initializeBoundBufs(lower, upper []byte) {
|
|
||||||
buf := i.boundsBuf[i.boundsBufIdx][:0]
|
|
||||||
if lower != nil {
|
|
||||||
buf = append(buf, lower...)
|
|
||||||
i.opts.LowerBound = buf
|
|
||||||
} else {
|
|
||||||
i.opts.LowerBound = nil
|
|
||||||
}
|
|
||||||
if upper != nil {
|
|
||||||
buf = append(buf, upper...)
|
|
||||||
i.opts.UpperBound = buf[len(buf)-len(upper):]
|
|
||||||
} else {
|
|
||||||
i.opts.UpperBound = nil
|
|
||||||
}
|
|
||||||
i.boundsBuf[i.boundsBufIdx] = buf
|
|
||||||
i.boundsBufIdx = 1 - i.boundsBufIdx
|
|
||||||
}
|
|
||||||
369
vendor/github.com/cockroachdb/pebble/iterator.go
generated
vendored
369
vendor/github.com/cockroachdb/pebble/iterator.go
generated
vendored
@@ -9,7 +9,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
@@ -188,7 +187,9 @@ type Iterator struct {
|
|||||||
comparer base.Comparer
|
comparer base.Comparer
|
||||||
iter internalIterator
|
iter internalIterator
|
||||||
pointIter internalIterator
|
pointIter internalIterator
|
||||||
|
// Either readState or version is set, but not both.
|
||||||
readState *readState
|
readState *readState
|
||||||
|
version *version
|
||||||
// rangeKey holds iteration state specific to iteration over range keys.
|
// rangeKey holds iteration state specific to iteration over range keys.
|
||||||
// The range key field may be nil if the Iterator has never been configured
|
// 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
|
// to iterate over range keys. Its non-nilness cannot be used to determine
|
||||||
@@ -446,6 +447,11 @@ const (
|
|||||||
seekPrefixGELastPositioningOp
|
seekPrefixGELastPositioningOp
|
||||||
seekGELastPositioningOp
|
seekGELastPositioningOp
|
||||||
seekLTLastPositioningOp
|
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
|
// invalidatedLastPositionOp is similar to unknownLastPositionOp and the
|
||||||
// only reason to distinguish this is for the wider set of SeekGE
|
// only reason to distinguish this is for the wider set of SeekGE
|
||||||
// optimizations we permit for the external iterator Iterator.forwardOnly
|
// optimizations we permit for the external iterator Iterator.forwardOnly
|
||||||
@@ -549,6 +555,21 @@ func (i *Iterator) findNextEntry(limit []byte) {
|
|||||||
return
|
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() {
|
switch key.Kind() {
|
||||||
case InternalKeyKindRangeKeySet:
|
case InternalKeyKindRangeKeySet:
|
||||||
// Save the current key.
|
// Save the current key.
|
||||||
@@ -569,7 +590,10 @@ func (i *Iterator) findNextEntry(limit []byte) {
|
|||||||
i.iterValidityState = IterValid
|
i.iterValidityState = IterValid
|
||||||
return
|
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()
|
i.nextUserKey()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -615,6 +639,13 @@ func (i *Iterator) findNextEntry(limit []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Iterator) nextPointCurrentUserKey() bool {
|
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.pos = iterPosCurForward
|
||||||
|
|
||||||
i.iterKey, i.iterValue = i.iter.Next()
|
i.iterKey, i.iterValue = i.iter.Next()
|
||||||
@@ -632,7 +663,10 @@ func (i *Iterator) nextPointCurrentUserKey() bool {
|
|||||||
i.err = base.CorruptionErrorf("pebble: unexpected range key set mid-user key")
|
i.err = base.CorruptionErrorf("pebble: unexpected range key set mid-user key")
|
||||||
return false
|
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
|
return false
|
||||||
|
|
||||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||||
@@ -773,52 +807,54 @@ func (i *Iterator) maybeSampleRead() {
|
|||||||
func (i *Iterator) sampleRead() {
|
func (i *Iterator) sampleRead() {
|
||||||
var topFile *manifest.FileMetadata
|
var topFile *manifest.FileMetadata
|
||||||
topLevel, numOverlappingLevels := numLevels, 0
|
topLevel, numOverlappingLevels := numLevels, 0
|
||||||
if mi, ok := i.iter.(*mergingIter); ok {
|
mi := i.merging
|
||||||
if len(mi.levels) > 1 {
|
if mi == nil {
|
||||||
mi.ForEachLevelIter(func(li *levelIter) bool {
|
return
|
||||||
l := manifest.LevelToInt(li.level)
|
}
|
||||||
if f := li.iterFile; f != nil {
|
if len(mi.levels) > 1 {
|
||||||
var containsKey bool
|
mi.ForEachLevelIter(func(li *levelIter) bool {
|
||||||
if i.pos == iterPosNext || i.pos == iterPosCurForward ||
|
l := manifest.LevelToInt(li.level)
|
||||||
i.pos == iterPosCurForwardPaused {
|
if f := li.iterFile; f != nil {
|
||||||
containsKey = i.cmp(f.SmallestPointKey.UserKey, i.key) <= 0
|
var containsKey bool
|
||||||
} else if i.pos == iterPosPrev || i.pos == iterPosCurReverse ||
|
if i.pos == iterPosNext || i.pos == iterPosCurForward ||
|
||||||
i.pos == iterPosCurReversePaused {
|
i.pos == iterPosCurForwardPaused {
|
||||||
containsKey = i.cmp(f.LargestPointKey.UserKey, i.key) >= 0
|
containsKey = i.cmp(f.SmallestPointKey.UserKey, i.key) <= 0
|
||||||
}
|
} else if i.pos == iterPosPrev || i.pos == iterPosCurReverse ||
|
||||||
// Do nothing if the current key is not contained in f's
|
i.pos == iterPosCurReversePaused {
|
||||||
// bounds. We could seek the LevelIterator at this level
|
containsKey = i.cmp(f.LargestPointKey.UserKey, i.key) >= 0
|
||||||
// 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
|
// 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 {
|
if topFile == nil || topLevel >= numLevels {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if numOverlappingLevels >= 2 {
|
if numOverlappingLevels >= 2 {
|
||||||
allowedSeeks := atomic.AddInt64(&topFile.Atomic.AllowedSeeks, -1)
|
allowedSeeks := topFile.AllowedSeeks.Add(-1)
|
||||||
if allowedSeeks == 0 {
|
if allowedSeeks == 0 {
|
||||||
|
|
||||||
// Since the compaction queue can handle duplicates, we can keep
|
// Since the compaction queue can handle duplicates, we can keep
|
||||||
// adding to the queue even once allowedSeeks hits 0.
|
// adding to the queue even once allowedSeeks hits 0.
|
||||||
// In fact, we NEED to keep adding to the queue, because the queue
|
// In fact, we NEED to keep adding to the queue, because the queue
|
||||||
// is small and evicts older and possibly useful compactions.
|
// is small and evicts older and possibly useful compactions.
|
||||||
atomic.AddInt64(&topFile.Atomic.AllowedSeeks, topFile.InitAllowedSeeks)
|
topFile.AllowedSeeks.Add(topFile.InitAllowedSeeks)
|
||||||
|
|
||||||
read := readCompaction{
|
read := readCompaction{
|
||||||
start: topFile.SmallestPointKey.UserKey,
|
start: topFile.SmallestPointKey.UserKey,
|
||||||
@@ -902,6 +938,26 @@ func (i *Iterator) findPrevEntry(limit []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the user has configured a SkipPoint function, invoke it to see
|
||||||
|
// whether we should skip over the current user key.
|
||||||
|
if i.opts.SkipPoint != nil && key.Kind() != InternalKeyKindRangeKeySet && i.opts.SkipPoint(key.UserKey) {
|
||||||
|
// NB: We could call prevUserKey, but in some cases the SkipPoint
|
||||||
|
// predicate function might be cheaper than prevUserKey's key copy
|
||||||
|
// and key comparison. This should be the case for MVCC suffix
|
||||||
|
// comparisons, for example. In the future, we could expand the
|
||||||
|
// SkipPoint interface to give the implementor more control over
|
||||||
|
// whether we skip over just the internal key, the user key, or even
|
||||||
|
// the key prefix.
|
||||||
|
i.stats.ReverseStepCount[InternalIterCall]++
|
||||||
|
i.iterKey, i.iterValue = i.iter.Prev()
|
||||||
|
if limit != nil && i.iterKey != nil && i.cmp(limit, i.iterKey.UserKey) > 0 && !i.rangeKeyWithinLimit(limit) {
|
||||||
|
i.iterValidityState = IterAtLimit
|
||||||
|
i.pos = iterPosCurReversePaused
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch key.Kind() {
|
switch key.Kind() {
|
||||||
case InternalKeyKindRangeKeySet:
|
case InternalKeyKindRangeKeySet:
|
||||||
// Range key start boundary markers are interleaved with the maximum
|
// Range key start boundary markers are interleaved with the maximum
|
||||||
@@ -930,7 +986,7 @@ func (i *Iterator) findPrevEntry(limit []byte) {
|
|||||||
// return the key even if the MERGE point key is deleted.
|
// return the key even if the MERGE point key is deleted.
|
||||||
rangeKeyBoundary = true
|
rangeKeyBoundary = true
|
||||||
|
|
||||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||||
i.value = LazyValue{}
|
i.value = LazyValue{}
|
||||||
i.iterValidityState = IterExhausted
|
i.iterValidityState = IterExhausted
|
||||||
valueMerger = nil
|
valueMerger = nil
|
||||||
@@ -939,12 +995,12 @@ func (i *Iterator) findPrevEntry(limit []byte) {
|
|||||||
// Compare with the limit. We could optimize by only checking when
|
// Compare with the limit. We could optimize by only checking when
|
||||||
// we step to the previous user key, but detecting that requires a
|
// we step to the previous user key, but detecting that requires a
|
||||||
// comparison too. Note that this position may already passed a
|
// comparison too. Note that this position may already passed a
|
||||||
// number of versions of this user key, but they are all deleted,
|
// number of versions of this user key, but they are all deleted, so
|
||||||
// so the fact that a subsequent Prev*() call will not see them is
|
// the fact that a subsequent Prev*() call will not see them is
|
||||||
// harmless. Also note that this is the only place in the loop,
|
// harmless. Also note that this is the only place in the loop,
|
||||||
// other than the firstLoopIter case above, where we could step
|
// other than the firstLoopIter and SkipPoint cases above, where we
|
||||||
// to a different user key and start processing it for returning
|
// could step to a different user key and start processing it for
|
||||||
// to the caller.
|
// returning to the caller.
|
||||||
if limit != nil && i.iterKey != nil && i.cmp(limit, i.iterKey.UserKey) > 0 && !i.rangeKeyWithinLimit(limit) {
|
if limit != nil && i.iterKey != nil && i.cmp(limit, i.iterKey.UserKey) > 0 && !i.rangeKeyWithinLimit(limit) {
|
||||||
i.iterValidityState = IterAtLimit
|
i.iterValidityState = IterAtLimit
|
||||||
i.pos = iterPosCurReversePaused
|
i.pos = iterPosCurReversePaused
|
||||||
@@ -1093,9 +1149,13 @@ func (i *Iterator) mergeNext(key InternalKey, valueMerger ValueMerger) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch key.Kind() {
|
switch key.Kind() {
|
||||||
case InternalKeyKindDelete, InternalKeyKindSingleDelete:
|
case InternalKeyKindDelete, InternalKeyKindSingleDelete, InternalKeyKindDeleteSized:
|
||||||
// We've hit a deletion tombstone. Return everything up to this
|
// We've hit a deletion tombstone. Return everything up to this
|
||||||
// point.
|
// point.
|
||||||
|
//
|
||||||
|
// NB: treating InternalKeyKindSingleDelete as equivalent to DEL is not
|
||||||
|
// only simpler, but is also necessary for correctness due to
|
||||||
|
// InternalKeyKindSSTableInternalObsoleteBit.
|
||||||
return
|
return
|
||||||
|
|
||||||
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
case InternalKeyKindSet, InternalKeyKindSetWithDelete:
|
||||||
@@ -1751,7 +1811,8 @@ func (i *Iterator) internalNextPrefix(currKeyPrefixLen int) {
|
|||||||
i.iterKey, i.iterValue = i.iter.NextPrefix(i.prefixOrFullSeekKey)
|
i.iterKey, i.iterValue = i.iter.NextPrefix(i.prefixOrFullSeekKey)
|
||||||
if invariants.Enabled && i.iterKey != nil {
|
if invariants.Enabled && i.iterKey != nil {
|
||||||
if iterKeyPrefixLen := i.split(i.iterKey.UserKey); i.cmp(i.iterKey.UserKey[:iterKeyPrefixLen], i.prefixOrFullSeekKey) < 0 {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2120,6 +2181,9 @@ func (i *Iterator) Value() []byte {
|
|||||||
|
|
||||||
// ValueAndErr returns the value, and any error encountered in extracting the value.
|
// ValueAndErr returns the value, and any error encountered in extracting the value.
|
||||||
// REQUIRES: i.Error()==nil and HasPointAndRange() returns true for hasPoint.
|
// 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) {
|
func (i *Iterator) ValueAndErr() ([]byte, error) {
|
||||||
val, callerOwned, err := i.value.Value(i.lazyValueBuf)
|
val, callerOwned, err := i.value.Value(i.lazyValueBuf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2150,7 +2214,13 @@ func (i *Iterator) RangeKeys() []RangeKeyData {
|
|||||||
// Valid returns true if the iterator is positioned at a valid key/value pair
|
// Valid returns true if the iterator is positioned at a valid key/value pair
|
||||||
// and false otherwise.
|
// and false otherwise.
|
||||||
func (i *Iterator) Valid() bool {
|
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.
|
// Error returns any accumulated error.
|
||||||
@@ -2215,6 +2285,10 @@ func (i *Iterator) Close() error {
|
|||||||
i.readState = nil
|
i.readState = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if i.version != nil {
|
||||||
|
i.version.Unref()
|
||||||
|
}
|
||||||
|
|
||||||
for _, readers := range i.externalReaders {
|
for _, readers := range i.externalReaders {
|
||||||
for _, r := range readers {
|
for _, r := range readers {
|
||||||
err = firstError(err, r.Close())
|
err = firstError(err, r.Close())
|
||||||
@@ -2407,7 +2481,8 @@ func (i *Iterator) SetOptions(o *IterOptions) {
|
|||||||
// If either options specify block property filters for an iterator stack,
|
// If either options specify block property filters for an iterator stack,
|
||||||
// reconstruct it.
|
// reconstruct it.
|
||||||
if i.pointIter != nil && (closeBoth || len(o.PointKeyFilters) > 0 || len(i.opts.PointKeyFilters) > 0 ||
|
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.err = firstError(i.err, i.pointIter.Close())
|
||||||
i.pointIter = nil
|
i.pointIter = nil
|
||||||
}
|
}
|
||||||
@@ -2646,11 +2721,22 @@ func (i *Iterator) CloneWithContext(ctx context.Context, opts CloneOptions) (*It
|
|||||||
}
|
}
|
||||||
|
|
||||||
readState := i.readState
|
readState := i.readState
|
||||||
if readState == nil {
|
vers := i.version
|
||||||
|
if readState == nil && vers == nil {
|
||||||
return nil, errors.Errorf("cannot Clone a closed Iterator")
|
return nil, errors.Errorf("cannot Clone a closed Iterator")
|
||||||
}
|
}
|
||||||
// i is already holding a ref, so there is no race with unref here.
|
// 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
|
// Bundle various structures under a single umbrella in order to allocate
|
||||||
// them together.
|
// them together.
|
||||||
buf := iterAllocPool.Get().(*iterAlloc)
|
buf := iterAllocPool.Get().(*iterAlloc)
|
||||||
@@ -2662,6 +2748,7 @@ func (i *Iterator) CloneWithContext(ctx context.Context, opts CloneOptions) (*It
|
|||||||
merge: i.merge,
|
merge: i.merge,
|
||||||
comparer: i.comparer,
|
comparer: i.comparer,
|
||||||
readState: readState,
|
readState: readState,
|
||||||
|
version: vers,
|
||||||
keyBuf: buf.keyBuf,
|
keyBuf: buf.keyBuf,
|
||||||
prefixOrFullSeekKey: buf.prefixOrFullSeekKey,
|
prefixOrFullSeekKey: buf.prefixOrFullSeekKey,
|
||||||
boundsBuf: buf.boundsBuf,
|
boundsBuf: buf.boundsBuf,
|
||||||
@@ -2716,19 +2803,19 @@ func (stats *IteratorStats) SafeFormat(s redact.SafePrinter, verb rune) {
|
|||||||
s.SafeString(",\n(internal-stats: ")
|
s.SafeString(",\n(internal-stats: ")
|
||||||
s.Printf("(block-bytes: (total %s, cached %s, read-time %s)), "+
|
s.Printf("(block-bytes: (total %s, cached %s, read-time %s)), "+
|
||||||
"(points: (count %s, key-bytes %s, value-bytes %s, tombstoned %s))",
|
"(points: (count %s, key-bytes %s, value-bytes %s, tombstoned %s))",
|
||||||
humanize.IEC.Uint64(stats.InternalStats.BlockBytes),
|
humanize.Bytes.Uint64(stats.InternalStats.BlockBytes),
|
||||||
humanize.IEC.Uint64(stats.InternalStats.BlockBytesInCache),
|
humanize.Bytes.Uint64(stats.InternalStats.BlockBytesInCache),
|
||||||
humanize.FormattedString(stats.InternalStats.BlockReadDuration.String()),
|
humanize.FormattedString(stats.InternalStats.BlockReadDuration.String()),
|
||||||
humanize.SI.Uint64(stats.InternalStats.PointCount),
|
humanize.Count.Uint64(stats.InternalStats.PointCount),
|
||||||
humanize.SI.Uint64(stats.InternalStats.KeyBytes),
|
humanize.Bytes.Uint64(stats.InternalStats.KeyBytes),
|
||||||
humanize.SI.Uint64(stats.InternalStats.ValueBytes),
|
humanize.Bytes.Uint64(stats.InternalStats.ValueBytes),
|
||||||
humanize.SI.Uint64(stats.InternalStats.PointsCoveredByRangeTombstones),
|
humanize.Count.Uint64(stats.InternalStats.PointsCoveredByRangeTombstones),
|
||||||
)
|
)
|
||||||
if stats.InternalStats.SeparatedPointValue.Count != 0 {
|
if stats.InternalStats.SeparatedPointValue.Count != 0 {
|
||||||
s.Printf(", (separated: (count %s, bytes %s, fetched %s)))",
|
s.Printf(", (separated: (count %s, bytes %s, fetched %s)))",
|
||||||
humanize.SI.Uint64(stats.InternalStats.SeparatedPointValue.Count),
|
humanize.Count.Uint64(stats.InternalStats.SeparatedPointValue.Count),
|
||||||
humanize.IEC.Uint64(stats.InternalStats.SeparatedPointValue.ValueBytes),
|
humanize.Bytes.Uint64(stats.InternalStats.SeparatedPointValue.ValueBytes),
|
||||||
humanize.IEC.Uint64(stats.InternalStats.SeparatedPointValue.ValueBytesFetched))
|
humanize.Bytes.Uint64(stats.InternalStats.SeparatedPointValue.ValueBytesFetched))
|
||||||
} else {
|
} else {
|
||||||
s.Printf(")")
|
s.Printf(")")
|
||||||
}
|
}
|
||||||
@@ -2741,3 +2828,169 @@ func (stats *IteratorStats) SafeFormat(s redact.SafePrinter, verb rune) {
|
|||||||
stats.RangeKeyStats.SkippedPoints)
|
stats.RangeKeyStats.SkippedPoints)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanDeterministicallySingleDelete takes a valid iterator and examines internal
|
||||||
|
// state to determine if a SingleDelete deleting Iterator.Key() would
|
||||||
|
// deterministically delete the key. CanDeterministicallySingleDelete requires
|
||||||
|
// the iterator to be oriented in the forward direction (eg, the last
|
||||||
|
// positioning operation must've been a First, a Seek[Prefix]GE, or a
|
||||||
|
// Next[Prefix][WithLimit]).
|
||||||
|
//
|
||||||
|
// This function does not change the external position of the iterator, and all
|
||||||
|
// positioning methods should behave the same as if it was never called. This
|
||||||
|
// function will only return a meaningful result the first time it's invoked at
|
||||||
|
// an iterator position. This function invalidates the iterator Value's memory,
|
||||||
|
// and the caller must not rely on the memory safety of the previous Iterator
|
||||||
|
// position.
|
||||||
|
//
|
||||||
|
// If CanDeterministicallySingleDelete returns true AND the key at the iterator
|
||||||
|
// position is not modified between the creation of the Iterator and the commit
|
||||||
|
// of a batch containing a SingleDelete over the key, then the caller can be
|
||||||
|
// assured that SingleDelete is equivalent to Delete on the local engine, but it
|
||||||
|
// may not be true on another engine that received the same writes and with
|
||||||
|
// logically equivalent state since this engine may have collapsed multiple SETs
|
||||||
|
// into one.
|
||||||
|
func CanDeterministicallySingleDelete(it *Iterator) (bool, error) {
|
||||||
|
// This function may only be called once per external iterator position. We
|
||||||
|
// can validate this by checking the last positioning operation.
|
||||||
|
if it.lastPositioningOp == internalNextOp {
|
||||||
|
return false, errors.New("pebble: CanDeterministicallySingleDelete called twice")
|
||||||
|
}
|
||||||
|
validity, kind := it.internalNext()
|
||||||
|
var shadowedBySingleDelete bool
|
||||||
|
for validity == internalNextValid {
|
||||||
|
switch kind {
|
||||||
|
case InternalKeyKindDelete, InternalKeyKindDeleteSized:
|
||||||
|
// A DEL or DELSIZED tombstone is okay. An internal key
|
||||||
|
// sequence like SINGLEDEL; SET; DEL; SET can be handled
|
||||||
|
// deterministically. If there are SETs further down, we
|
||||||
|
// don't care about them.
|
||||||
|
return true, nil
|
||||||
|
case InternalKeyKindSingleDelete:
|
||||||
|
// A SingleDelete is okay as long as when that SingleDelete was
|
||||||
|
// written, it was written deterministically (eg, with its own
|
||||||
|
// CanDeterministicallySingleDelete check). Validate that it was
|
||||||
|
// written deterministically. We'll allow one set to appear after
|
||||||
|
// the SingleDelete.
|
||||||
|
shadowedBySingleDelete = true
|
||||||
|
validity, kind = it.internalNext()
|
||||||
|
continue
|
||||||
|
case InternalKeyKindSet, InternalKeyKindSetWithDelete, InternalKeyKindMerge:
|
||||||
|
// If we observed a single delete, it's allowed to delete 1 key.
|
||||||
|
// We'll keep looping to validate that the internal keys beneath the
|
||||||
|
// already-written single delete are copacetic.
|
||||||
|
if shadowedBySingleDelete {
|
||||||
|
shadowedBySingleDelete = false
|
||||||
|
validity, kind = it.internalNext()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// We encountered a shadowed SET, SETWITHDEL, MERGE. A SINGLEDEL
|
||||||
|
// that deleted the KV at the original iterator position could
|
||||||
|
// result in this key becoming visible.
|
||||||
|
return false, nil
|
||||||
|
case InternalKeyKindRangeDelete:
|
||||||
|
// RangeDeletes are handled by the merging iterator and should never
|
||||||
|
// be observed by the top-level Iterator.
|
||||||
|
panic(errors.AssertionFailedf("pebble: unexpected range delete"))
|
||||||
|
case InternalKeyKindRangeKeySet, InternalKeyKindRangeKeyUnset, InternalKeyKindRangeKeyDelete:
|
||||||
|
// Range keys are interleaved at the maximal sequence number and
|
||||||
|
// should never be observed within a user key.
|
||||||
|
panic(errors.AssertionFailedf("pebble: unexpected range key"))
|
||||||
|
default:
|
||||||
|
panic(errors.AssertionFailedf("pebble: unexpected key kind: %s", errors.Safe(kind)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if validity == internalNextError {
|
||||||
|
return false, it.Error()
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// internalNextValidity enumerates the potential outcomes of a call to
|
||||||
|
// internalNext.
|
||||||
|
type internalNextValidity int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// internalNextError is returned by internalNext when an error occurred and
|
||||||
|
// the caller is responsible for checking iter.Error().
|
||||||
|
internalNextError internalNextValidity = iota
|
||||||
|
// internalNextExhausted is returned by internalNext when the next internal
|
||||||
|
// key is an internal key with a different user key than Iterator.Key().
|
||||||
|
internalNextExhausted
|
||||||
|
// internalNextValid is returned by internalNext when the internal next
|
||||||
|
// found a shadowed internal key with a user key equal to Iterator.Key().
|
||||||
|
internalNextValid
|
||||||
|
)
|
||||||
|
|
||||||
|
// internalNext advances internal Iterator state forward to expose the
|
||||||
|
// InternalKeyKind of the next internal key with a user key equal to Key().
|
||||||
|
//
|
||||||
|
// internalNext is a highly specialized operation and is unlikely to be
|
||||||
|
// generally useful. See Iterator.Next for how to reposition the iterator to the
|
||||||
|
// next key. internalNext requires the Iterator to be at a valid position in the
|
||||||
|
// forward direction (the last positioning operation must've been a First, a
|
||||||
|
// Seek[Prefix]GE, or a Next[Prefix][WithLimit] and Valid() must return true).
|
||||||
|
//
|
||||||
|
// internalNext, unlike all other Iterator methods, exposes internal LSM state.
|
||||||
|
// internalNext advances the Iterator's internal iterator to the next shadowed
|
||||||
|
// key with a user key equal to Key(). When a key is overwritten or deleted, its
|
||||||
|
// removal from the LSM occurs lazily as a part of compactions. internalNext
|
||||||
|
// allows the caller to see whether an obsolete internal key exists with the
|
||||||
|
// current Key(), and what it's key kind is. Note that the existence of an
|
||||||
|
// internal key is nondeterministic and dependent on internal LSM state. These
|
||||||
|
// semantics are unlikely to be applicable to almost all use cases.
|
||||||
|
//
|
||||||
|
// If internalNext finds a key that shares the same user key as Key(), it
|
||||||
|
// returns internalNextValid and the internal key's kind. If internalNext
|
||||||
|
// encounters an error, it returns internalNextError and the caller is expected
|
||||||
|
// to call Iterator.Error() to retrieve it. In all other circumstances,
|
||||||
|
// internalNext returns internalNextExhausted, indicating that there are no more
|
||||||
|
// additional internal keys with the user key Key().
|
||||||
|
//
|
||||||
|
// internalNext does not change the external position of the iterator, and a
|
||||||
|
// Next operation should behave the same as if internalNext was never called.
|
||||||
|
// internalNext does invalidate the iterator Value's memory, and the caller must
|
||||||
|
// not rely on the memory safety of the previous Iterator position.
|
||||||
|
func (i *Iterator) internalNext() (internalNextValidity, base.InternalKeyKind) {
|
||||||
|
i.stats.ForwardStepCount[InterfaceCall]++
|
||||||
|
if i.err != nil {
|
||||||
|
return internalNextError, base.InternalKeyKindInvalid
|
||||||
|
} else if i.iterValidityState != IterValid {
|
||||||
|
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||||
|
}
|
||||||
|
i.lastPositioningOp = internalNextOp
|
||||||
|
|
||||||
|
switch i.pos {
|
||||||
|
case iterPosCurForward:
|
||||||
|
i.iterKey, i.iterValue = i.iter.Next()
|
||||||
|
if i.iterKey == nil {
|
||||||
|
// We check i.iter.Error() here and return an internalNextError enum
|
||||||
|
// variant so that the caller does not need to check i.iter.Error()
|
||||||
|
// in the common case that the next internal key has a new user key.
|
||||||
|
if i.err = i.iter.Error(); i.err != nil {
|
||||||
|
return internalNextError, base.InternalKeyKindInvalid
|
||||||
|
}
|
||||||
|
i.pos = iterPosNext
|
||||||
|
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||||
|
} else if i.comparer.Equal(i.iterKey.UserKey, i.key) {
|
||||||
|
return internalNextValid, i.iterKey.Kind()
|
||||||
|
}
|
||||||
|
i.pos = iterPosNext
|
||||||
|
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||||
|
case iterPosCurReverse, iterPosCurReversePaused, iterPosPrev:
|
||||||
|
i.err = errors.New("switching from reverse to forward via internalNext is prohibited")
|
||||||
|
i.iterValidityState = IterExhausted
|
||||||
|
return internalNextError, base.InternalKeyKindInvalid
|
||||||
|
case iterPosNext, iterPosCurForwardPaused:
|
||||||
|
// The previous method already moved onto the next user key. This is
|
||||||
|
// only possible if
|
||||||
|
// - the last positioning method was a call to internalNext, and we
|
||||||
|
// advanced to a new user key.
|
||||||
|
// - the previous non-internalNext iterator operation encountered a
|
||||||
|
// range key or merge, forcing an internal Next that found a new
|
||||||
|
// user key that's not equal to i.Iterator.Key().
|
||||||
|
return internalNextExhausted, base.InternalKeyKindInvalid
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
18
vendor/github.com/cockroachdb/pebble/level_checker.go
generated
vendored
18
vendor/github.com/cockroachdb/pebble/level_checker.go
generated
vendored
@@ -9,7 +9,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
@@ -166,7 +165,7 @@ func (m *simpleMergingIter) step() bool {
|
|||||||
if m.valueMerger != nil {
|
if m.valueMerger != nil {
|
||||||
// Ongoing series of MERGE records.
|
// Ongoing series of MERGE records.
|
||||||
switch item.key.Kind() {
|
switch item.key.Kind() {
|
||||||
case InternalKeyKindSingleDelete, InternalKeyKindDelete:
|
case InternalKeyKindSingleDelete, InternalKeyKindDelete, InternalKeyKindDeleteSized:
|
||||||
var closer io.Closer
|
var closer io.Closer
|
||||||
_, closer, m.err = m.valueMerger.Finish(true /* includesBase */)
|
_, closer, m.err = m.valueMerger.Finish(true /* includesBase */)
|
||||||
if m.err == nil && closer != nil {
|
if m.err == nil && closer != nil {
|
||||||
@@ -346,7 +345,7 @@ func iterateAndCheckTombstones(
|
|||||||
|
|
||||||
type checkConfig struct {
|
type checkConfig struct {
|
||||||
logger Logger
|
logger Logger
|
||||||
cmp Compare
|
comparer *Comparer
|
||||||
readState *readState
|
readState *readState
|
||||||
newIters tableNewIters
|
newIters tableNewIters
|
||||||
seqNum uint64
|
seqNum uint64
|
||||||
@@ -355,6 +354,9 @@ type checkConfig struct {
|
|||||||
formatKey base.FormatKey
|
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 {
|
func checkRangeTombstones(c *checkConfig) error {
|
||||||
var level int
|
var level int
|
||||||
var tombstones []tombstoneWithLevel
|
var tombstones []tombstoneWithLevel
|
||||||
@@ -380,7 +382,7 @@ func checkRangeTombstones(c *checkConfig) error {
|
|||||||
atomicUnit, _ := expandToAtomicUnit(c.cmp, lf.Slice(), true /* disableIsCompacting */)
|
atomicUnit, _ := expandToAtomicUnit(c.cmp, lf.Slice(), true /* disableIsCompacting */)
|
||||||
lower, upper := manifest.KeyRange(c.cmp, atomicUnit.Iter())
|
lower, upper := manifest.KeyRange(c.cmp, atomicUnit.Iter())
|
||||||
iterToClose, iter, err := c.newIters(
|
iterToClose, iter, err := c.newIters(
|
||||||
context.Background(), lf.FileMetadata, nil, internalIterOpts{})
|
context.Background(), lf.FileMetadata, &IterOptions{level: manifest.Level(lsmLevel)}, internalIterOpts{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -568,11 +570,11 @@ func (d *DB) CheckLevels(stats *CheckLevelsStats) error {
|
|||||||
|
|
||||||
// Determine the seqnum to read at after grabbing the read state (current and
|
// Determine the seqnum to read at after grabbing the read state (current and
|
||||||
// memtables) above.
|
// memtables) above.
|
||||||
seqNum := atomic.LoadUint64(&d.mu.versions.atomic.visibleSeqNum)
|
seqNum := d.mu.versions.visibleSeqNum.Load()
|
||||||
|
|
||||||
checkConfig := &checkConfig{
|
checkConfig := &checkConfig{
|
||||||
logger: d.opts.Logger,
|
logger: d.opts.Logger,
|
||||||
cmp: d.cmp,
|
comparer: d.opts.Comparer,
|
||||||
readState: readState,
|
readState: readState,
|
||||||
newIters: d.newIters,
|
newIters: d.newIters,
|
||||||
seqNum: seqNum,
|
seqNum: seqNum,
|
||||||
@@ -640,7 +642,7 @@ func checkLevelsInternal(c *checkConfig) (err error) {
|
|||||||
manifestIter := current.L0SublevelFiles[sublevel].Iter()
|
manifestIter := current.L0SublevelFiles[sublevel].Iter()
|
||||||
iterOpts := IterOptions{logger: c.logger}
|
iterOpts := IterOptions{logger: c.logger}
|
||||||
li := &levelIter{}
|
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{})
|
manifest.L0Sublevel(sublevel), internalIterOpts{})
|
||||||
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
|
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
|
||||||
li.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
|
li.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
|
||||||
@@ -654,7 +656,7 @@ func checkLevelsInternal(c *checkConfig) (err error) {
|
|||||||
|
|
||||||
iterOpts := IterOptions{logger: c.logger}
|
iterOpts := IterOptions{logger: c.logger}
|
||||||
li := &levelIter{}
|
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{})
|
current.Levels[level].Iter(), manifest.Level(level), internalIterOpts{})
|
||||||
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
|
li.initRangeDel(&mlevelAlloc[0].rangeDelIter)
|
||||||
li.initBoundaryContext(&mlevelAlloc[0].levelIterBoundaryContext)
|
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
|
// tableNewRangeDelIter takes a tableNewIters and returns a TableNewSpanIter
|
||||||
// for the rangedel iterator returned by tableNewIters.
|
// for the rangedel iterator returned by tableNewIters.
|
||||||
func tableNewRangeDelIter(ctx context.Context, newIters tableNewIters) keyspan.TableNewSpanIter {
|
func tableNewRangeDelIter(ctx context.Context, newIters tableNewIters) keyspan.TableNewSpanIter {
|
||||||
return func(file *manifest.FileMetadata, iterOptions *keyspan.SpanIterOptions) (keyspan.FragmentIterator, error) {
|
return func(file *manifest.FileMetadata, iterOptions keyspan.SpanIterOptions) (keyspan.FragmentIterator, error) {
|
||||||
iter, rangeDelIter, err := newIters(
|
iter, rangeDelIter, err := newIters(ctx, file, nil, internalIterOpts{})
|
||||||
ctx, file, &IterOptions{RangeKeyFilters: iterOptions.RangeKeyFilters}, internalIterOpts{})
|
|
||||||
if iter != nil {
|
if iter != nil {
|
||||||
_ = iter.Close()
|
_ = iter.Close()
|
||||||
}
|
}
|
||||||
@@ -51,6 +50,7 @@ func tableNewRangeDelIter(ctx context.Context, newIters tableNewIters) keyspan.T
|
|||||||
|
|
||||||
type internalIterOpts struct {
|
type internalIterOpts struct {
|
||||||
bytesIterated *uint64
|
bytesIterated *uint64
|
||||||
|
bufferPool *sstable.BufferPool
|
||||||
stats *base.InternalIteratorStats
|
stats *base.InternalIteratorStats
|
||||||
boundLimitedFilter sstable.BoundLimitedBlockPropertyFilter
|
boundLimitedFilter sstable.BoundLimitedBlockPropertyFilter
|
||||||
}
|
}
|
||||||
@@ -82,10 +82,11 @@ type levelIter struct {
|
|||||||
// short-lived (since they pin sstables), (b) plumbing a context into every
|
// short-lived (since they pin sstables), (b) plumbing a context into every
|
||||||
// method is very painful, (c) they do not (yet) respect context
|
// method is very painful, (c) they do not (yet) respect context
|
||||||
// cancellation and are only used for tracing.
|
// cancellation and are only used for tracing.
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger Logger
|
logger Logger
|
||||||
cmp Compare
|
comparer *Comparer
|
||||||
split Split
|
cmp Compare
|
||||||
|
split Split
|
||||||
// The lower/upper bounds for iteration as specified at creation or the most
|
// The lower/upper bounds for iteration as specified at creation or the most
|
||||||
// recent call to SetBounds.
|
// recent call to SetBounds.
|
||||||
lower []byte
|
lower []byte
|
||||||
@@ -200,6 +201,11 @@ type levelIter struct {
|
|||||||
// cache when constructing new table iterators.
|
// cache when constructing new table iterators.
|
||||||
internalOpts internalIterOpts
|
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
|
// Disable invariant checks even if they are otherwise enabled. Used by tests
|
||||||
// which construct "impossible" situations (e.g. seeking to a key before the
|
// which construct "impossible" situations (e.g. seeking to a key before the
|
||||||
// lower bound).
|
// lower bound).
|
||||||
@@ -236,24 +242,22 @@ var _ base.InternalIterator = (*levelIter)(nil)
|
|||||||
// parameter if the caller is never going to call SeekPrefixGE.
|
// parameter if the caller is never going to call SeekPrefixGE.
|
||||||
func newLevelIter(
|
func newLevelIter(
|
||||||
opts IterOptions,
|
opts IterOptions,
|
||||||
cmp Compare,
|
comparer *Comparer,
|
||||||
split Split,
|
|
||||||
newIters tableNewIters,
|
newIters tableNewIters,
|
||||||
files manifest.LevelIterator,
|
files manifest.LevelIterator,
|
||||||
level manifest.Level,
|
level manifest.Level,
|
||||||
bytesIterated *uint64,
|
internalOpts internalIterOpts,
|
||||||
) *levelIter {
|
) *levelIter {
|
||||||
l := &levelIter{}
|
l := &levelIter{}
|
||||||
l.init(context.Background(), opts, cmp, split, newIters, files, level,
|
l.init(context.Background(), opts, comparer, newIters, files, level,
|
||||||
internalIterOpts{bytesIterated: bytesIterated})
|
internalOpts)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *levelIter) init(
|
func (l *levelIter) init(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
opts IterOptions,
|
opts IterOptions,
|
||||||
cmp Compare,
|
comparer *Comparer,
|
||||||
split Split,
|
|
||||||
newIters tableNewIters,
|
newIters tableNewIters,
|
||||||
files manifest.LevelIterator,
|
files manifest.LevelIterator,
|
||||||
level manifest.Level,
|
level manifest.Level,
|
||||||
@@ -267,10 +271,15 @@ func (l *levelIter) init(
|
|||||||
l.upper = opts.UpperBound
|
l.upper = opts.UpperBound
|
||||||
l.tableOpts.TableFilter = opts.TableFilter
|
l.tableOpts.TableFilter = opts.TableFilter
|
||||||
l.tableOpts.PointKeyFilters = opts.PointKeyFilters
|
l.tableOpts.PointKeyFilters = opts.PointKeyFilters
|
||||||
|
if len(opts.PointKeyFilters) == 0 {
|
||||||
|
l.tableOpts.PointKeyFilters = l.filtersBuf[:0:1]
|
||||||
|
}
|
||||||
l.tableOpts.UseL6Filters = opts.UseL6Filters
|
l.tableOpts.UseL6Filters = opts.UseL6Filters
|
||||||
l.tableOpts.level = l.level
|
l.tableOpts.level = l.level
|
||||||
l.cmp = cmp
|
l.tableOpts.snapshotForHideObsoletePoints = opts.snapshotForHideObsoletePoints
|
||||||
l.split = split
|
l.comparer = comparer
|
||||||
|
l.cmp = comparer.Compare
|
||||||
|
l.split = comparer.Split
|
||||||
l.iterFile = nil
|
l.iterFile = nil
|
||||||
l.newIters = newIters
|
l.newIters = newIters
|
||||||
l.files = files
|
l.files = files
|
||||||
@@ -788,7 +797,7 @@ func (l *levelIter) SeekPrefixGE(
|
|||||||
}
|
}
|
||||||
return l.verify(l.largestBoundary, base.LazyValue{})
|
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
|
// 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
|
// 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
|
// 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
|
// next file will defeat the optimization for the next SeekPrefixGE that is
|
||||||
// called with flags.TrySeekUsingNext(), since for sparse key spaces it 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.
|
// 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 nil, base.LazyValue{}
|
||||||
}
|
}
|
||||||
return l.verify(l.skipEmptyFileForward())
|
return l.verify(l.skipEmptyFileForward())
|
||||||
@@ -1066,7 +1084,9 @@ func (l *levelIter) skipEmptyFileForward() (*InternalKey, base.LazyValue) {
|
|||||||
if *l.rangeDelIterPtr != nil && l.filteredIter != nil &&
|
if *l.rangeDelIterPtr != nil && l.filteredIter != nil &&
|
||||||
l.filteredIter.MaybeFilteredKeys() {
|
l.filteredIter.MaybeFilteredKeys() {
|
||||||
l.largestBoundary = &l.iterFile.Largest
|
l.largestBoundary = &l.iterFile.Largest
|
||||||
l.boundaryContext.isIgnorableBoundaryKey = true
|
if l.boundaryContext != nil {
|
||||||
|
l.boundaryContext.isIgnorableBoundaryKey = true
|
||||||
|
}
|
||||||
return l.largestBoundary, base.LazyValue{}
|
return l.largestBoundary, base.LazyValue{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1155,7 +1175,9 @@ func (l *levelIter) skipEmptyFileBackward() (*InternalKey, base.LazyValue) {
|
|||||||
// the next file.
|
// the next file.
|
||||||
if *l.rangeDelIterPtr != nil && l.filteredIter != nil && l.filteredIter.MaybeFilteredKeys() {
|
if *l.rangeDelIterPtr != nil && l.filteredIter != nil && l.filteredIter.MaybeFilteredKeys() {
|
||||||
l.smallestBoundary = &l.iterFile.Smallest
|
l.smallestBoundary = &l.iterFile.Smallest
|
||||||
l.boundaryContext.isIgnorableBoundaryKey = true
|
if l.boundaryContext != nil {
|
||||||
|
l.boundaryContext.isIgnorableBoundaryKey = true
|
||||||
|
}
|
||||||
return l.smallestBoundary, base.LazyValue{}
|
return l.smallestBoundary, base.LazyValue{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
vendor/github.com/cockroachdb/pebble/log_recycler.go
generated
vendored
10
vendor/github.com/cockroachdb/pebble/log_recycler.go
generated
vendored
@@ -32,14 +32,14 @@ type logRecycler struct {
|
|||||||
// the log file should not be deleted (i.e. the log is being recycled), and
|
// the log file should not be deleted (i.e. the log is being recycled), and
|
||||||
// false otherwise.
|
// false otherwise.
|
||||||
func (r *logRecycler) add(logInfo fileInfo) bool {
|
func (r *logRecycler) add(logInfo fileInfo) bool {
|
||||||
if logInfo.fileNum < r.minRecycleLogNum {
|
if logInfo.fileNum.FileNum() < r.minRecycleLogNum {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
if logInfo.fileNum <= r.mu.maxLogNum {
|
if logInfo.fileNum.FileNum() <= r.mu.maxLogNum {
|
||||||
// The log file number was already considered for recycling. Don't consider
|
// The log file number was already considered for recycling. Don't consider
|
||||||
// it again. This avoids a race between adding the same log file for
|
// it again. This avoids a race between adding the same log file for
|
||||||
// recycling multiple times, and removing the log file for actual
|
// recycling multiple times, and removing the log file for actual
|
||||||
@@ -49,7 +49,7 @@ func (r *logRecycler) add(logInfo fileInfo) bool {
|
|||||||
// shouldn't be deleted.
|
// shouldn't be deleted.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
r.mu.maxLogNum = logInfo.fileNum
|
r.mu.maxLogNum = logInfo.fileNum.FileNum()
|
||||||
if len(r.mu.logs) >= r.limit {
|
if len(r.mu.logs) >= r.limit {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ func (r *logRecycler) pop(logNum FileNum) error {
|
|||||||
if len(r.mu.logs) == 0 {
|
if len(r.mu.logs) == 0 {
|
||||||
return errors.New("pebble: log recycler empty")
|
return errors.New("pebble: log recycler empty")
|
||||||
}
|
}
|
||||||
if r.mu.logs[0].fileNum != logNum {
|
if r.mu.logs[0].fileNum.FileNum() != logNum {
|
||||||
return errors.Errorf("pebble: log recycler invalid %d vs %d", errors.Safe(logNum), errors.Safe(fileInfoNums(r.mu.logs)))
|
return errors.Errorf("pebble: log recycler invalid %d vs %d", errors.Safe(logNum), errors.Safe(fileInfoNums(r.mu.logs)))
|
||||||
}
|
}
|
||||||
r.mu.logs = r.mu.logs[1:]
|
r.mu.logs = r.mu.logs[1:]
|
||||||
@@ -102,7 +102,7 @@ func fileInfoNums(finfos []fileInfo) []FileNum {
|
|||||||
}
|
}
|
||||||
nums := make([]FileNum, len(finfos))
|
nums := make([]FileNum, len(finfos))
|
||||||
for i := range finfos {
|
for i := range finfos {
|
||||||
nums[i] = finfos[i].fileNum
|
nums[i] = finfos[i].fileNum.FileNum()
|
||||||
}
|
}
|
||||||
return nums
|
return nums
|
||||||
}
|
}
|
||||||
|
|||||||
104
vendor/github.com/cockroachdb/pebble/mem_table.go
generated
vendored
104
vendor/github.com/cockroachdb/pebble/mem_table.go
generated
vendored
@@ -10,12 +10,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/cockroachdb/pebble/internal/arenaskl"
|
"github.com/cockroachdb/pebble/internal/arenaskl"
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
"github.com/cockroachdb/pebble/internal/keyspan"
|
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||||
|
"github.com/cockroachdb/pebble/internal/manual"
|
||||||
"github.com/cockroachdb/pebble/internal/rangedel"
|
"github.com/cockroachdb/pebble/internal/rangedel"
|
||||||
"github.com/cockroachdb/pebble/internal/rangekey"
|
"github.com/cockroachdb/pebble/internal/rangekey"
|
||||||
)
|
)
|
||||||
@@ -80,12 +80,21 @@ type memTable struct {
|
|||||||
// inflight mutations that have reserved space in the memtable but not yet
|
// inflight mutations that have reserved space in the memtable but not yet
|
||||||
// applied. The memtable cannot be flushed to disk until the writer refs
|
// applied. The memtable cannot be flushed to disk until the writer refs
|
||||||
// drops to zero.
|
// drops to zero.
|
||||||
writerRefs int32
|
writerRefs atomic.Int32
|
||||||
tombstones keySpanCache
|
tombstones keySpanCache
|
||||||
rangeKeys keySpanCache
|
rangeKeys keySpanCache
|
||||||
// The current logSeqNum at the time the memtable was created. This is
|
// 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.
|
// 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
|
// memTableOptions holds configuration used when creating a memTable. All of
|
||||||
@@ -93,9 +102,10 @@ type memTable struct {
|
|||||||
// which is used by tests.
|
// which is used by tests.
|
||||||
type memTableOptions struct {
|
type memTableOptions struct {
|
||||||
*Options
|
*Options
|
||||||
arenaBuf []byte
|
arenaBuf []byte
|
||||||
size int
|
size int
|
||||||
logSeqNum uint64
|
logSeqNum uint64
|
||||||
|
releaseAccountingReservation func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkMemTable(obj interface{}) {
|
func checkMemTable(obj interface{}) {
|
||||||
@@ -110,18 +120,24 @@ func checkMemTable(obj interface{}) {
|
|||||||
// Options.MemTableSize is used instead.
|
// Options.MemTableSize is used instead.
|
||||||
func newMemTable(opts memTableOptions) *memTable {
|
func newMemTable(opts memTableOptions) *memTable {
|
||||||
opts.Options = opts.Options.EnsureDefaults()
|
opts.Options = opts.Options.EnsureDefaults()
|
||||||
if opts.size == 0 {
|
m := new(memTable)
|
||||||
opts.size = opts.MemTableSize
|
m.init(opts)
|
||||||
}
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
m := &memTable{
|
func (m *memTable) init(opts memTableOptions) {
|
||||||
cmp: opts.Comparer.Compare,
|
if opts.size == 0 {
|
||||||
formatKey: opts.Comparer.FormatKey,
|
opts.size = int(opts.MemTableSize)
|
||||||
equal: opts.Comparer.Equal,
|
|
||||||
arenaBuf: opts.arenaBuf,
|
|
||||||
writerRefs: 1,
|
|
||||||
logSeqNum: opts.logSeqNum,
|
|
||||||
}
|
}
|
||||||
|
*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{
|
m.tombstones = keySpanCache{
|
||||||
cmp: m.cmp,
|
cmp: m.cmp,
|
||||||
formatKey: m.formatKey,
|
formatKey: m.formatKey,
|
||||||
@@ -144,18 +160,18 @@ func newMemTable(opts memTableOptions) *memTable {
|
|||||||
m.rangeDelSkl.Reset(arena, m.cmp)
|
m.rangeDelSkl.Reset(arena, m.cmp)
|
||||||
m.rangeKeySkl.Reset(arena, m.cmp)
|
m.rangeKeySkl.Reset(arena, m.cmp)
|
||||||
m.reserved = arena.Size()
|
m.reserved = arena.Size()
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memTable) writerRef() {
|
func (m *memTable) writerRef() {
|
||||||
switch v := atomic.AddInt32(&m.writerRefs, 1); {
|
switch v := m.writerRefs.Add(1); {
|
||||||
case v <= 1:
|
case v <= 1:
|
||||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memTable) writerUnref() bool {
|
// writerUnref drops a ref on the memtable. Returns true if this was the last ref.
|
||||||
switch v := atomic.AddInt32(&m.writerRefs, -1); {
|
func (m *memTable) writerUnref() (wasLastRef bool) {
|
||||||
|
switch v := m.writerRefs.Add(-1); {
|
||||||
case v < 0:
|
case v < 0:
|
||||||
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
panic(fmt.Sprintf("pebble: inconsistent reference count: %d", v))
|
||||||
case v == 0:
|
case v == 0:
|
||||||
@@ -165,8 +181,9 @@ func (m *memTable) writerUnref() bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readyForFlush is part of the flushable interface.
|
||||||
func (m *memTable) readyForFlush() bool {
|
func (m *memTable) readyForFlush() bool {
|
||||||
return atomic.LoadInt32(&m.writerRefs) == 0
|
return m.writerRefs.Load() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare reserves space for the batch in the memtable and references the
|
// Prepare reserves space for the batch in the memtable and references the
|
||||||
@@ -194,11 +211,13 @@ func (m *memTable) apply(batch *Batch, seqNum uint64) error {
|
|||||||
var tombstoneCount, rangeKeyCount uint32
|
var tombstoneCount, rangeKeyCount uint32
|
||||||
startSeqNum := seqNum
|
startSeqNum := seqNum
|
||||||
for r := batch.Reader(); ; seqNum++ {
|
for r := batch.Reader(); ; seqNum++ {
|
||||||
kind, ukey, value, ok := r.Next()
|
kind, ukey, value, ok, err := r.Next()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
ikey := base.MakeInternalKey(ukey, seqNum, kind)
|
ikey := base.MakeInternalKey(ukey, seqNum, kind)
|
||||||
switch kind {
|
switch kind {
|
||||||
case InternalKeyKindRangeDelete:
|
case InternalKeyKindRangeDelete:
|
||||||
@@ -233,17 +252,19 @@ func (m *memTable) apply(batch *Batch, seqNum uint64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newIter returns an iterator that is unpositioned (Iterator.Valid() will
|
// newIter is part of the flushable interface. It returns an iterator that is
|
||||||
// return false). The iterator can be positioned via a call to SeekGE,
|
// unpositioned (Iterator.Valid() will return false). The iterator can be
|
||||||
// SeekLT, First or Last.
|
// positioned via a call to SeekGE, SeekLT, First or Last.
|
||||||
func (m *memTable) newIter(o *IterOptions) internalIterator {
|
func (m *memTable) newIter(o *IterOptions) internalIterator {
|
||||||
return m.skl.NewIter(o.GetLowerBound(), o.GetUpperBound())
|
return m.skl.NewIter(o.GetLowerBound(), o.GetUpperBound())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newFlushIter is part of the flushable interface.
|
||||||
func (m *memTable) newFlushIter(o *IterOptions, bytesFlushed *uint64) internalIterator {
|
func (m *memTable) newFlushIter(o *IterOptions, bytesFlushed *uint64) internalIterator {
|
||||||
return m.skl.NewFlushIter(bytesFlushed)
|
return m.skl.NewFlushIter(bytesFlushed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newRangeDelIter is part of the flushable interface.
|
||||||
func (m *memTable) newRangeDelIter(*IterOptions) keyspan.FragmentIterator {
|
func (m *memTable) newRangeDelIter(*IterOptions) keyspan.FragmentIterator {
|
||||||
tombstones := m.tombstones.get()
|
tombstones := m.tombstones.get()
|
||||||
if tombstones == nil {
|
if tombstones == nil {
|
||||||
@@ -252,6 +273,7 @@ func (m *memTable) newRangeDelIter(*IterOptions) keyspan.FragmentIterator {
|
|||||||
return keyspan.NewIter(m.cmp, tombstones)
|
return keyspan.NewIter(m.cmp, tombstones)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newRangeKeyIter is part of the flushable interface.
|
||||||
func (m *memTable) newRangeKeyIter(*IterOptions) keyspan.FragmentIterator {
|
func (m *memTable) newRangeKeyIter(*IterOptions) keyspan.FragmentIterator {
|
||||||
rangeKeys := m.rangeKeys.get()
|
rangeKeys := m.rangeKeys.get()
|
||||||
if rangeKeys == nil {
|
if rangeKeys == nil {
|
||||||
@@ -260,13 +282,14 @@ func (m *memTable) newRangeKeyIter(*IterOptions) keyspan.FragmentIterator {
|
|||||||
return keyspan.NewIter(m.cmp, rangeKeys)
|
return keyspan.NewIter(m.cmp, rangeKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// containsRangeKeys is part of the flushable interface.
|
||||||
func (m *memTable) containsRangeKeys() bool {
|
func (m *memTable) containsRangeKeys() bool {
|
||||||
return atomic.LoadUint32(&m.rangeKeys.atomicCount) > 0
|
return m.rangeKeys.count.Load() > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memTable) availBytes() uint32 {
|
func (m *memTable) availBytes() uint32 {
|
||||||
a := m.skl.Arena()
|
a := m.skl.Arena()
|
||||||
if atomic.LoadInt32(&m.writerRefs) == 1 {
|
if m.writerRefs.Load() == 1 {
|
||||||
// If there are no other concurrent apply operations, we can update the
|
// If there are no other concurrent apply operations, we can update the
|
||||||
// reserved bytes setting to accurately reflect how many bytes of been
|
// reserved bytes setting to accurately reflect how many bytes of been
|
||||||
// allocated vs the over-estimation present in memTableEntrySize.
|
// allocated vs the over-estimation present in memTableEntrySize.
|
||||||
@@ -275,10 +298,12 @@ func (m *memTable) availBytes() uint32 {
|
|||||||
return a.Capacity() - m.reserved
|
return a.Capacity() - m.reserved
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inuseBytes is part of the flushable interface.
|
||||||
func (m *memTable) inuseBytes() uint64 {
|
func (m *memTable) inuseBytes() uint64 {
|
||||||
return uint64(m.skl.Size() - memTableEmptySize)
|
return uint64(m.skl.Size() - memTableEmptySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// totalBytes is part of the flushable interface.
|
||||||
func (m *memTable) totalBytes() uint64 {
|
func (m *memTable) totalBytes() uint64 {
|
||||||
return uint64(m.skl.Arena().Capacity())
|
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
|
// 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.
|
// populated when empty when a span iterator of that key kind is created.
|
||||||
type keySpanCache struct {
|
type keySpanCache struct {
|
||||||
atomicCount uint32
|
count atomic.Uint32
|
||||||
frags unsafe.Pointer
|
frags atomic.Pointer[keySpanFrags]
|
||||||
cmp Compare
|
cmp Compare
|
||||||
formatKey base.FormatKey
|
formatKey base.FormatKey
|
||||||
constructSpan constructSpan
|
constructSpan constructSpan
|
||||||
@@ -366,23 +391,20 @@ type keySpanCache struct {
|
|||||||
// Invalidate the current set of cached spans, indicating the number of
|
// Invalidate the current set of cached spans, indicating the number of
|
||||||
// spans that were added.
|
// spans that were added.
|
||||||
func (c *keySpanCache) invalidate(count uint32) {
|
func (c *keySpanCache) invalidate(count uint32) {
|
||||||
newCount := atomic.AddUint32(&c.atomicCount, count)
|
newCount := c.count.Add(count)
|
||||||
var frags *keySpanFrags
|
var frags *keySpanFrags
|
||||||
|
|
||||||
for {
|
for {
|
||||||
oldPtr := atomic.LoadPointer(&c.frags)
|
oldFrags := c.frags.Load()
|
||||||
if oldPtr != nil {
|
if oldFrags != nil && oldFrags.count >= newCount {
|
||||||
oldFrags := (*keySpanFrags)(oldPtr)
|
// Someone else invalidated the cache before us and their invalidation
|
||||||
if oldFrags.count >= newCount {
|
// subsumes ours.
|
||||||
// Someone else invalidated the cache before us and their invalidation
|
break
|
||||||
// subsumes ours.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if frags == nil {
|
if frags == nil {
|
||||||
frags = &keySpanFrags{count: newCount}
|
frags = &keySpanFrags{count: newCount}
|
||||||
}
|
}
|
||||||
if atomic.CompareAndSwapPointer(&c.frags, oldPtr, unsafe.Pointer(frags)) {
|
if c.frags.CompareAndSwap(oldFrags, frags) {
|
||||||
// We successfully invalidated the cache.
|
// We successfully invalidated the cache.
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -391,7 +413,7 @@ func (c *keySpanCache) invalidate(count uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *keySpanCache) get() []keyspan.Span {
|
func (c *keySpanCache) get() []keyspan.Span {
|
||||||
frags := (*keySpanFrags)(atomic.LoadPointer(&c.frags))
|
frags := c.frags.Load()
|
||||||
if frags == nil {
|
if frags == nil {
|
||||||
return 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 and iterValue cache the current key and value iter are pointed at.
|
||||||
iterKey *InternalKey
|
iterKey *InternalKey
|
||||||
iterValue base.LazyValue
|
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
|
// levelIterBoundaryContext's fields are set when using levelIter, in order
|
||||||
// to surface sstable boundary keys and file-level context. See levelIter
|
// 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) {
|
func (m *mergingIter) ForEachLevelIter(fn func(li *levelIter) bool) {
|
||||||
for _, iter := range m.levels {
|
for _, ml := range m.levels {
|
||||||
if li, ok := iter.iter.(*levelIter); ok {
|
if ml.levelIter != nil {
|
||||||
if done := fn(li); done {
|
if done := fn(ml.levelIter); done {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
413
vendor/github.com/cockroachdb/pebble/metrics.go
generated
vendored
413
vendor/github.com/cockroachdb/pebble/metrics.go
generated
vendored
@@ -6,12 +6,13 @@ package pebble
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
"github.com/cockroachdb/pebble/internal/cache"
|
"github.com/cockroachdb/pebble/internal/cache"
|
||||||
"github.com/cockroachdb/pebble/internal/humanize"
|
"github.com/cockroachdb/pebble/internal/humanize"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache"
|
||||||
"github.com/cockroachdb/pebble/record"
|
"github.com/cockroachdb/pebble/record"
|
||||||
"github.com/cockroachdb/pebble/sstable"
|
"github.com/cockroachdb/pebble/sstable"
|
||||||
"github.com/cockroachdb/redact"
|
"github.com/cockroachdb/redact"
|
||||||
@@ -28,13 +29,10 @@ type FilterMetrics = sstable.FilterMetrics
|
|||||||
// comment in base.
|
// comment in base.
|
||||||
type ThroughputMetric = base.ThroughputMetric
|
type ThroughputMetric = base.ThroughputMetric
|
||||||
|
|
||||||
func formatCacheMetrics(w redact.SafePrinter, m *CacheMetrics, name redact.SafeString) {
|
// SecondaryCacheMetrics holds metrics for the persistent secondary cache
|
||||||
w.Printf("%7s %9s %7s %6.1f%% (score == hit-rate)\n",
|
// that caches commonly accessed blocks from blob storage on a local
|
||||||
name,
|
// file system.
|
||||||
humanize.SI.Int64(m.Count),
|
type SecondaryCacheMetrics = sharedcache.Metrics
|
||||||
humanize.IEC.Int64(m.Size),
|
|
||||||
redact.Safe(hitRate(m.Hits, m.Misses)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LevelMetrics holds per-level metrics such as the number of files and total
|
// LevelMetrics holds per-level metrics such as the number of files and total
|
||||||
// size of the files, and compaction related metrics.
|
// size of the files, and compaction related metrics.
|
||||||
@@ -46,9 +44,14 @@ type LevelMetrics struct {
|
|||||||
Sublevels int32
|
Sublevels int32
|
||||||
// The total number of files in the level.
|
// The total number of files in the level.
|
||||||
NumFiles int64
|
NumFiles int64
|
||||||
|
// The total number of virtual sstables in the level.
|
||||||
|
NumVirtualFiles uint64
|
||||||
// The total size in bytes of the files in the level.
|
// The total size in bytes of the files in the level.
|
||||||
Size int64
|
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
|
Score float64
|
||||||
// The number of incoming bytes from other levels read during
|
// The number of incoming bytes from other levels read during
|
||||||
// compactions. This excludes bytes moved and bytes ingested. For L0 this is
|
// compactions. This excludes bytes moved and bytes ingested. For L0 this is
|
||||||
@@ -79,6 +82,18 @@ type LevelMetrics struct {
|
|||||||
TablesIngested uint64
|
TablesIngested uint64
|
||||||
// The number of sstables moved to this level by a "move" compaction.
|
// The number of sstables moved to this level by a "move" compaction.
|
||||||
TablesMoved uint64
|
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 contains misc additional metrics that are not always printed.
|
||||||
Additional struct {
|
Additional struct {
|
||||||
// The sum of Properties.ValueBlocksSize for all the sstables in this
|
// 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.
|
// Add updates the counter metrics for the level.
|
||||||
func (m *LevelMetrics) Add(u *LevelMetrics) {
|
func (m *LevelMetrics) Add(u *LevelMetrics) {
|
||||||
m.NumFiles += u.NumFiles
|
m.NumFiles += u.NumFiles
|
||||||
|
m.NumVirtualFiles += u.NumVirtualFiles
|
||||||
|
m.VirtualSize += u.VirtualSize
|
||||||
m.Size += u.Size
|
m.Size += u.Size
|
||||||
m.BytesIn += u.BytesIn
|
m.BytesIn += u.BytesIn
|
||||||
m.BytesIngested += u.BytesIngested
|
m.BytesIngested += u.BytesIngested
|
||||||
@@ -107,6 +124,9 @@ func (m *LevelMetrics) Add(u *LevelMetrics) {
|
|||||||
m.TablesFlushed += u.TablesFlushed
|
m.TablesFlushed += u.TablesFlushed
|
||||||
m.TablesIngested += u.TablesIngested
|
m.TablesIngested += u.TablesIngested
|
||||||
m.TablesMoved += u.TablesMoved
|
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.BytesWrittenDataBlocks += u.Additional.BytesWrittenDataBlocks
|
||||||
m.Additional.BytesWrittenValueBlocks += u.Additional.BytesWrittenValueBlocks
|
m.Additional.BytesWrittenValueBlocks += u.Additional.BytesWrittenValueBlocks
|
||||||
m.Additional.ValueBlocksSize += u.Additional.ValueBlocksSize
|
m.Additional.ValueBlocksSize += u.Additional.ValueBlocksSize
|
||||||
@@ -121,32 +141,6 @@ func (m *LevelMetrics) WriteAmp() float64 {
|
|||||||
return float64(m.BytesFlushed+m.BytesCompacted) / float64(m.BytesIn)
|
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,
|
// Metrics holds metrics for various subsystems of the DB such as the Cache,
|
||||||
// Compactions, WAL, and per-Level metrics.
|
// Compactions, WAL, and per-Level metrics.
|
||||||
//
|
//
|
||||||
@@ -158,14 +152,15 @@ type Metrics struct {
|
|||||||
|
|
||||||
Compact struct {
|
Compact struct {
|
||||||
// The total number of compactions, and per-compaction type counts.
|
// The total number of compactions, and per-compaction type counts.
|
||||||
Count int64
|
Count int64
|
||||||
DefaultCount int64
|
DefaultCount int64
|
||||||
DeleteOnlyCount int64
|
DeleteOnlyCount int64
|
||||||
ElisionOnlyCount int64
|
ElisionOnlyCount int64
|
||||||
MoveCount int64
|
MoveCount int64
|
||||||
ReadCount int64
|
ReadCount int64
|
||||||
RewriteCount int64
|
RewriteCount int64
|
||||||
MultiLevelCount int64
|
MultiLevelCount int64
|
||||||
|
CounterLevelCount int64
|
||||||
// An estimate of the number of bytes that need to be compacted for the LSM
|
// An estimate of the number of bytes that need to be compacted for the LSM
|
||||||
// to reach a stable state.
|
// to reach a stable state.
|
||||||
EstimatedDebt uint64
|
EstimatedDebt uint64
|
||||||
@@ -179,6 +174,14 @@ type Metrics struct {
|
|||||||
// compaction. Such files are compacted in a rewrite compaction
|
// compaction. Such files are compacted in a rewrite compaction
|
||||||
// when no other compactions are picked.
|
// when no other compactions are picked.
|
||||||
MarkedFiles int
|
MarkedFiles int
|
||||||
|
// Duration records the cumulative duration of all compactions since the
|
||||||
|
// database was opened.
|
||||||
|
Duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
Ingest struct {
|
||||||
|
// The total number of ingestions
|
||||||
|
Count uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
Flush struct {
|
Flush struct {
|
||||||
@@ -210,7 +213,10 @@ type Metrics struct {
|
|||||||
// The count of memtables.
|
// The count of memtables.
|
||||||
Count int64
|
Count int64
|
||||||
// The number of bytes present in zombie memtables which are no longer
|
// 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
|
ZombieSize uint64
|
||||||
// The count of zombie memtables.
|
// The count of zombie memtables.
|
||||||
ZombieCount int64
|
ZombieCount int64
|
||||||
@@ -222,6 +228,9 @@ type Metrics struct {
|
|||||||
// The approximate count of internal tombstones (DEL, SINGLEDEL and
|
// The approximate count of internal tombstones (DEL, SINGLEDEL and
|
||||||
// RANGEDEL key kinds) within the database.
|
// RANGEDEL key kinds) within the database.
|
||||||
TombstoneCount uint64
|
TombstoneCount uint64
|
||||||
|
// A cumulative total number of missized DELSIZED keys encountered by
|
||||||
|
// compactions since the database was opened.
|
||||||
|
MissizedTombstonesCount uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
Snapshots struct {
|
Snapshots struct {
|
||||||
@@ -250,12 +259,18 @@ type Metrics struct {
|
|||||||
ZombieSize uint64
|
ZombieSize uint64
|
||||||
// The count of zombie tables.
|
// The count of zombie tables.
|
||||||
ZombieCount int64
|
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
|
TableCache CacheMetrics
|
||||||
|
|
||||||
// Count of the number of open sstable iterators.
|
// Count of the number of open sstable iterators.
|
||||||
TableIters int64
|
TableIters int64
|
||||||
|
// Uptime is the total time since this DB was opened.
|
||||||
|
Uptime time.Duration
|
||||||
|
|
||||||
WAL struct {
|
WAL struct {
|
||||||
// Number of live WAL files.
|
// Number of live WAL files.
|
||||||
@@ -281,6 +296,8 @@ type Metrics struct {
|
|||||||
record.LogWriterMetrics
|
record.LogWriterMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SecondaryCacheMetrics SecondaryCacheMetrics
|
||||||
|
|
||||||
private struct {
|
private struct {
|
||||||
optionsFileSize uint64
|
optionsFileSize uint64
|
||||||
manifestFileSize uint64
|
manifestFileSize uint64
|
||||||
@@ -294,6 +311,13 @@ var (
|
|||||||
prometheus.LinearBuckets(0.0, float64(time.Microsecond*100), 50),
|
prometheus.LinearBuckets(0.0, float64(time.Microsecond*100), 50),
|
||||||
prometheus.ExponentialBucketsRange(float64(time.Millisecond*5), float64(10*time.Second), 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,
|
// DiskSpaceUsage returns the total disk space used by the database in bytes,
|
||||||
@@ -313,12 +337,25 @@ func (m *Metrics) DiskSpaceUsage() uint64 {
|
|||||||
return usageBytes
|
return usageBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Metrics) levelSizes() [numLevels]int64 {
|
// NumVirtual is the number of virtual sstables in the latest version
|
||||||
var sizes [numLevels]int64
|
// summed over every level in the lsm.
|
||||||
for i := 0; i < len(sizes); i++ {
|
func (m *Metrics) NumVirtual() uint64 {
|
||||||
sizes[i] = m.Levels[i].Size
|
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.
|
// ReadAmp returns the current read amplification of the database.
|
||||||
@@ -349,59 +386,34 @@ func (m *Metrics) Total() LevelMetrics {
|
|||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
|
|
||||||
const notApplicable = redact.SafeString("-")
|
// String pretty-prints the metrics as below:
|
||||||
|
|
||||||
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:
|
|
||||||
//
|
//
|
||||||
// __level_____count____size___score______in__ingest(sz_cnt)____move(sz_cnt)___write(sz_cnt)____read___r-amp___w-amp
|
// | | | | ingested | moved | written | | amp
|
||||||
// WAL 1 28 B - 17 B - - - - 56 B - - - 3.3
|
// level | tables size val-bl vtables | score | in | tables size | tables size | tables size | read | r w
|
||||||
// 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
|
// 0 | 101 102B 0B 0 | 103.0 | 104B | 112 104B | 113 106B | 221 217B | 107B | 1 2.1
|
||||||
// 2 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
// 1 | 201 202B 0B 0 | 203.0 | 204B | 212 204B | 213 206B | 421 417B | 207B | 2 2.0
|
||||||
// 3 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
// 2 | 301 302B 0B 0 | 303.0 | 304B | 312 304B | 313 306B | 621 617B | 307B | 3 2.0
|
||||||
// 4 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
// 3 | 401 402B 0B 0 | 403.0 | 404B | 412 404B | 413 406B | 821 817B | 407B | 4 2.0
|
||||||
// 5 0 0 B 0.00 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
// 4 | 501 502B 0B 0 | 503.0 | 504B | 512 504B | 513 506B | 1.0K 1017B | 507B | 5 2.0
|
||||||
// 6 0 0 B - 0 B 0 B 0 0 B 0 0 B 0 0 B 0 0.0
|
// 5 | 601 602B 0B 0 | 603.0 | 604B | 612 604B | 613 606B | 1.2K 1.2KB | 607B | 6 2.0
|
||||||
// total 1 770 B - 56 B 0 B 0 0 B 0 826 B 1 0 B 1 14.8
|
// 6 | 701 702B 0B 0 | - | 704B | 712 704B | 713 706B | 1.4K 1.4KB | 707B | 7 2.0
|
||||||
// flush 1 0 B 0 0 (ingest = ingested-as-flushable, move = tables-ingested)
|
// 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
|
||||||
// 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)
|
// WAL: 22 files (24B) in: 25B written: 26B (4% overhead)
|
||||||
// memtbl 1 256 K
|
// Flushes: 8
|
||||||
// zmemtbl 1 256 K
|
// Compactions: 5 estimated debt: 6B in progress: 2 (7B)
|
||||||
// ztbl 0 0 B
|
// default: 27 delete: 28 elision: 29 move: 30 read: 31 rewrite: 32 multi-level: 33
|
||||||
// bcache 4 697 B 0.0% (score == hit-rate)
|
// MemTables: 12 (11B) zombie: 14 (13B)
|
||||||
// tcache 1 696 B 0.0% (score == hit-rate)
|
// Zombie tables: 16 (15B)
|
||||||
// snaps 0 - 0 (score == earliest seq num)
|
// Backing tables: 0 (0B)
|
||||||
// titers 1
|
// Block cache: 2 entries (1B) hit rate: 42.9%
|
||||||
// filter - - 0.0% (score == utility)
|
// Table cache: 18 entries (17B) hit rate: 48.7%
|
||||||
//
|
// Secondary cache: 40 entries (40B) hit rate: 49.9%
|
||||||
// The WAL "in" metric is the size of the batches written to the WAL. The WAL
|
// Snapshots: 4 earliest seq num: 1024
|
||||||
// "write" metric is the size of the physical data written to the WAL which
|
// Table iters: 21
|
||||||
// includes record fragment overhead. Write amplification is computed as
|
// Filter utility: 47.4%
|
||||||
// bytes-written / bytes-in, except for the total row where bytes-in is
|
// Ingestions: 27 as flushable: 36 (34B in 35 tables)
|
||||||
// replaced with WAL-bytes-written + bytes-ingested.
|
|
||||||
func (m *Metrics) String() string {
|
func (m *Metrics) String() string {
|
||||||
return redact.StringWithoutMarkers(m)
|
return redact.StringWithoutMarkers(m)
|
||||||
}
|
}
|
||||||
@@ -422,29 +434,84 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
|
|||||||
// width specifiers. When the issue is fixed, we can convert these to
|
// width specifiers. When the issue is fixed, we can convert these to
|
||||||
// RedactableStrings. https://github.com/cockroachdb/redact/issues/17
|
// RedactableStrings. https://github.com/cockroachdb/redact/issues/17
|
||||||
|
|
||||||
haveValueBlocks := false
|
multiExists := m.Compact.MultiLevelCount > 0
|
||||||
var valueBlocksHeading redact.SafeString
|
appendIfMulti := func(line redact.SafeString) {
|
||||||
for level := 0; level < numLevels; level++ {
|
if multiExists {
|
||||||
if m.Levels[level].Additional.ValueBlocksSize > 0 {
|
w.SafeString(line)
|
||||||
haveValueBlocks = true
|
|
||||||
valueBlocksHeading = "__val-bl"
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
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++ {
|
for level := 0; level < numLevels; level++ {
|
||||||
l := &m.Levels[level]
|
l := &m.Levels[level]
|
||||||
w.Printf("%7d ", redact.Safe(level))
|
w.Printf("%5d ", redact.Safe(level))
|
||||||
|
|
||||||
// Format the score.
|
// Format the score.
|
||||||
var score redact.SafeValue = notApplicable
|
score := math.NaN()
|
||||||
if level < numLevels-1 {
|
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.Add(l)
|
||||||
total.Sublevels += l.Sublevels
|
total.Sublevels += l.Sublevels
|
||||||
}
|
}
|
||||||
@@ -454,57 +521,105 @@ func (m *Metrics) SafeFormat(w redact.SafePrinter, _ rune) {
|
|||||||
// the bytes written to the log and bytes written externally and then
|
// the bytes written to the log and bytes written externally and then
|
||||||
// ingested.
|
// ingested.
|
||||||
total.BytesFlushed += total.BytesIn
|
total.BytesFlushed += total.BytesIn
|
||||||
w.SafeString(" total ")
|
w.SafeString("total ")
|
||||||
total.format(w, notApplicable, haveValueBlocks)
|
formatRow(&total, math.NaN())
|
||||||
|
|
||||||
w.Printf(" flush %9d %31s %7d %7d %s\n",
|
w.SafeString("-------------------------------------------------------------------------------------------------------------------")
|
||||||
redact.Safe(m.Flush.Count),
|
appendIfMulti("--------------------")
|
||||||
humanize.IEC.Uint64(m.Flush.AsIngestBytes),
|
newline()
|
||||||
redact.Safe(m.Flush.AsIngestTableCount),
|
w.Printf("WAL: %d files (%s) in: %s written: %s (%.0f%% overhead)\n",
|
||||||
redact.Safe(m.Flush.AsIngestCount),
|
redact.Safe(m.WAL.Files),
|
||||||
redact.SafeString(`(ingest = tables-ingested, move = ingested-as-flushable)`))
|
humanize.Bytes.Uint64(m.WAL.Size),
|
||||||
w.Printf("compact %9d %7s %7s %7d %s %s\n",
|
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),
|
redact.Safe(m.Compact.Count),
|
||||||
humanize.IEC.Uint64(m.Compact.EstimatedDebt),
|
humanize.Bytes.Uint64(m.Compact.EstimatedDebt),
|
||||||
humanize.IEC.Int64(m.Compact.InProgressBytes),
|
|
||||||
redact.Safe(m.Compact.NumInProgress),
|
redact.Safe(m.Compact.NumInProgress),
|
||||||
redact.SafeString(strings.Repeat(" ", 24)),
|
humanize.Bytes.Int64(m.Compact.InProgressBytes))
|
||||||
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",
|
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.DefaultCount),
|
||||||
redact.Safe(m.Compact.DeleteOnlyCount),
|
redact.Safe(m.Compact.DeleteOnlyCount),
|
||||||
redact.Safe(m.Compact.ElisionOnlyCount),
|
redact.Safe(m.Compact.ElisionOnlyCount),
|
||||||
redact.Safe(m.Compact.MoveCount),
|
redact.Safe(m.Compact.MoveCount),
|
||||||
redact.Safe(m.Compact.ReadCount),
|
redact.Safe(m.Compact.ReadCount),
|
||||||
redact.Safe(m.Compact.RewriteCount),
|
redact.Safe(m.Compact.RewriteCount),
|
||||||
redact.Safe(m.Compact.MultiLevelCount),
|
redact.Safe(m.Compact.MultiLevelCount))
|
||||||
redact.SafeString(`(default, delete, elision, move, read, rewrite, multi-level)`))
|
|
||||||
w.Printf(" memtbl %9d %7s\n",
|
w.Printf("MemTables: %d (%s) zombie: %d (%s)\n",
|
||||||
redact.Safe(m.MemTable.Count),
|
redact.Safe(m.MemTable.Count),
|
||||||
humanize.IEC.Uint64(m.MemTable.Size))
|
humanize.Bytes.Uint64(m.MemTable.Size),
|
||||||
w.Printf("zmemtbl %9d %7s\n",
|
|
||||||
redact.Safe(m.MemTable.ZombieCount),
|
redact.Safe(m.MemTable.ZombieCount),
|
||||||
humanize.IEC.Uint64(m.MemTable.ZombieSize))
|
humanize.Bytes.Uint64(m.MemTable.ZombieSize))
|
||||||
w.Printf(" ztbl %9d %7s\n",
|
|
||||||
|
w.Printf("Zombie tables: %d (%s)\n",
|
||||||
redact.Safe(m.Table.ZombieCount),
|
redact.Safe(m.Table.ZombieCount),
|
||||||
humanize.IEC.Uint64(m.Table.ZombieSize))
|
humanize.Bytes.Uint64(m.Table.ZombieSize))
|
||||||
formatCacheMetrics(w, &m.BlockCache, "bcache")
|
|
||||||
formatCacheMetrics(w, &m.TableCache, "tcache")
|
w.Printf("Backing tables: %d (%s)\n",
|
||||||
w.Printf(" snaps %9d %7s %7d (score == earliest seq num)\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),
|
redact.Safe(m.Snapshots.Count),
|
||||||
notApplicable,
|
|
||||||
redact.Safe(m.Snapshots.EarliestSeqNum))
|
redact.Safe(m.Snapshots.EarliestSeqNum))
|
||||||
w.Printf(" titers %9d\n", redact.Safe(m.TableIters))
|
|
||||||
w.Printf(" filter %9s %7s %6.1f%% (score == utility)\n",
|
w.Printf("Table iters: %d\n", redact.Safe(m.TableIters))
|
||||||
notApplicable,
|
w.Printf("Filter utility: %.1f%%\n", redact.Safe(hitRate(m.Filter.Hits, m.Filter.Misses)))
|
||||||
notApplicable,
|
w.Printf("Ingestions: %d as flushable: %d (%s in %d tables)\n",
|
||||||
redact.Safe(hitRate(m.Filter.Hits, m.Filter.Misses)))
|
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 {
|
func hitRate(hits, misses int64) float64 {
|
||||||
sum := hits + misses
|
return percent(hits, hits+misses)
|
||||||
if sum == 0 {
|
}
|
||||||
|
|
||||||
|
func percent(numerator, denominator int64) float64 {
|
||||||
|
if denominator == 0 {
|
||||||
return 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)
|
||||||
}
|
}
|
||||||
|
|||||||
19
vendor/github.com/cockroachdb/pebble/objstorage/noop_readahead.go
generated
vendored
19
vendor/github.com/cockroachdb/pebble/objstorage/noop_readahead.go
generated
vendored
@@ -4,26 +4,31 @@
|
|||||||
|
|
||||||
package objstorage
|
package objstorage
|
||||||
|
|
||||||
import "io"
|
import "context"
|
||||||
|
|
||||||
// NoopReadHandle can be used by Readable implementations that don't
|
// NoopReadHandle can be used by Readable implementations that don't
|
||||||
// support read-ahead.
|
// support read-ahead.
|
||||||
type NoopReadHandle struct {
|
type NoopReadHandle struct {
|
||||||
io.ReaderAt
|
readable Readable
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeNoopReadHandle initializes a NoopReadHandle.
|
// MakeNoopReadHandle initializes a NoopReadHandle.
|
||||||
func MakeNoopReadHandle(r io.ReaderAt) NoopReadHandle {
|
func MakeNoopReadHandle(r Readable) NoopReadHandle {
|
||||||
return NoopReadHandle{ReaderAt: r}
|
return NoopReadHandle{readable: r}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ ReadHandle = (*NoopReadHandle)(nil)
|
var _ ReadHandle = (*NoopReadHandle)(nil)
|
||||||
|
|
||||||
|
// ReadAt is part of the ReadHandle interface.
|
||||||
|
func (h *NoopReadHandle) ReadAt(ctx context.Context, p []byte, off int64) error {
|
||||||
|
return h.readable.ReadAt(ctx, p, off)
|
||||||
|
}
|
||||||
|
|
||||||
// Close is part of the ReadHandle interface.
|
// Close is part of the ReadHandle interface.
|
||||||
func (*NoopReadHandle) Close() error { return nil }
|
func (*NoopReadHandle) Close() error { return nil }
|
||||||
|
|
||||||
// MaxReadahead is part of the ReadHandle interface.
|
// SetupForCompaction is part of the ReadHandle interface.
|
||||||
func (*NoopReadHandle) MaxReadahead() {}
|
func (*NoopReadHandle) SetupForCompaction() {}
|
||||||
|
|
||||||
// RecordCacheHit is part of the ReadHandle interface.
|
// RecordCacheHit is part of the ReadHandle interface.
|
||||||
func (*NoopReadHandle) RecordCacheHit(offset, size int64) {}
|
func (*NoopReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {}
|
||||||
|
|||||||
324
vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go
generated
vendored
Normal file
324
vendor/github.com/cockroachdb/pebble/objstorage/objstorage.go
generated
vendored
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
// 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 objstorage
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Readable is the handle for an object that is open for reading.
|
||||||
|
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
|
||||||
|
|
||||||
|
Close() error
|
||||||
|
|
||||||
|
// Size returns the size of the object.
|
||||||
|
Size() int64
|
||||||
|
|
||||||
|
// NewReadHandle creates a read handle for ReadAt requests that are related
|
||||||
|
// and can benefit from optimizations like read-ahead.
|
||||||
|
//
|
||||||
|
// The ReadHandle must be closed before the Readable is closed.
|
||||||
|
//
|
||||||
|
// Multiple separate ReadHandles can be used.
|
||||||
|
NewReadHandle(ctx context.Context) ReadHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadHandle is used to perform reads that are related and might benefit from
|
||||||
|
// optimizations like read-ahead.
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// trying to detect a sequential reading pattern.
|
||||||
|
RecordCacheHit(ctx context.Context, offset, size int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writable is the handle for an object that is open for writing.
|
||||||
|
// Either Finish or Abort must be called.
|
||||||
|
type Writable interface {
|
||||||
|
// Write writes len(p) bytes from p to the underlying object. The data is not
|
||||||
|
// guaranteed to be durable until Finish is called.
|
||||||
|
//
|
||||||
|
// Note that Write *is* allowed to modify the slice passed in, whether
|
||||||
|
// temporarily or permanently. Callers of Write need to take this into
|
||||||
|
// account.
|
||||||
|
Write(p []byte) error
|
||||||
|
|
||||||
|
// Finish completes the object and makes the data durable.
|
||||||
|
// No further calls are allowed after calling Finish.
|
||||||
|
Finish() error
|
||||||
|
|
||||||
|
// Abort gives up on finishing the object. There is no guarantee about whether
|
||||||
|
// the object exists after calling Abort.
|
||||||
|
// No further calls are allowed after calling Abort.
|
||||||
|
Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectMetadata contains the metadata required to be able to access an object.
|
||||||
|
type ObjectMetadata struct {
|
||||||
|
DiskFileNum base.DiskFileNum
|
||||||
|
FileType base.FileType
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.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.
|
||||||
|
// This ID is incorporated in backing object names.
|
||||||
|
// Must be non-zero.
|
||||||
|
type CreatorID uint64
|
||||||
|
|
||||||
|
// IsSet returns true if the CreatorID is not zero.
|
||||||
|
func (c CreatorID) IsSet() bool { return c != 0 }
|
||||||
|
|
||||||
|
func (c CreatorID) String() string { return fmt.Sprintf("%d", c) }
|
||||||
|
|
||||||
|
// SharedCleanupMethod indicates the method for cleaning up unused shared objects.
|
||||||
|
type SharedCleanupMethod uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SharedRefTracking is used for shared objects for which objstorage providers
|
||||||
|
// keep track of references via reference marker objects.
|
||||||
|
SharedRefTracking SharedCleanupMethod = iota
|
||||||
|
|
||||||
|
// SharedNoCleanup is used for remote objects that are managed externally; the
|
||||||
|
// objstorage provider never deletes such objects.
|
||||||
|
SharedNoCleanup
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenOptions contains optional arguments for OpenForReading.
|
||||||
|
type OpenOptions struct {
|
||||||
|
// MustExist triggers a fatal error if the file does not exist. The fatal
|
||||||
|
// error message contains extra information helpful for debugging.
|
||||||
|
MustExist bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOptions contains optional arguments for Create.
|
||||||
|
type CreateOptions struct {
|
||||||
|
// PreferSharedStorage causes the object to be created on shared storage if
|
||||||
|
// the provider has shared storage configured.
|
||||||
|
PreferSharedStorage bool
|
||||||
|
|
||||||
|
// SharedCleanupMethod is used for the object when it is created on shared storage.
|
||||||
|
// The default (zero) value is SharedRefTracking.
|
||||||
|
SharedCleanupMethod SharedCleanupMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provider is a singleton object used to access and manage objects.
|
||||||
|
//
|
||||||
|
// An object is conceptually like a large immutable file. The main use of
|
||||||
|
// objects is for storing sstables; in the future it could also be used for blob
|
||||||
|
// storage.
|
||||||
|
//
|
||||||
|
// The Provider can only manage objects that it knows about - either objects
|
||||||
|
// created by the provider, or existing objects the Provider was informed about
|
||||||
|
// via AddObjects.
|
||||||
|
//
|
||||||
|
// Objects are currently backed by a vfs.File or a remote.Storage object.
|
||||||
|
type Provider interface {
|
||||||
|
// OpenForReading opens an existing object.
|
||||||
|
OpenForReading(
|
||||||
|
ctx context.Context, fileType base.FileType, FileNum base.DiskFileNum, opts OpenOptions,
|
||||||
|
) (Readable, error)
|
||||||
|
|
||||||
|
// Create creates a new object and opens it for writing.
|
||||||
|
//
|
||||||
|
// The object is not guaranteed to be durable (accessible in case of crashes)
|
||||||
|
// until Sync is called.
|
||||||
|
Create(
|
||||||
|
ctx context.Context, fileType base.FileType, FileNum base.DiskFileNum, opts CreateOptions,
|
||||||
|
) (w Writable, meta ObjectMetadata, err error)
|
||||||
|
|
||||||
|
// Remove removes an object.
|
||||||
|
//
|
||||||
|
// The object is not guaranteed to be durably removed until Sync is called.
|
||||||
|
Remove(fileType base.FileType, FileNum base.DiskFileNum) error
|
||||||
|
|
||||||
|
// Sync flushes the metadata from creation or removal of objects since the last Sync.
|
||||||
|
// This includes objects that have been Created but for which
|
||||||
|
// Writable.Finish() has not yet been called.
|
||||||
|
Sync() error
|
||||||
|
|
||||||
|
// LinkOrCopyFromLocal creates a new object that is either a copy of a given
|
||||||
|
// local file or a hard link (if the new object is created on the same FS, and
|
||||||
|
// if the FS supports it).
|
||||||
|
//
|
||||||
|
// The object is not guaranteed to be durable (accessible in case of crashes)
|
||||||
|
// until Sync is called.
|
||||||
|
LinkOrCopyFromLocal(
|
||||||
|
ctx context.Context,
|
||||||
|
srcFS vfs.FS,
|
||||||
|
srcFilePath string,
|
||||||
|
dstFileType base.FileType,
|
||||||
|
dstFileNum base.DiskFileNum,
|
||||||
|
opts CreateOptions,
|
||||||
|
) (ObjectMetadata, error)
|
||||||
|
|
||||||
|
// Lookup returns the metadata of an object that is already known to the Provider.
|
||||||
|
// Does not perform any I/O.
|
||||||
|
Lookup(fileType base.FileType, FileNum base.DiskFileNum) (ObjectMetadata, error)
|
||||||
|
|
||||||
|
// Path returns an internal, implementation-dependent path for the object. It is
|
||||||
|
// meant to be used for informational purposes (like logging).
|
||||||
|
Path(meta ObjectMetadata) string
|
||||||
|
|
||||||
|
// Size returns the size of the object.
|
||||||
|
Size(meta ObjectMetadata) (int64, error)
|
||||||
|
|
||||||
|
// List returns the objects currently known to the provider. Does not perform any I/O.
|
||||||
|
List() []ObjectMetadata
|
||||||
|
|
||||||
|
// SetCreatorID sets the CreatorID which is needed in order to use shared
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// IsSharedForeign returns whether this object is owned by a different node.
|
||||||
|
IsSharedForeign(meta ObjectMetadata) bool
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 RemoteObjectBacking []byte
|
||||||
|
|
||||||
|
// 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 RemoteObjectBackingHandle
|
||||||
|
// delays any unref until Close.
|
||||||
|
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() (RemoteObjectBacking, error)
|
||||||
|
Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 remote object backing (normally
|
||||||
|
// generated from a different instance, but using the same Provider
|
||||||
|
// implementation).
|
||||||
|
Backing RemoteObjectBacking
|
||||||
|
}
|
||||||
68
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing/obj_io_tracing.go
generated
vendored
Normal file
68
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing/obj_io_tracing.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// 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 objiotracing
|
||||||
|
|
||||||
|
import "github.com/cockroachdb/pebble/internal/base"
|
||||||
|
|
||||||
|
// OpType indicates the type of operation.
|
||||||
|
type OpType uint8
|
||||||
|
|
||||||
|
// OpType values.
|
||||||
|
const (
|
||||||
|
ReadOp OpType = iota
|
||||||
|
WriteOp
|
||||||
|
// RecordCacheHitOp happens when a read is satisfied from the block cache. See
|
||||||
|
// objstorage.ReadHandle.RecordCacheHit().
|
||||||
|
RecordCacheHitOp
|
||||||
|
// 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.
|
||||||
|
type Reason uint8
|
||||||
|
|
||||||
|
// Reason values.
|
||||||
|
const (
|
||||||
|
UnknownReason Reason = iota
|
||||||
|
ForFlush
|
||||||
|
ForCompaction
|
||||||
|
ForIngestion
|
||||||
|
// TODO(radu): add ForUserFacing.
|
||||||
|
)
|
||||||
|
|
||||||
|
// BlockType indicates the type of data block relevant to an operation.
|
||||||
|
type BlockType uint8
|
||||||
|
|
||||||
|
// BlockType values.
|
||||||
|
const (
|
||||||
|
UnknownBlock BlockType = iota
|
||||||
|
DataBlock
|
||||||
|
ValueBlock
|
||||||
|
FilterBlock
|
||||||
|
MetadataBlock
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event is the on-disk format of a tracing event. It is exported here so that
|
||||||
|
// trace processing tools can use it by importing this package.
|
||||||
|
type Event struct {
|
||||||
|
// Event start time as a Unix time (see time.Time.StartUnixNano()).
|
||||||
|
// Note that recorded events are not necessarily ordered by time - this is
|
||||||
|
// because separate event "streams" use local buffers (for performance).
|
||||||
|
StartUnixNano int64
|
||||||
|
Op OpType
|
||||||
|
Reason Reason
|
||||||
|
BlockType BlockType
|
||||||
|
// LSM level plus one (with 0 indicating unknown level).
|
||||||
|
LevelPlusOne uint8
|
||||||
|
// Hardcoded padding so that struct layout doesn't depend on architecture.
|
||||||
|
_ uint32
|
||||||
|
FileNum base.FileNum
|
||||||
|
// HandleID is a unique identifier corresponding to an objstorage.ReadHandle;
|
||||||
|
// only set for read operations performed through a ReadHandle.
|
||||||
|
HandleID uint64
|
||||||
|
Offset int64
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
59
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing/obj_io_tracing_off.go
generated
vendored
Normal file
59
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing/obj_io_tracing_off.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !pebble_obj_io_tracing
|
||||||
|
// +build !pebble_obj_io_tracing
|
||||||
|
|
||||||
|
package objiotracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage"
|
||||||
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enabled is used to short circuit tracing-related code in regular builds.
|
||||||
|
const Enabled = false
|
||||||
|
|
||||||
|
// Tracer manages the writing of object IO traces to files.
|
||||||
|
type Tracer struct{}
|
||||||
|
|
||||||
|
// Open creates a Tracer which generates trace files in the given directory.
|
||||||
|
// Each trace file contains a series of Events (as they are in memory).
|
||||||
|
func Open(fs vfs.FS, fsDir string) *Tracer {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the tracer, flushing any remaining events.
|
||||||
|
func (*Tracer) Close() {}
|
||||||
|
|
||||||
|
// WrapReadable wraps an objstorage.Readable with one that generates tracing
|
||||||
|
// events.
|
||||||
|
func (*Tracer) WrapReadable(
|
||||||
|
ctx context.Context, r objstorage.Readable, fileNum base.DiskFileNum,
|
||||||
|
) objstorage.Readable {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapWritable wraps an objstorage.Writable with one that generates tracing
|
||||||
|
// events.
|
||||||
|
func (t *Tracer) WrapWritable(
|
||||||
|
ctx context.Context, w objstorage.Writable, fileNum base.DiskFileNum,
|
||||||
|
) objstorage.Writable {
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReason creates a context that has an associated Reason (which ends up in
|
||||||
|
// traces created under that context).
|
||||||
|
func WithReason(ctx context.Context, reason Reason) context.Context { return ctx }
|
||||||
|
|
||||||
|
// WithBlockType creates a context that has an associated BlockType (which ends up in
|
||||||
|
// traces created under that context).
|
||||||
|
func WithBlockType(ctx context.Context, blockType BlockType) context.Context { return ctx }
|
||||||
|
|
||||||
|
// WithLevel creates a context that has an associated level (which ends up in
|
||||||
|
// traces created under that context).
|
||||||
|
func WithLevel(ctx context.Context, level int) context.Context { return ctx }
|
||||||
410
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing/obj_io_tracing_on.go
generated
vendored
Normal file
410
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/objiotracing/obj_io_tracing_on.go
generated
vendored
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build pebble_obj_io_tracing
|
||||||
|
// +build pebble_obj_io_tracing
|
||||||
|
|
||||||
|
package objiotracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage"
|
||||||
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enabled is used to short circuit tracing-related code in regular builds.
|
||||||
|
const Enabled = true
|
||||||
|
|
||||||
|
// Tracer manages the writing of object IO traces to files.
|
||||||
|
//
|
||||||
|
// The tracer runs a background worker goroutine which receives trace event
|
||||||
|
// buffers over a channel and dumps them to IOTRACES- files. Wrapper
|
||||||
|
// implementations for Readable, ReadHandle, Writable are producers of traces;
|
||||||
|
// they maintain internal buffers of events which get flushed to the buffered
|
||||||
|
// channel when they get full. This allows for minimal synchronization per IO
|
||||||
|
// (as for most of these structures, an instance only allows a single IO at a
|
||||||
|
// time).
|
||||||
|
type Tracer struct {
|
||||||
|
fs vfs.FS
|
||||||
|
fsDir string
|
||||||
|
|
||||||
|
handleID atomic.Uint64
|
||||||
|
|
||||||
|
workerStopCh chan struct{}
|
||||||
|
workerDataCh chan eventBuf
|
||||||
|
workerWait sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open creates a Tracer which generates trace files in the given directory.
|
||||||
|
// Each trace file contains a series of Events (as they are in memory).
|
||||||
|
func Open(fs vfs.FS, fsDir string) *Tracer {
|
||||||
|
t := &Tracer{
|
||||||
|
fs: fs,
|
||||||
|
fsDir: fsDir,
|
||||||
|
workerStopCh: make(chan struct{}),
|
||||||
|
workerDataCh: make(chan eventBuf, channelBufSize),
|
||||||
|
}
|
||||||
|
|
||||||
|
t.handleID.Store(uint64(rand.NewSource(time.Now().UnixNano()).Int63()))
|
||||||
|
|
||||||
|
t.workerWait.Add(1)
|
||||||
|
go t.workerLoop()
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the tracer, flushing any remaining events.
|
||||||
|
func (t *Tracer) Close() {
|
||||||
|
if t.workerStopCh == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Tell the worker to stop and wait for it to finish up.
|
||||||
|
close(t.workerStopCh)
|
||||||
|
t.workerWait.Wait()
|
||||||
|
t.workerStopCh = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapWritable wraps an objstorage.Writable with one that generates tracing
|
||||||
|
// events.
|
||||||
|
func (t *Tracer) WrapWritable(
|
||||||
|
ctx context.Context, w objstorage.Writable, fileNum base.FileNum,
|
||||||
|
) objstorage.Writable {
|
||||||
|
return &writable{
|
||||||
|
w: w,
|
||||||
|
fileNum: fileNum,
|
||||||
|
g: makeEventGenerator(ctx, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type writable struct {
|
||||||
|
w objstorage.Writable
|
||||||
|
fileNum base.FileNum
|
||||||
|
curOffset int64
|
||||||
|
g eventGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ objstorage.Writable = (*writable)(nil)
|
||||||
|
|
||||||
|
// Write is part of the objstorage.Writable interface.
|
||||||
|
func (w *writable) Write(p []byte) error {
|
||||||
|
w.g.add(context.Background(), Event{
|
||||||
|
Op: WriteOp,
|
||||||
|
FileNum: w.fileNum,
|
||||||
|
Offset: w.curOffset,
|
||||||
|
Size: int64(len(p)),
|
||||||
|
})
|
||||||
|
// If w.w.Write(p) returns an error, a new writable
|
||||||
|
// will be used, so even tho all of p may not have
|
||||||
|
// been written to the underlying "file", it is okay
|
||||||
|
// to add len(p) to curOffset.
|
||||||
|
w.curOffset += int64(len(p))
|
||||||
|
return w.w.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish is part of the objstorage.Writable interface.
|
||||||
|
func (w *writable) Finish() error {
|
||||||
|
w.g.flush()
|
||||||
|
return w.w.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort is part of the objstorage.Writable interface.
|
||||||
|
func (w *writable) Abort() {
|
||||||
|
w.g.flush()
|
||||||
|
w.w.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapReadable wraps an objstorage.Readable with one that generates tracing
|
||||||
|
// events.
|
||||||
|
func (t *Tracer) WrapReadable(
|
||||||
|
ctx context.Context, r objstorage.Readable, fileNum base.FileNum,
|
||||||
|
) objstorage.Readable {
|
||||||
|
res := &readable{
|
||||||
|
r: r,
|
||||||
|
fileNum: fileNum,
|
||||||
|
}
|
||||||
|
res.mu.g = makeEventGenerator(ctx, t)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
type readable struct {
|
||||||
|
r objstorage.Readable
|
||||||
|
fileNum base.FileNum
|
||||||
|
mu struct {
|
||||||
|
sync.Mutex
|
||||||
|
g eventGenerator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ objstorage.Readable = (*readable)(nil)
|
||||||
|
|
||||||
|
// ReadAt is part of the objstorage.Readable interface.
|
||||||
|
func (r *readable) ReadAt(ctx context.Context, v []byte, off int64) (n int, err error) {
|
||||||
|
r.mu.Lock()
|
||||||
|
r.mu.g.add(ctx, Event{
|
||||||
|
Op: ReadOp,
|
||||||
|
FileNum: r.fileNum,
|
||||||
|
Offset: off,
|
||||||
|
Size: int64(len(v)),
|
||||||
|
})
|
||||||
|
r.mu.Unlock()
|
||||||
|
return r.r.ReadAt(ctx, v, off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is part of the objstorage.Readable interface.
|
||||||
|
func (r *readable) Close() error {
|
||||||
|
r.mu.g.flush()
|
||||||
|
return r.r.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size is part of the objstorage.Readable interface.
|
||||||
|
func (r *readable) Size() int64 {
|
||||||
|
return r.r.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReadHandle is part of the objstorage.Readable interface.
|
||||||
|
func (r *readable) NewReadHandle(ctx context.Context) objstorage.ReadHandle {
|
||||||
|
// It's safe to get the tracer from the generator without the mutex since it never changes.
|
||||||
|
t := r.mu.g.t
|
||||||
|
return &readHandle{
|
||||||
|
rh: r.r.NewReadHandle(ctx),
|
||||||
|
fileNum: r.fileNum,
|
||||||
|
handleID: t.handleID.Add(1),
|
||||||
|
g: makeEventGenerator(ctx, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type readHandle struct {
|
||||||
|
rh objstorage.ReadHandle
|
||||||
|
fileNum base.FileNum
|
||||||
|
handleID uint64
|
||||||
|
g eventGenerator
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ objstorage.ReadHandle = (*readHandle)(nil)
|
||||||
|
|
||||||
|
// ReadAt is part of the objstorage.ReadHandle interface.
|
||||||
|
func (rh *readHandle) ReadAt(ctx context.Context, p []byte, off int64) (n int, err error) {
|
||||||
|
rh.g.add(ctx, Event{
|
||||||
|
Op: ReadOp,
|
||||||
|
FileNum: rh.fileNum,
|
||||||
|
HandleID: rh.handleID,
|
||||||
|
Offset: off,
|
||||||
|
Size: int64(len(p)),
|
||||||
|
})
|
||||||
|
return rh.rh.ReadAt(ctx, p, off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is part of the objstorage.ReadHandle interface.
|
||||||
|
func (rh *readHandle) Close() error {
|
||||||
|
rh.g.flush()
|
||||||
|
return rh.rh.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupForCompaction is part of the objstorage.ReadHandle interface.
|
||||||
|
func (rh *readHandle) SetupForCompaction() {
|
||||||
|
rh.g.add(context.Background(), Event{
|
||||||
|
Op: SetupForCompactionOp,
|
||||||
|
FileNum: rh.fileNum,
|
||||||
|
HandleID: rh.handleID,
|
||||||
|
})
|
||||||
|
rh.rh.SetupForCompaction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecordCacheHit is part of the objstorage.ReadHandle interface.
|
||||||
|
func (rh *readHandle) RecordCacheHit(ctx context.Context, offset, size int64) {
|
||||||
|
rh.g.add(ctx, Event{
|
||||||
|
Op: RecordCacheHitOp,
|
||||||
|
FileNum: rh.fileNum,
|
||||||
|
HandleID: rh.handleID,
|
||||||
|
Offset: offset,
|
||||||
|
Size: size,
|
||||||
|
})
|
||||||
|
rh.rh.RecordCacheHit(ctx, offset, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ctxInfo struct {
|
||||||
|
reason Reason
|
||||||
|
blockType BlockType
|
||||||
|
levelPlusOne uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeCtxInfo(base, other ctxInfo) ctxInfo {
|
||||||
|
res := other
|
||||||
|
if res.reason == 0 {
|
||||||
|
res.reason = base.reason
|
||||||
|
}
|
||||||
|
if res.blockType == 0 {
|
||||||
|
res.blockType = base.blockType
|
||||||
|
}
|
||||||
|
if res.levelPlusOne == 0 {
|
||||||
|
res.levelPlusOne = base.levelPlusOne
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
type ctxInfoKey struct{}
|
||||||
|
|
||||||
|
func withInfo(ctx context.Context, info ctxInfo) context.Context {
|
||||||
|
return context.WithValue(ctx, ctxInfoKey{}, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func infoFromCtx(ctx context.Context) ctxInfo {
|
||||||
|
res := ctx.Value(ctxInfoKey{})
|
||||||
|
if res == nil {
|
||||||
|
return ctxInfo{}
|
||||||
|
}
|
||||||
|
return res.(ctxInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithReason creates a context that has an associated Reason (which ends up in
|
||||||
|
// traces created under that context).
|
||||||
|
func WithReason(ctx context.Context, reason Reason) context.Context {
|
||||||
|
info := infoFromCtx(ctx)
|
||||||
|
info.reason = reason
|
||||||
|
return withInfo(ctx, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBlockType creates a context that has an associated BlockType (which ends up in
|
||||||
|
// traces created under that context).
|
||||||
|
func WithBlockType(ctx context.Context, blockType BlockType) context.Context {
|
||||||
|
info := infoFromCtx(ctx)
|
||||||
|
info.blockType = blockType
|
||||||
|
return withInfo(ctx, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLevel creates a context that has an associated level (which ends up in
|
||||||
|
// traces created under that context).
|
||||||
|
func WithLevel(ctx context.Context, level int) context.Context {
|
||||||
|
info := infoFromCtx(ctx)
|
||||||
|
info.levelPlusOne = uint8(level) + 1
|
||||||
|
return withInfo(ctx, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
eventSize = int(unsafe.Sizeof(Event{}))
|
||||||
|
targetEntriesPerFile = 256 * 1024 * 1024 / eventSize // 256MB files
|
||||||
|
eventsPerBuf = 16
|
||||||
|
channelBufSize = 512 * 1024 / eventsPerBuf // 512K events.
|
||||||
|
bytesPerFileSync = 128 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
type eventBuf struct {
|
||||||
|
events [eventsPerBuf]Event
|
||||||
|
num int
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventGenerator struct {
|
||||||
|
t *Tracer
|
||||||
|
baseCtxInfo ctxInfo
|
||||||
|
buf eventBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeEventGenerator(ctx context.Context, t *Tracer) eventGenerator {
|
||||||
|
return eventGenerator{
|
||||||
|
t: t,
|
||||||
|
baseCtxInfo: infoFromCtx(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *eventGenerator) flush() {
|
||||||
|
if g.buf.num > 0 {
|
||||||
|
g.t.workerDataCh <- g.buf
|
||||||
|
g.buf.num = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *eventGenerator) add(ctx context.Context, e Event) {
|
||||||
|
e.StartUnixNano = time.Now().UnixNano()
|
||||||
|
info := infoFromCtx(ctx)
|
||||||
|
info = mergeCtxInfo(g.baseCtxInfo, info)
|
||||||
|
e.Reason = info.reason
|
||||||
|
e.BlockType = info.blockType
|
||||||
|
e.LevelPlusOne = info.levelPlusOne
|
||||||
|
if g.buf.num == eventsPerBuf {
|
||||||
|
g.flush()
|
||||||
|
}
|
||||||
|
g.buf.events[g.buf.num] = e
|
||||||
|
g.buf.num++
|
||||||
|
}
|
||||||
|
|
||||||
|
type workerState struct {
|
||||||
|
curFile vfs.File
|
||||||
|
curBW *bufio.Writer
|
||||||
|
numEntriesInFile int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tracer) workerLoop() {
|
||||||
|
defer t.workerWait.Done()
|
||||||
|
stopCh := t.workerStopCh
|
||||||
|
dataCh := t.workerDataCh
|
||||||
|
var state workerState
|
||||||
|
t.workerNewFile(&state)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopCh:
|
||||||
|
close(dataCh)
|
||||||
|
// Flush any remaining traces.
|
||||||
|
for data := range dataCh {
|
||||||
|
t.workerWriteTraces(&state, data)
|
||||||
|
}
|
||||||
|
t.workerCloseFile(&state)
|
||||||
|
return
|
||||||
|
|
||||||
|
case data := <-dataCh:
|
||||||
|
t.workerWriteTraces(&state, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tracer) workerWriteTraces(state *workerState, data eventBuf) {
|
||||||
|
if state.numEntriesInFile >= targetEntriesPerFile {
|
||||||
|
t.workerCloseFile(state)
|
||||||
|
t.workerNewFile(state)
|
||||||
|
}
|
||||||
|
state.numEntriesInFile += data.num
|
||||||
|
p := unsafe.Pointer(&data.events[0])
|
||||||
|
b := unsafe.Slice((*byte)(p), eventSize*data.num)
|
||||||
|
if _, err := state.curBW.Write(b); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tracer) workerNewFile(state *workerState) {
|
||||||
|
filename := fmt.Sprintf("IOTRACES-%s", time.Now().UTC().Format(time.RFC3339Nano))
|
||||||
|
|
||||||
|
file, err := t.fs.Create(t.fs.PathJoin(t.fsDir, filename))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
file = vfs.NewSyncingFile(file, vfs.SyncingFileOptions{
|
||||||
|
BytesPerSync: bytesPerFileSync,
|
||||||
|
})
|
||||||
|
state.curFile = file
|
||||||
|
state.curBW = bufio.NewWriter(file)
|
||||||
|
state.numEntriesInFile = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tracer) workerCloseFile(state *workerState) {
|
||||||
|
if state.curFile != nil {
|
||||||
|
if err := state.curBW.Flush(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := state.curFile.Sync(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := state.curFile.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
state.curFile = nil
|
||||||
|
state.curBW = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
522
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/provider.go
generated
vendored
Normal file
522
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/provider.go
generated
vendored
Normal file
@@ -0,0 +1,522 @@
|
|||||||
|
// 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"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"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/pebble/objstorage/objstorageprovider/objiotracing"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// provider is the implementation of objstorage.Provider.
|
||||||
|
type provider struct {
|
||||||
|
st Settings
|
||||||
|
|
||||||
|
fsDir vfs.File
|
||||||
|
|
||||||
|
tracer *objiotracing.Tracer
|
||||||
|
|
||||||
|
remote remoteSubsystem
|
||||||
|
|
||||||
|
mu struct {
|
||||||
|
sync.RWMutex
|
||||||
|
|
||||||
|
remote struct {
|
||||||
|
// catalogBatch accumulates remote object creations and deletions until
|
||||||
|
// Sync is called.
|
||||||
|
catalogBatch remoteobjcat.Batch
|
||||||
|
|
||||||
|
storageObjects map[remote.Locator]remote.Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
// localObjectsChanged is set if non-remote objects were created or deleted
|
||||||
|
// but Sync was not yet called.
|
||||||
|
localObjectsChanged bool
|
||||||
|
|
||||||
|
// knownObjects maintains information about objects that are known to the provider.
|
||||||
|
// It is initialized with the list of files in the manifest when we open a DB.
|
||||||
|
knownObjects map[base.DiskFileNum]objstorage.ObjectMetadata
|
||||||
|
|
||||||
|
// protectedObjects are objects that cannot be unreferenced because they
|
||||||
|
// have outstanding SharedObjectBackingHandles. The value is a count of outstanding handles
|
||||||
|
protectedObjects map[base.DiskFileNum]int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ objstorage.Provider = (*provider)(nil)
|
||||||
|
|
||||||
|
// Settings that must be specified when creating the provider.
|
||||||
|
type Settings struct {
|
||||||
|
Logger base.Logger
|
||||||
|
|
||||||
|
// Local filesystem configuration.
|
||||||
|
FS vfs.FS
|
||||||
|
FSDirName string
|
||||||
|
|
||||||
|
// FSDirInitialListing is a listing of FSDirName at the time of calling Open.
|
||||||
|
//
|
||||||
|
// This is an optional optimization to avoid double listing on Open when the
|
||||||
|
// higher layer already has a listing. When nil, we obtain the listing on
|
||||||
|
// Open.
|
||||||
|
FSDirInitialListing []string
|
||||||
|
|
||||||
|
// Cleaner cleans obsolete files from the local filesystem.
|
||||||
|
//
|
||||||
|
// The default cleaner uses the DeleteCleaner.
|
||||||
|
FSCleaner base.Cleaner
|
||||||
|
|
||||||
|
// NoSyncOnClose decides whether the implementation will enforce a
|
||||||
|
// close-time synchronization (e.g., fdatasync() or sync_file_range())
|
||||||
|
// on files it writes to. Setting this to true removes the guarantee for a
|
||||||
|
// sync on close. Some implementations can still issue a non-blocking sync.
|
||||||
|
NoSyncOnClose bool
|
||||||
|
|
||||||
|
// BytesPerSync enables periodic syncing of files in order to smooth out
|
||||||
|
// writes to disk. This option does not provide any persistence guarantee, but
|
||||||
|
// is used to avoid latency spikes if the OS automatically decides to write
|
||||||
|
// out a large chunk of dirty filesystem buffers.
|
||||||
|
BytesPerSync int
|
||||||
|
|
||||||
|
// Fields here are set only if the provider is to support remote objects
|
||||||
|
// (experimental).
|
||||||
|
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 remote storage),
|
||||||
|
// suitable for tests and tools.
|
||||||
|
func DefaultSettings(fs vfs.FS, dirName string) Settings {
|
||||||
|
return Settings{
|
||||||
|
Logger: base.DefaultLogger,
|
||||||
|
FS: fs,
|
||||||
|
FSDirName: dirName,
|
||||||
|
FSCleaner: base.DeleteCleaner{},
|
||||||
|
NoSyncOnClose: false,
|
||||||
|
BytesPerSync: 512 * 1024, // 512KB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open creates the provider.
|
||||||
|
func Open(settings Settings) (objstorage.Provider, error) {
|
||||||
|
// 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) {
|
||||||
|
fsDir, err := settings.FS.OpenDir(settings.FSDirName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if p == nil {
|
||||||
|
fsDir.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
p = &provider{
|
||||||
|
st: settings,
|
||||||
|
fsDir: fsDir,
|
||||||
|
}
|
||||||
|
p.mu.knownObjects = make(map[base.DiskFileNum]objstorage.ObjectMetadata)
|
||||||
|
p.mu.protectedObjects = make(map[base.DiskFileNum]int)
|
||||||
|
|
||||||
|
if objiotracing.Enabled {
|
||||||
|
p.tracer = objiotracing.Open(settings.FS, settings.FSDirName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add local FS objects.
|
||||||
|
if err := p.vfsInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize remote subsystem (if configured) and add remote objects.
|
||||||
|
if err := p.remoteInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is part of the objstorage.Provider interface.
|
||||||
|
func (p *provider) Close() error {
|
||||||
|
err := p.sharedClose()
|
||||||
|
if p.fsDir != nil {
|
||||||
|
err = firstError(err, p.fsDir.Close())
|
||||||
|
p.fsDir = nil
|
||||||
|
}
|
||||||
|
if objiotracing.Enabled {
|
||||||
|
if p.tracer != nil {
|
||||||
|
p.tracer.Close()
|
||||||
|
p.tracer = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenForReading opens an existing object.
|
||||||
|
func (p *provider) OpenForReading(
|
||||||
|
ctx context.Context,
|
||||||
|
fileType base.FileType,
|
||||||
|
fileNum base.DiskFileNum,
|
||||||
|
opts objstorage.OpenOptions,
|
||||||
|
) (objstorage.Readable, error) {
|
||||||
|
meta, err := p.Lookup(fileType, fileNum)
|
||||||
|
if err != nil {
|
||||||
|
if opts.MustExist {
|
||||||
|
p.st.Logger.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var r objstorage.Readable
|
||||||
|
if !meta.IsRemote() {
|
||||||
|
r, err = p.vfsOpenForReading(ctx, fileType, fileNum, opts)
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if objiotracing.Enabled {
|
||||||
|
r = p.tracer.WrapReadable(ctx, r, fileNum)
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create creates a new object and opens it for writing.
|
||||||
|
//
|
||||||
|
// The object is not guaranteed to be durable (accessible in case of crashes)
|
||||||
|
// until Sync is called.
|
||||||
|
func (p *provider) Create(
|
||||||
|
ctx context.Context,
|
||||||
|
fileType base.FileType,
|
||||||
|
fileNum base.DiskFileNum,
|
||||||
|
opts objstorage.CreateOptions,
|
||||||
|
) (w objstorage.Writable, meta objstorage.ObjectMetadata, err error) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrapf(err, "creating object %s", errors.Safe(fileNum))
|
||||||
|
return nil, objstorage.ObjectMetadata{}, err
|
||||||
|
}
|
||||||
|
p.addMetadata(meta)
|
||||||
|
if objiotracing.Enabled {
|
||||||
|
w = p.tracer.WrapWritable(ctx, w, fileNum)
|
||||||
|
}
|
||||||
|
return w, meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes an 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.
|
||||||
|
func (p *provider) Remove(fileType base.FileType, fileNum base.DiskFileNum) error {
|
||||||
|
meta, err := p.Lookup(fileType, fileNum)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !meta.IsRemote() {
|
||||||
|
err = p.vfsRemove(fileType, fileNum)
|
||||||
|
} else {
|
||||||
|
// 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.
|
||||||
|
// TODO(radu): we should mark the object as "zombie" and not allow any other
|
||||||
|
// operations.
|
||||||
|
return errors.Wrapf(err, "removing object %s", errors.Safe(fileNum))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.removeMetadata(fileNum)
|
||||||
|
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 {
|
||||||
|
// 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.
|
||||||
|
func (p *provider) Sync() error {
|
||||||
|
if err := p.vfsSync(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.sharedSync(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinkOrCopyFromLocal creates a new object that is either a copy of a given
|
||||||
|
// local file or a hard link (if the new object is created on the same FS, and
|
||||||
|
// if the FS supports it).
|
||||||
|
//
|
||||||
|
// The object is not guaranteed to be durable (accessible in case of crashes)
|
||||||
|
// until Sync is called.
|
||||||
|
func (p *provider) LinkOrCopyFromLocal(
|
||||||
|
ctx context.Context,
|
||||||
|
srcFS vfs.FS,
|
||||||
|
srcFilePath string,
|
||||||
|
dstFileType base.FileType,
|
||||||
|
dstFileNum base.DiskFileNum,
|
||||||
|
opts objstorage.CreateOptions,
|
||||||
|
) (objstorage.ObjectMetadata, error) {
|
||||||
|
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.
|
||||||
|
fs := vfs.NewSyncingFS(p.st.FS, vfs.SyncingFileOptions{
|
||||||
|
NoSyncOnClose: p.st.NoSyncOnClose,
|
||||||
|
BytesPerSync: p.st.BytesPerSync,
|
||||||
|
})
|
||||||
|
dstPath := p.vfsPath(dstFileType, dstFileNum)
|
||||||
|
if err := vfs.LinkOrCopy(fs, srcFilePath, dstPath); err != nil {
|
||||||
|
return objstorage.ObjectMetadata{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := objstorage.ObjectMetadata{
|
||||||
|
DiskFileNum: dstFileNum,
|
||||||
|
FileType: dstFileType,
|
||||||
|
}
|
||||||
|
p.addMetadata(meta)
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
// Create the object and copy the data.
|
||||||
|
w, meta, err := p.Create(ctx, dstFileType, dstFileNum, opts)
|
||||||
|
if err != nil {
|
||||||
|
return objstorage.ObjectMetadata{}, err
|
||||||
|
}
|
||||||
|
f, err := srcFS.Open(srcFilePath, vfs.SequentialReadsOption)
|
||||||
|
if err != nil {
|
||||||
|
return objstorage.ObjectMetadata{}, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
buf := make([]byte, 64*1024)
|
||||||
|
for {
|
||||||
|
n, readErr := f.Read(buf)
|
||||||
|
if readErr != nil && readErr != io.EOF {
|
||||||
|
w.Abort()
|
||||||
|
return objstorage.ObjectMetadata{}, readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > 0 {
|
||||||
|
if err := w.Write(buf[:n]); err != nil {
|
||||||
|
w.Abort()
|
||||||
|
return objstorage.ObjectMetadata{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if readErr == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := w.Finish(); err != nil {
|
||||||
|
return objstorage.ObjectMetadata{}, err
|
||||||
|
}
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup is part of the objstorage.Provider interface.
|
||||||
|
func (p *provider) Lookup(
|
||||||
|
fileType base.FileType, fileNum base.DiskFileNum,
|
||||||
|
) (objstorage.ObjectMetadata, error) {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
meta, ok := p.mu.knownObjects[fileNum]
|
||||||
|
if !ok {
|
||||||
|
return objstorage.ObjectMetadata{}, errors.Wrapf(
|
||||||
|
os.ErrNotExist,
|
||||||
|
"file %s (type %d) unknown to the objstorage provider",
|
||||||
|
errors.Safe(fileNum), errors.Safe(fileType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if meta.FileType != fileType {
|
||||||
|
return objstorage.ObjectMetadata{}, errors.AssertionFailedf(
|
||||||
|
"file %s type mismatch (known type %d, expected type %d)",
|
||||||
|
errors.Safe(fileNum), errors.Safe(meta.FileType), errors.Safe(fileType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path is part of the objstorage.Provider interface.
|
||||||
|
func (p *provider) Path(meta objstorage.ObjectMetadata) string {
|
||||||
|
if !meta.IsRemote() {
|
||||||
|
return p.vfsPath(meta.FileType, meta.DiskFileNum)
|
||||||
|
}
|
||||||
|
return p.remotePath(meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the size of the object.
|
||||||
|
func (p *provider) Size(meta objstorage.ObjectMetadata) (int64, error) {
|
||||||
|
if !meta.IsRemote() {
|
||||||
|
return p.vfsSize(meta.FileType, meta.DiskFileNum)
|
||||||
|
}
|
||||||
|
return p.remoteSize(meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is part of the objstorage.Provider interface.
|
||||||
|
func (p *provider) List() []objstorage.ObjectMetadata {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
res := make([]objstorage.ObjectMetadata, 0, len(p.mu.knownObjects))
|
||||||
|
for _, meta := range p.mu.knownObjects {
|
||||||
|
res = append(res, meta)
|
||||||
|
}
|
||||||
|
sort.Slice(res, func(i, j int) bool {
|
||||||
|
return res[i].DiskFileNum.FileNum() < res[j].DiskFileNum.FileNum()
|
||||||
|
})
|
||||||
|
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.IsRemote() {
|
||||||
|
p.mu.remote.catalogBatch.AddObject(remoteobjcat.RemoteObjectMetadata{
|
||||||
|
FileNum: meta.DiskFileNum,
|
||||||
|
FileType: meta.FileType,
|
||||||
|
CreatorID: meta.Remote.CreatorID,
|
||||||
|
CreatorFileNum: meta.Remote.CreatorFileNum,
|
||||||
|
Locator: meta.Remote.Locator,
|
||||||
|
CleanupMethod: meta.Remote.CleanupMethod,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
p.mu.localObjectsChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provider) removeMetadata(fileNum base.DiskFileNum) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
meta, ok := p.mu.knownObjects[fileNum]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(p.mu.knownObjects, fileNum)
|
||||||
|
if meta.IsRemote() {
|
||||||
|
p.mu.remote.catalogBatch.DeleteObject(fileNum)
|
||||||
|
} else {
|
||||||
|
p.mu.localObjectsChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// protectObject prevents the unreferencing of a remote object until
|
||||||
|
// unprotectObject is called.
|
||||||
|
func (p *provider) protectObject(fileNum base.DiskFileNum) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
p.mu.protectedObjects[fileNum] = p.mu.protectedObjects[fileNum] + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provider) unprotectObject(fileNum base.DiskFileNum) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
v := p.mu.protectedObjects[fileNum]
|
||||||
|
if invariants.Enabled && v == 0 {
|
||||||
|
panic("invalid protection count")
|
||||||
|
}
|
||||||
|
if v > 1 {
|
||||||
|
p.mu.protectedObjects[fileNum] = v - 1
|
||||||
|
} else {
|
||||||
|
delete(p.mu.protectedObjects, fileNum)
|
||||||
|
// TODO(radu): check if the object is still in knownObject; if not, unref it
|
||||||
|
// now.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provider) isProtected(fileNum base.DiskFileNum) bool {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
return p.mu.protectedObjects[fileNum] > 0
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
// of this source code is governed by a BSD-style license that can be found in
|
// of this source code is governed by a BSD-style license that can be found in
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
|
|
||||||
package objstorage
|
package objstorageprovider
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Constants for dynamic readahead of data blocks. Note that the size values
|
// Constants for dynamic readahead of data blocks. Note that the size values
|
||||||
@@ -11,15 +11,15 @@ const (
|
|||||||
minFileReadsForReadahead = 2
|
minFileReadsForReadahead = 2
|
||||||
// TODO(bilal): Have the initial size value be a factor of the block size,
|
// TODO(bilal): Have the initial size value be a factor of the block size,
|
||||||
// as opposed to a hardcoded value.
|
// as opposed to a hardcoded value.
|
||||||
initialReadaheadSize = 64 << 10 /* 64KB */
|
initialReadaheadSize = 64 << 10 /* 64KB */
|
||||||
maxReadaheadSize = 256 << 10 /* 256KB */
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// readaheadState contains state variables related to readahead. Updated on
|
// readaheadState contains state variables related to readahead. Updated on
|
||||||
// file reads.
|
// file reads.
|
||||||
type readaheadState struct {
|
type readaheadState struct {
|
||||||
// Number of sequential reads.
|
// Number of sequential reads.
|
||||||
numReads int64
|
numReads int64
|
||||||
|
maxReadaheadSize int64
|
||||||
// Size issued to the next call to Prefetch. Starts at or above
|
// Size issued to the next call to Prefetch. Starts at or above
|
||||||
// initialReadaheadSize and grows exponentially until maxReadaheadSize.
|
// initialReadaheadSize and grows exponentially until maxReadaheadSize.
|
||||||
size int64
|
size int64
|
||||||
@@ -32,22 +32,23 @@ type readaheadState struct {
|
|||||||
limit int64
|
limit int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeReadaheadState() readaheadState {
|
func makeReadaheadState(maxReadaheadSize int64) readaheadState {
|
||||||
return readaheadState{
|
return readaheadState{
|
||||||
size: initialReadaheadSize,
|
size: initialReadaheadSize,
|
||||||
|
maxReadaheadSize: maxReadaheadSize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *readaheadState) recordCacheHit(offset, blockLength int64) {
|
func (rs *readaheadState) recordCacheHit(offset, blockLength int64) {
|
||||||
currentReadEnd := offset + blockLength
|
currentReadEnd := offset + blockLength
|
||||||
if rs.numReads >= minFileReadsForReadahead {
|
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
|
// This is a read that would have resulted in a readahead, had it
|
||||||
// not been a cache hit.
|
// not been a cache hit.
|
||||||
rs.limit = currentReadEnd
|
rs.limit = currentReadEnd
|
||||||
return
|
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
|
// We read too far away from rs.limit to benefit from readahead in
|
||||||
// any scenario. Reset all variables.
|
// any scenario. Reset all variables.
|
||||||
rs.numReads = 1
|
rs.numReads = 1
|
||||||
@@ -61,7 +62,7 @@ func (rs *readaheadState) recordCacheHit(offset, blockLength int64) {
|
|||||||
// readahead.
|
// readahead.
|
||||||
return
|
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
|
// Blocks are being read sequentially and would benefit from readahead
|
||||||
// down the line.
|
// down the line.
|
||||||
rs.numReads++
|
rs.numReads++
|
||||||
@@ -89,28 +90,28 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
|||||||
// as well as the interval where a read would benefit from read ahead:
|
// as well as the interval where a read would benefit from read ahead:
|
||||||
// [rs.limit, rs.limit + rs.size]
|
// [rs.limit, rs.limit + rs.size]
|
||||||
// We increase the latter interval to
|
// 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
|
// readahead may not be beneficial with a small readahead size, but over
|
||||||
// time the readahead size would increase exponentially to make it
|
// time the readahead size would increase exponentially to make it
|
||||||
// beneficial.
|
// 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
|
// We are doing a read in the interval ahead of
|
||||||
// the last readahead range. In the diagrams below, ++++ is the last
|
// the last readahead range. In the diagrams below, ++++ is the last
|
||||||
// readahead range, ==== is the range represented by
|
// 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.
|
// being read.
|
||||||
//
|
//
|
||||||
// rs.limit rs.limit + maxReadaheadSize
|
// rs.limit rs.limit + rs.maxReadaheadSize
|
||||||
// ++++++++++|===========================|
|
// ++++++++++|===========================|
|
||||||
//
|
//
|
||||||
// |-------------|
|
// |-------------|
|
||||||
// offset currentReadEnd
|
// offset currentReadEnd
|
||||||
//
|
//
|
||||||
// This case is also possible, as are all cases with an overlap
|
// 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]:
|
// currentReadEnd]:
|
||||||
//
|
//
|
||||||
// rs.limit rs.limit + maxReadaheadSize
|
// rs.limit rs.limit + rs.maxReadaheadSize
|
||||||
// ++++++++++|===========================|
|
// ++++++++++|===========================|
|
||||||
//
|
//
|
||||||
// |-------------|
|
// |-------------|
|
||||||
@@ -122,19 +123,19 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
|||||||
rs.prevSize = rs.size
|
rs.prevSize = rs.size
|
||||||
// Increase rs.size for the next read.
|
// Increase rs.size for the next read.
|
||||||
rs.size *= 2
|
rs.size *= 2
|
||||||
if rs.size > maxReadaheadSize {
|
if rs.size > rs.maxReadaheadSize {
|
||||||
rs.size = maxReadaheadSize
|
rs.size = rs.maxReadaheadSize
|
||||||
}
|
}
|
||||||
return rs.prevSize
|
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
|
// The above conditional has rs.limit > rs.prevSize to confirm that
|
||||||
// rs.limit - rs.prevSize would not underflow.
|
// rs.limit - rs.prevSize would not underflow.
|
||||||
// We read too far away from rs.limit to benefit from readahead in
|
// We read too far away from rs.limit to benefit from readahead in
|
||||||
// any scenario. Reset all variables.
|
// any scenario. Reset all variables.
|
||||||
// The case where we read too far ahead:
|
// 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 +143,7 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
|||||||
//
|
//
|
||||||
// Or too far behind:
|
// 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 +161,7 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
|||||||
// readahead, but there's no reason to issue a readahead call at the
|
// readahead, but there's no reason to issue a readahead call at the
|
||||||
// moment.
|
// moment.
|
||||||
//
|
//
|
||||||
// (rs.limit - rs.prevSize) (rs.limit + maxReadaheadSize)
|
// (rs.limit - rs.prevSize) (rs.limit + rs.maxReadaheadSize)
|
||||||
// |+++++++++++++|===============|
|
// |+++++++++++++|===============|
|
||||||
// (rs.limit)
|
// (rs.limit)
|
||||||
//
|
//
|
||||||
@@ -170,11 +171,11 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
|||||||
rs.numReads++
|
rs.numReads++
|
||||||
return 0
|
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
|
// Blocks are being read sequentially and would benefit from readahead
|
||||||
// down the line.
|
// down the line.
|
||||||
//
|
//
|
||||||
// (rs.limit) (rs.limit + maxReadaheadSize)
|
// (rs.limit) (rs.limit + rs.maxReadaheadSize)
|
||||||
// |=============|
|
// |=============|
|
||||||
//
|
//
|
||||||
// |-------|
|
// |-------|
|
||||||
@@ -186,7 +187,7 @@ func (rs *readaheadState) maybeReadahead(offset, blockLength int64) int64 {
|
|||||||
// We read too far ahead of the last read, or before it. This indicates
|
// 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.
|
// 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)
|
||||||
|
}
|
||||||
303
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote_backing.go
generated
vendored
Normal file
303
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote_backing.go
generated
vendored
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
// 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 (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tagCreatorID = 1
|
||||||
|
tagCreatorFileNum = 2
|
||||||
|
tagCleanupMethod = 3
|
||||||
|
// tagRefCheckID encodes the information for a ref marker that needs to be
|
||||||
|
// checked when attaching this object to another provider. This is set to the
|
||||||
|
// creator ID and FileNum for the provider that encodes the backing, and
|
||||||
|
// 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).
|
||||||
|
|
||||||
|
// Any new tags that have the tagNotSafeToIgnoreMask bit set cause errors if
|
||||||
|
// they are encountered by earlier code that doesn't know the tag.
|
||||||
|
tagNotSafeToIgnoreMask = 64
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *provider) encodeRemoteObjectBacking(
|
||||||
|
meta *objstorage.ObjectMetadata,
|
||||||
|
) (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.Remote.CreatorID))
|
||||||
|
// TODO(radu): encode file type as well?
|
||||||
|
buf = binary.AppendUvarint(buf, tagCreatorFileNum)
|
||||||
|
buf = binary.AppendUvarint(buf, uint64(meta.Remote.CreatorFileNum.FileNum()))
|
||||||
|
buf = binary.AppendUvarint(buf, tagCleanupMethod)
|
||||||
|
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.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 remoteObjectBackingHandle struct {
|
||||||
|
backing objstorage.RemoteObjectBacking
|
||||||
|
fileNum base.DiskFileNum
|
||||||
|
p *provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *remoteObjectBackingHandle) Get() (objstorage.RemoteObjectBacking, error) {
|
||||||
|
if s.backing == nil {
|
||||||
|
return nil, errors.Errorf("RemoteObjectBackingHandle.Get() called after Close()")
|
||||||
|
}
|
||||||
|
return s.backing, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *remoteObjectBackingHandle) Close() {
|
||||||
|
if s.backing != nil {
|
||||||
|
s.backing = nil
|
||||||
|
s.p.unprotectObject(s.fileNum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ objstorage.RemoteObjectBackingHandle = (*remoteObjectBackingHandle)(nil)
|
||||||
|
|
||||||
|
// RemoteObjectBacking is part of the objstorage.Provider interface.
|
||||||
|
func (p *provider) RemoteObjectBacking(
|
||||||
|
meta *objstorage.ObjectMetadata,
|
||||||
|
) (objstorage.RemoteObjectBackingHandle, error) {
|
||||||
|
backing, err := p.encodeRemoteObjectBacking(meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.protectObject(meta.DiskFileNum)
|
||||||
|
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.Remote.CleanupMethod is RefTracking
|
||||||
|
refToCheck struct {
|
||||||
|
creatorID objstorage.CreatorID
|
||||||
|
fileNum base.DiskFileNum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return decodedBacking{}, err
|
||||||
|
}
|
||||||
|
switch tag {
|
||||||
|
case tagCreatorID:
|
||||||
|
creatorID, err = binary.ReadUvarint(br)
|
||||||
|
|
||||||
|
case tagCreatorFileNum:
|
||||||
|
creatorFileNum, err = binary.ReadUvarint(br)
|
||||||
|
|
||||||
|
case tagCleanupMethod:
|
||||||
|
cleanupMethod, err = binary.ReadUvarint(br)
|
||||||
|
|
||||||
|
case tagRefCheckID:
|
||||||
|
refCheckCreatorID, err = binary.ReadUvarint(br)
|
||||||
|
if err == nil {
|
||||||
|
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 {
|
||||||
|
return decodedBacking{}, errors.Newf("unknown tag %d", tag)
|
||||||
|
}
|
||||||
|
var dataLen uint64
|
||||||
|
dataLen, err = binary.ReadUvarint(br)
|
||||||
|
if err == nil {
|
||||||
|
_, err = br.Seek(int64(dataLen), io.SeekCurrent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return decodedBacking{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.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("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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the reference marker objects.
|
||||||
|
// TODO(radu): parallelize this.
|
||||||
|
for _, d := range decoded {
|
||||||
|
if d.meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := p.sharedCreateRef(d.meta); err != nil {
|
||||||
|
// TODO(radu): clean up references previously created in this loop.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Check the "origin"'s reference.
|
||||||
|
refName := sharedObjectRefName(d.meta, d.refToCheck.creatorID, d.refToCheck.fileNum)
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(err, "checking origin's marker object %s", refName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func() {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
for _, d := range decoded {
|
||||||
|
p.mu.remote.catalogBatch.AddObject(remoteobjcat.RemoteObjectMetadata{
|
||||||
|
FileNum: d.meta.DiskFileNum,
|
||||||
|
FileType: d.meta.FileType,
|
||||||
|
CreatorID: d.meta.Remote.CreatorID,
|
||||||
|
CreatorFileNum: d.meta.Remote.CreatorFileNum,
|
||||||
|
CleanupMethod: d.meta.Remote.CleanupMethod,
|
||||||
|
Locator: d.meta.Remote.Locator,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err := p.sharedSync(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
metas := make([]objstorage.ObjectMetadata, len(decoded))
|
||||||
|
for i, d := range decoded {
|
||||||
|
metas[i] = d.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
for _, meta := range metas {
|
||||||
|
p.mu.knownObjects[meta.DiskFileNum] = meta
|
||||||
|
}
|
||||||
|
return metas, nil
|
||||||
|
}
|
||||||
91
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote_obj_name.go
generated
vendored
Normal file
91
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remote_obj_name.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 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.Remote.CreatorID, meta.Remote.CreatorFileNum.FileNum(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
panic("unknown FileType")
|
||||||
|
}
|
||||||
|
|
||||||
|
// sharedObjectRefName returns the name of the object's ref marker associated
|
||||||
|
// with a given referencing provider. This name is the object's name concatenated with
|
||||||
|
// ".ref.<ref-creator-id>.<local-file-num>".
|
||||||
|
//
|
||||||
|
// For example: 1a3f-2-000001.sst.ref.5.000008
|
||||||
|
func sharedObjectRefName(
|
||||||
|
meta objstorage.ObjectMetadata, refCreatorID objstorage.CreatorID, refFileNum base.DiskFileNum,
|
||||||
|
) string {
|
||||||
|
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.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.Remote.CreatorID, meta.Remote.CreatorFileNum.FileNum(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
panic("unknown FileType")
|
||||||
|
}
|
||||||
|
|
||||||
|
// sharedObjectRefName returns the name of the object's ref marker associated
|
||||||
|
// with this provider. This name is the object's name concatenated with
|
||||||
|
// ".ref.<creator-id>.<local-file-num>".
|
||||||
|
//
|
||||||
|
// For example: 1a3f-2-000001.sst.ref.5.000008
|
||||||
|
func (p *provider) sharedObjectRefName(meta objstorage.ObjectMetadata) string {
|
||||||
|
if meta.Remote.CleanupMethod != objstorage.SharedRefTracking {
|
||||||
|
panic("ref object used when ref tracking disabled")
|
||||||
|
}
|
||||||
|
return sharedObjectRefName(meta, p.remote.shared.creatorID, meta.DiskFileNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// objHash returns a 16-bit hash value derived from the creator ID and creator
|
||||||
|
// file num. We prepend this value to object names to ensure balanced
|
||||||
|
// partitioning with AWS (and likely other blob storage providers).
|
||||||
|
func objHash(meta objstorage.ObjectMetadata) uint16 {
|
||||||
|
const prime1 = 7459
|
||||||
|
const prime2 = 17539
|
||||||
|
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
|
// of this source code is governed by a BSD-style license that can be found in
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
|
|
||||||
package sharedobjcat
|
package remoteobjcat
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -12,23 +12,25 @@ import (
|
|||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"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/record"
|
||||||
"github.com/cockroachdb/pebble/vfs"
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
"github.com/cockroachdb/pebble/vfs/atomicfs"
|
"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
|
// The catalog file is a log of records, where each record is an encoded
|
||||||
// versionEdit.
|
// VersionEdit.
|
||||||
type Catalog struct {
|
type Catalog struct {
|
||||||
fs vfs.FS
|
fs vfs.FS
|
||||||
dirname string
|
dirname string
|
||||||
mu struct {
|
mu struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
|
||||||
creatorID CreatorID
|
creatorID objstorage.CreatorID
|
||||||
objects map[base.FileNum]SharedObjectMetadata
|
objects map[base.DiskFileNum]RemoteObjectMetadata
|
||||||
|
|
||||||
marker *atomicfs.Marker
|
marker *atomicfs.Marker
|
||||||
|
|
||||||
@@ -43,44 +45,41 @@ type Catalog struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatorID identifies the DB instance that originally created a shared object.
|
// RemoteObjectMetadata encapsulates the data stored in the catalog file for each object.
|
||||||
// This ID is incorporated in backing object names.
|
type RemoteObjectMetadata struct {
|
||||||
// Must be non-zero.
|
|
||||||
type CreatorID uint64
|
|
||||||
|
|
||||||
// IsSet returns true if the CreatorID is not zero.
|
|
||||||
func (c CreatorID) IsSet() bool { return c != 0 }
|
|
||||||
|
|
||||||
func (c CreatorID) String() string { return fmt.Sprintf("%020d", c) }
|
|
||||||
|
|
||||||
// SharedObjectMetadata encapsulates the data stored in the catalog file for each object.
|
|
||||||
type SharedObjectMetadata struct {
|
|
||||||
// FileNum is the identifier for the object within the context of a single DB
|
// FileNum is the identifier for the object within the context of a single DB
|
||||||
// instance.
|
// instance.
|
||||||
FileNum base.FileNum
|
FileNum base.DiskFileNum
|
||||||
// FileType is the type of the object. Only certain FileTypes are possible.
|
// FileType is the type of the object. Only certain FileTypes are possible.
|
||||||
FileType base.FileType
|
FileType base.FileType
|
||||||
// CreatorID identifies the DB instance that originally created the object.
|
// CreatorID identifies the DB instance that originally created the object.
|
||||||
CreatorID CreatorID
|
CreatorID objstorage.CreatorID
|
||||||
// CreatorFileNum is the identifier for the object within the context of the
|
// CreatorFileNum is the identifier for the object within the context of the
|
||||||
// DB instance that originally created the object.
|
// DB instance that originally created the object.
|
||||||
CreatorFileNum base.FileNum
|
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 (
|
const (
|
||||||
catalogFilenameBase = "SHARED-CATALOG"
|
catalogFilenameBase = "REMOTE-OBJ-CATALOG"
|
||||||
catalogMarkerName = "shared-catalog"
|
catalogMarkerName = "remote-obj-catalog"
|
||||||
|
|
||||||
// We create a new file when the size exceeds 1MB (and some other conditions
|
// We create a new file when the size exceeds 1MB (and some other conditions
|
||||||
// hold; see record.RotationHelper).
|
// hold; see record.RotationHelper).
|
||||||
rotateFileSize = 1024 * 1024 // 1MB
|
rotateFileSize = 1024 * 1024 // 1MB
|
||||||
)
|
)
|
||||||
|
|
||||||
// CatalogContents contains the shared objects in the catalog.
|
// CatalogContents contains the remote objects in the catalog.
|
||||||
type CatalogContents struct {
|
type CatalogContents struct {
|
||||||
// CreatorID, if it is set.
|
// CreatorID, if it is set.
|
||||||
CreatorID CreatorID
|
CreatorID objstorage.CreatorID
|
||||||
Objects []SharedObjectMetadata
|
Objects []RemoteObjectMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open creates a Catalog and loads any existing catalog file, returning the
|
// Open creates a Catalog and loads any existing catalog file, returning the
|
||||||
@@ -90,7 +89,7 @@ func Open(fs vfs.FS, dirname string) (*Catalog, CatalogContents, error) {
|
|||||||
fs: fs,
|
fs: fs,
|
||||||
dirname: dirname,
|
dirname: dirname,
|
||||||
}
|
}
|
||||||
c.mu.objects = make(map[base.FileNum]SharedObjectMetadata)
|
c.mu.objects = make(map[base.DiskFileNum]RemoteObjectMetadata)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
c.mu.marker, c.mu.catalogFilename, err = atomicfs.LocateMarker(fs, dirname, catalogMarkerName)
|
c.mu.marker, c.mu.catalogFilename, err = atomicfs.LocateMarker(fs, dirname, catalogMarkerName)
|
||||||
@@ -109,20 +108,20 @@ func Open(fs vfs.FS, dirname string) (*Catalog, CatalogContents, error) {
|
|||||||
}
|
}
|
||||||
res := CatalogContents{
|
res := CatalogContents{
|
||||||
CreatorID: c.mu.creatorID,
|
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 {
|
for _, meta := range c.mu.objects {
|
||||||
res.Objects = append(res.Objects, meta)
|
res.Objects = append(res.Objects, meta)
|
||||||
}
|
}
|
||||||
// Sort the objects so the function is deterministic.
|
// Sort the objects so the function is deterministic.
|
||||||
sort.Slice(res.Objects, func(i, j int) bool {
|
sort.Slice(res.Objects, func(i, j int) bool {
|
||||||
return res.Objects[i].FileNum < res.Objects[j].FileNum
|
return res.Objects[i].FileNum.FileNum() < res.Objects[j].FileNum.FileNum()
|
||||||
})
|
})
|
||||||
return c, res, nil
|
return c, res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCreatorID sets the creator ID. If it is already set, it must match.
|
// SetCreatorID sets the creator ID. If it is already set, it must match.
|
||||||
func (c *Catalog) SetCreatorID(id CreatorID) error {
|
func (c *Catalog) SetCreatorID(id objstorage.CreatorID) error {
|
||||||
if !id.IsSet() {
|
if !id.IsSet() {
|
||||||
return errors.AssertionFailedf("attempt to unset CreatorID")
|
return errors.AssertionFailedf("attempt to unset CreatorID")
|
||||||
}
|
}
|
||||||
@@ -137,9 +136,9 @@ func (c *Catalog) SetCreatorID(id CreatorID) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ve := versionEdit{CreatorID: id}
|
ve := VersionEdit{CreatorID: id}
|
||||||
if err := c.writeToCatalogFileLocked(&ve); err != nil {
|
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
|
c.mu.creatorID = id
|
||||||
return nil
|
return nil
|
||||||
@@ -166,19 +165,19 @@ func (c *Catalog) closeCatalogFile() error {
|
|||||||
|
|
||||||
// Batch is used to perform multiple object additions/deletions at once.
|
// Batch is used to perform multiple object additions/deletions at once.
|
||||||
type Batch struct {
|
type Batch struct {
|
||||||
ve versionEdit
|
ve VersionEdit
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddObject adds a new object to the batch.
|
// AddObject adds a new object to the batch.
|
||||||
//
|
//
|
||||||
// The given FileNum must be new - it must not match that of any object that was
|
// The given FileNum must be new - it must not match that of any object that was
|
||||||
// ever in the catalog.
|
// ever in the catalog.
|
||||||
func (b *Batch) AddObject(meta SharedObjectMetadata) {
|
func (b *Batch) AddObject(meta RemoteObjectMetadata) {
|
||||||
b.ve.NewObjects = append(b.ve.NewObjects, meta)
|
b.ve.NewObjects = append(b.ve.NewObjects, meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteObject adds an object removal to the batch.
|
// DeleteObject adds an object removal to the batch.
|
||||||
func (b *Batch) DeleteObject(fileNum base.FileNum) {
|
func (b *Batch) DeleteObject(fileNum base.DiskFileNum) {
|
||||||
b.ve.DeletedObjects = append(b.ve.DeletedObjects, fileNum)
|
b.ve.DeletedObjects = append(b.ve.DeletedObjects, fileNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,11 +196,11 @@ func (b *Batch) IsEmpty() bool {
|
|||||||
func (b *Batch) Copy() Batch {
|
func (b *Batch) Copy() Batch {
|
||||||
var res Batch
|
var res Batch
|
||||||
if len(b.ve.NewObjects) > 0 {
|
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)
|
copy(res.ve.NewObjects, b.ve.NewObjects)
|
||||||
}
|
}
|
||||||
if len(b.ve.DeletedObjects) > 0 {
|
if len(b.ve.DeletedObjects) > 0 {
|
||||||
res.ve.DeletedObjects = make([]base.FileNum, len(b.ve.DeletedObjects))
|
res.ve.DeletedObjects = make([]base.DiskFileNum, len(b.ve.DeletedObjects))
|
||||||
copy(res.ve.DeletedObjects, b.ve.DeletedObjects)
|
copy(res.ve.DeletedObjects, b.ve.DeletedObjects)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
@@ -219,29 +218,40 @@ func (c *Catalog) ApplyBatch(b Batch) error {
|
|||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
for _, n := range b.ve.DeletedObjects {
|
// Sanity checks.
|
||||||
if _, exists := c.mu.objects[n]; !exists {
|
toAdd := make(map[base.DiskFileNum]struct{}, len(b.ve.NewObjects))
|
||||||
return errors.AssertionFailedf("deleting non-existent object %s", n)
|
exists := func(n base.DiskFileNum) bool {
|
||||||
|
_, ok := c.mu.objects[n]
|
||||||
|
if !ok {
|
||||||
|
_, ok = toAdd[n]
|
||||||
}
|
}
|
||||||
|
return ok
|
||||||
}
|
}
|
||||||
for _, meta := range b.ve.NewObjects {
|
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)
|
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 {
|
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.
|
// Add new objects before deleting any objects. This allows for cases where
|
||||||
for _, n := range b.ve.DeletedObjects {
|
// the same batch adds and deletes an object.
|
||||||
delete(c.mu.objects, n)
|
|
||||||
}
|
|
||||||
for _, meta := range b.ve.NewObjects {
|
for _, meta := range b.ve.NewObjects {
|
||||||
c.mu.objects[meta.FileNum] = meta
|
c.mu.objects[meta.FileNum] = meta
|
||||||
}
|
}
|
||||||
b.Reset()
|
for _, n := range b.ve.DeletedObjects {
|
||||||
|
delete(c.mu.objects, n)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +260,7 @@ func (c *Catalog) loadFromCatalogFile(filename string) error {
|
|||||||
f, err := c.fs.Open(catalogPath)
|
f, err := c.fs.Open(catalogPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(
|
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,
|
errors.Safe(filename), c.dirname,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -262,32 +272,26 @@ func (c *Catalog) loadFromCatalogFile(filename string) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
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))
|
errors.Safe(filename))
|
||||||
}
|
}
|
||||||
var ve versionEdit
|
var ve VersionEdit
|
||||||
err = ve.Decode(r)
|
if err := ve.Decode(r); err != nil {
|
||||||
if err != nil {
|
return errors.Wrapf(err, "pebble: error when loading remote object catalog file %q",
|
||||||
return errors.Wrapf(err, "pebble: error when loading shared object catalog file %q",
|
|
||||||
errors.Safe(filename))
|
errors.Safe(filename))
|
||||||
}
|
}
|
||||||
// Apply the version edit to the current state.
|
// Apply the version edit to the current state.
|
||||||
if ve.CreatorID.IsSet() {
|
if err := ve.Apply(&c.mu.creatorID, c.mu.objects); err != nil {
|
||||||
c.mu.creatorID = ve.CreatorID
|
return errors.Wrapf(err, "pebble: error when loading remote object catalog file %q",
|
||||||
}
|
errors.Safe(filename))
|
||||||
for _, fileNum := range ve.DeletedObjects {
|
|
||||||
delete(c.mu.objects, fileNum)
|
|
||||||
}
|
|
||||||
for _, meta := range ve.NewObjects {
|
|
||||||
c.mu.objects[meta.FileNum] = meta
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
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.
|
// 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)))
|
c.mu.rotationHelper.AddRecord(int64(len(ve.NewObjects) + len(ve.DeletedObjects)))
|
||||||
snapshotSize := int64(len(c.mu.objects))
|
snapshotSize := int64(len(c.mu.objects))
|
||||||
|
|
||||||
@@ -330,9 +334,10 @@ func (c *Catalog) createNewCatalogFileLocked() (outErr error) {
|
|||||||
}
|
}
|
||||||
recWriter := record.NewWriter(file)
|
recWriter := record.NewWriter(file)
|
||||||
err = func() error {
|
err = func() error {
|
||||||
// Create a versionEdit that gets us from an empty catalog to the current state.
|
// Create a VersionEdit that gets us from an empty catalog to the current state.
|
||||||
var ve versionEdit
|
var ve VersionEdit
|
||||||
ve.NewObjects = make([]SharedObjectMetadata, 0, len(c.mu.objects))
|
ve.CreatorID = c.mu.creatorID
|
||||||
|
ve.NewObjects = make([]RemoteObjectMetadata, 0, len(c.mu.objects))
|
||||||
for _, meta := range c.mu.objects {
|
for _, meta := range c.mu.objects {
|
||||||
ve.NewObjects = append(ve.NewObjects, meta)
|
ve.NewObjects = append(ve.NewObjects, meta)
|
||||||
}
|
}
|
||||||
@@ -367,7 +372,7 @@ func (c *Catalog) createNewCatalogFileLocked() (outErr error) {
|
|||||||
return nil
|
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()
|
w, err := recWriter.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
254
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat/version_edit.go
generated
vendored
Normal file
254
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/remoteobjcat/version_edit.go
generated
vendored
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
// 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 remoteobjcat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"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 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 []RemoteObjectMetadata
|
||||||
|
DeletedObjects []base.DiskFileNum
|
||||||
|
CreatorID objstorage.CreatorID
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 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
|
||||||
|
// more general (and we want freedom to change it in the future).
|
||||||
|
const (
|
||||||
|
objTypeTable = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
func objTypeToFileType(objType uint64) (base.FileType, error) {
|
||||||
|
switch objType {
|
||||||
|
case objTypeTable:
|
||||||
|
return base.FileTypeTable, nil
|
||||||
|
default:
|
||||||
|
return 0, errors.Newf("unknown object type %d", objType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileTypeToObjType(fileType base.FileType) (uint64, error) {
|
||||||
|
switch fileType {
|
||||||
|
case base.FileTypeTable:
|
||||||
|
return objTypeTable, nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0, errors.Newf("unknown object type for file type %d", fileType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)*10+len(v.DeletedObjects)*2+2))
|
||||||
|
for _, meta := range v.NewObjects {
|
||||||
|
objType, err := fileTypeToObjType(meta.FileType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf = binary.AppendUvarint(buf, uint64(tagNewObject))
|
||||||
|
buf = binary.AppendUvarint(buf, uint64(meta.FileNum.FileNum()))
|
||||||
|
buf = binary.AppendUvarint(buf, objType)
|
||||||
|
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 {
|
||||||
|
buf = binary.AppendUvarint(buf, uint64(tagDeletedObject))
|
||||||
|
buf = binary.AppendUvarint(buf, uint64(dfn.FileNum()))
|
||||||
|
}
|
||||||
|
if v.CreatorID.IsSet() {
|
||||||
|
buf = binary.AppendUvarint(buf, uint64(tagCreatorID))
|
||||||
|
buf = binary.AppendUvarint(buf, uint64(v.CreatorID))
|
||||||
|
}
|
||||||
|
_, err := w.Write(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes an edit from the specified reader.
|
||||||
|
func (v *VersionEdit) Decode(r io.Reader) error {
|
||||||
|
br, ok := r.(io.ByteReader)
|
||||||
|
if !ok {
|
||||||
|
br = bufio.NewReader(r)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
tag, err := binary.ReadUvarint(br)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
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 {
|
||||||
|
var objType uint64
|
||||||
|
objType, err = binary.ReadUvarint(br)
|
||||||
|
if err == nil {
|
||||||
|
fileType, err = objTypeToFileType(objType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
creatorID, err = binary.ReadUvarint(br)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
creatorFileNum, err = binary.ReadUvarint(br)
|
||||||
|
}
|
||||||
|
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, 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
case tagDeletedObject:
|
||||||
|
var fileNum uint64
|
||||||
|
fileNum, err = binary.ReadUvarint(br)
|
||||||
|
if err == nil {
|
||||||
|
v.DeletedObjects = append(v.DeletedObjects, base.FileNum(fileNum).DiskFileNum())
|
||||||
|
}
|
||||||
|
|
||||||
|
case tagCreatorID:
|
||||||
|
var id uint64
|
||||||
|
id, err = binary.ReadUvarint(br)
|
||||||
|
if err == nil {
|
||||||
|
v.CreatorID = objstorage.CreatorID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = errors.Newf("unknown tag %d", tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return errCorruptCatalog
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
65
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_writable.go
generated
vendored
Normal file
65
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/shared_writable.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// 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 (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble/objstorage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewRemoteWritable creates an objstorage.Writable out of an io.WriteCloser.
|
||||||
|
func NewRemoteWritable(obj io.WriteCloser) objstorage.Writable {
|
||||||
|
return &sharedWritable{storageWriter: obj}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sharedWritable is a very simple implementation of Writable on top of the
|
||||||
|
// WriteCloser returned by remote.Storage.CreateObject.
|
||||||
|
type sharedWritable struct {
|
||||||
|
// Either both p and meta must be unset / zero values, or both must be set.
|
||||||
|
// The case where both are unset is true only in tests.
|
||||||
|
p *provider
|
||||||
|
meta objstorage.ObjectMetadata
|
||||||
|
storageWriter io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ objstorage.Writable = (*sharedWritable)(nil)
|
||||||
|
|
||||||
|
// Write is part of the Writable interface.
|
||||||
|
func (w *sharedWritable) Write(p []byte) error {
|
||||||
|
_, err := w.storageWriter.Write(p)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish is part of the Writable interface.
|
||||||
|
func (w *sharedWritable) Finish() error {
|
||||||
|
err := w.storageWriter.Close()
|
||||||
|
w.storageWriter = nil
|
||||||
|
if err != nil {
|
||||||
|
w.Abort()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the marker object.
|
||||||
|
if w.p != nil {
|
||||||
|
if err := w.p.sharedCreateRef(w.meta); err != nil {
|
||||||
|
w.Abort()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort is part of the Writable interface.
|
||||||
|
func (w *sharedWritable) Abort() {
|
||||||
|
if w.storageWriter != nil {
|
||||||
|
_ = w.storageWriter.Close()
|
||||||
|
w.storageWriter = nil
|
||||||
|
}
|
||||||
|
if w.p != nil {
|
||||||
|
w.p.removeMetadata(w.meta.DiskFileNum)
|
||||||
|
}
|
||||||
|
// TODO(radu): delete the object if it was created.
|
||||||
|
}
|
||||||
900
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache/shared_cache.go
generated
vendored
Normal file
900
vendor/github.com/cockroachdb/pebble/objstorage/objstorageprovider/sharedcache/shared_cache.go
generated
vendored
Normal file
@@ -0,0 +1,900 @@
|
|||||||
|
// 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 sharedcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/bits"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
|
"github.com/cockroachdb/pebble/internal/invariants"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||||
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exported to enable exporting from package pebble to enable
|
||||||
|
// exporting metrics with below buckets in CRDB.
|
||||||
|
var (
|
||||||
|
IOBuckets = prometheus.ExponentialBucketsRange(float64(time.Millisecond*1), float64(10*time.Second), 50)
|
||||||
|
ChannelWriteBuckets = prometheus.ExponentialBucketsRange(float64(time.Microsecond*1), float64(10*time.Second), 50)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache is a persistent cache backed by a local filesystem. It is intended
|
||||||
|
// to cache data that is in slower shared storage (e.g. S3), hence the
|
||||||
|
// package name 'sharedcache'.
|
||||||
|
type Cache struct {
|
||||||
|
shards []shard
|
||||||
|
writeWorkers writeWorkers
|
||||||
|
|
||||||
|
bm blockMath
|
||||||
|
shardingBlockSize int64
|
||||||
|
|
||||||
|
logger base.Logger
|
||||||
|
metrics internalMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metrics is a struct containing metrics exported by the secondary cache.
|
||||||
|
// TODO(josh): Reconsider the set of metrics exported by the secondary cache
|
||||||
|
// before we release the secondary cache to users. We choose to export many metrics
|
||||||
|
// right now, so we learn a lot from the benchmarking we are doing over the 23.2
|
||||||
|
// cycle.
|
||||||
|
type Metrics struct {
|
||||||
|
// The number of sstable bytes stored in the cache.
|
||||||
|
Size int64
|
||||||
|
// The count of cache blocks in the cache (not sstable blocks).
|
||||||
|
Count int64
|
||||||
|
|
||||||
|
// The number of calls to ReadAt.
|
||||||
|
TotalReads int64
|
||||||
|
// The number of calls to ReadAt that require reading data from 2+ shards.
|
||||||
|
MultiShardReads int64
|
||||||
|
// The number of calls to ReadAt that require reading data from 2+ cache blocks.
|
||||||
|
MultiBlockReads int64
|
||||||
|
// The number of calls to ReadAt where all data returned was read from the cache.
|
||||||
|
ReadsWithFullHit int64
|
||||||
|
// The number of calls to ReadAt where some data returned was read from the cache.
|
||||||
|
ReadsWithPartialHit int64
|
||||||
|
// The number of calls to ReadAt where no data returned was read from the cache.
|
||||||
|
ReadsWithNoHit int64
|
||||||
|
|
||||||
|
// The number of times a cache block was evicted from the cache.
|
||||||
|
Evictions int64
|
||||||
|
// The number of times writing a cache block to the cache failed.
|
||||||
|
WriteBackFailures int64
|
||||||
|
|
||||||
|
// The latency of calls to get some data from the cache.
|
||||||
|
GetLatency prometheus.Histogram
|
||||||
|
// The latency of reads of a single cache block from disk.
|
||||||
|
DiskReadLatency prometheus.Histogram
|
||||||
|
// The latency of writing data to write back to the cache to a channel.
|
||||||
|
// Generally should be low, but if the channel is full, could be high.
|
||||||
|
QueuePutLatency prometheus.Histogram
|
||||||
|
// The latency of calls to put some data read from block storage into the cache.
|
||||||
|
PutLatency prometheus.Histogram
|
||||||
|
// The latency of writes of a single cache block to disk.
|
||||||
|
DiskWriteLatency prometheus.Histogram
|
||||||
|
}
|
||||||
|
|
||||||
|
// See docs at Metrics.
|
||||||
|
type internalMetrics struct {
|
||||||
|
count atomic.Int64
|
||||||
|
|
||||||
|
totalReads atomic.Int64
|
||||||
|
multiShardReads atomic.Int64
|
||||||
|
multiBlockReads atomic.Int64
|
||||||
|
readsWithFullHit atomic.Int64
|
||||||
|
readsWithPartialHit atomic.Int64
|
||||||
|
readsWithNoHit atomic.Int64
|
||||||
|
|
||||||
|
evictions atomic.Int64
|
||||||
|
writeBackFailures atomic.Int64
|
||||||
|
|
||||||
|
getLatency prometheus.Histogram
|
||||||
|
diskReadLatency prometheus.Histogram
|
||||||
|
queuePutLatency prometheus.Histogram
|
||||||
|
putLatency prometheus.Histogram
|
||||||
|
diskWriteLatency prometheus.Histogram
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// writeWorkersPerShard is used to establish the number of worker goroutines
|
||||||
|
// that perform writes to the cache.
|
||||||
|
writeWorkersPerShard = 4
|
||||||
|
// writeTaskPerWorker is used to establish how many tasks can be queued up
|
||||||
|
// until we have to block.
|
||||||
|
writeTasksPerWorker = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Open opens a cache. If there is no existing cache at fsDir, a new one
|
||||||
|
// is created.
|
||||||
|
func Open(
|
||||||
|
fs vfs.FS,
|
||||||
|
logger base.Logger,
|
||||||
|
fsDir string,
|
||||||
|
blockSize 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.
|
||||||
|
shardingBlockSize int64,
|
||||||
|
sizeBytes int64,
|
||||||
|
numShards int,
|
||||||
|
) (*Cache, error) {
|
||||||
|
if minSize := shardingBlockSize * int64(numShards); sizeBytes < minSize {
|
||||||
|
// Up the size so that we have one block per shard. In practice, this should
|
||||||
|
// only happen in tests.
|
||||||
|
sizeBytes = minSize
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &Cache{
|
||||||
|
logger: logger,
|
||||||
|
bm: makeBlockMath(blockSize),
|
||||||
|
shardingBlockSize: shardingBlockSize,
|
||||||
|
}
|
||||||
|
c.shards = make([]shard, numShards)
|
||||||
|
blocksPerShard := sizeBytes / int64(numShards) / int64(blockSize)
|
||||||
|
for i := range c.shards {
|
||||||
|
if err := c.shards[i].init(c, fs, fsDir, i, blocksPerShard, blockSize, shardingBlockSize); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.writeWorkers.Start(c, numShards*writeWorkersPerShard)
|
||||||
|
|
||||||
|
c.metrics.getLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||||
|
c.metrics.diskReadLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||||
|
c.metrics.putLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||||
|
c.metrics.diskWriteLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: IOBuckets})
|
||||||
|
|
||||||
|
// Measures a channel write, so lower min.
|
||||||
|
c.metrics.queuePutLatency = prometheus.NewHistogram(prometheus.HistogramOpts{Buckets: ChannelWriteBuckets})
|
||||||
|
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the cache. Methods such as ReadAt should not be called after Close is
|
||||||
|
// called.
|
||||||
|
func (c *Cache) Close() error {
|
||||||
|
c.writeWorkers.Stop()
|
||||||
|
|
||||||
|
var retErr error
|
||||||
|
for i := range c.shards {
|
||||||
|
if err := c.shards[i].close(); err != nil && retErr == nil {
|
||||||
|
retErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.shards = nil
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metrics return metrics for the cache. Callers should not mutate
|
||||||
|
// the returned histograms, which are pointer types.
|
||||||
|
func (c *Cache) Metrics() Metrics {
|
||||||
|
return Metrics{
|
||||||
|
Count: c.metrics.count.Load(),
|
||||||
|
Size: c.metrics.count.Load() * int64(c.bm.BlockSize()),
|
||||||
|
TotalReads: c.metrics.totalReads.Load(),
|
||||||
|
MultiShardReads: c.metrics.multiShardReads.Load(),
|
||||||
|
MultiBlockReads: c.metrics.multiBlockReads.Load(),
|
||||||
|
ReadsWithFullHit: c.metrics.readsWithFullHit.Load(),
|
||||||
|
ReadsWithPartialHit: c.metrics.readsWithPartialHit.Load(),
|
||||||
|
ReadsWithNoHit: c.metrics.readsWithNoHit.Load(),
|
||||||
|
Evictions: c.metrics.evictions.Load(),
|
||||||
|
WriteBackFailures: c.metrics.writeBackFailures.Load(),
|
||||||
|
GetLatency: c.metrics.getLatency,
|
||||||
|
DiskReadLatency: c.metrics.diskReadLatency,
|
||||||
|
QueuePutLatency: c.metrics.queuePutLatency,
|
||||||
|
PutLatency: c.metrics.putLatency,
|
||||||
|
DiskWriteLatency: c.metrics.diskWriteLatency,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFlags contains options for Cache.ReadAt.
|
||||||
|
type ReadFlags struct {
|
||||||
|
// ReadOnly instructs ReadAt to not write any new data into the cache; it is
|
||||||
|
// used when the data is unlikely to be used again.
|
||||||
|
ReadOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAt performs a read form an object, attempting to use cached data when
|
||||||
|
// possible.
|
||||||
|
func (c *Cache) ReadAt(
|
||||||
|
ctx context.Context,
|
||||||
|
fileNum base.DiskFileNum,
|
||||||
|
p []byte,
|
||||||
|
ofs int64,
|
||||||
|
objReader remote.ObjectReader,
|
||||||
|
objSize int64,
|
||||||
|
flags ReadFlags,
|
||||||
|
) error {
|
||||||
|
c.metrics.totalReads.Add(1)
|
||||||
|
if ofs >= objSize {
|
||||||
|
if invariants.Enabled {
|
||||||
|
panic(fmt.Sprintf("invalid ReadAt offset %v %v", ofs, objSize))
|
||||||
|
}
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
// TODO(radu): for compaction reads, we may not want to read from the cache at
|
||||||
|
// all.
|
||||||
|
{
|
||||||
|
start := time.Now()
|
||||||
|
n, err := c.get(fileNum, p, ofs)
|
||||||
|
c.metrics.getLatency.Observe(float64(time.Since(start)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n == len(p) {
|
||||||
|
// Everything was in cache!
|
||||||
|
c.metrics.readsWithFullHit.Add(1)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
c.metrics.readsWithNoHit.Add(1)
|
||||||
|
} else {
|
||||||
|
c.metrics.readsWithPartialHit.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note this. The below code does not need the original ofs, as with the earlier
|
||||||
|
// reading from the cache done, the relevant offset is ofs + int64(n). Same with p.
|
||||||
|
ofs += int64(n)
|
||||||
|
p = p[n:]
|
||||||
|
|
||||||
|
if invariants.Enabled {
|
||||||
|
if n != 0 && c.bm.Remainder(ofs) != 0 {
|
||||||
|
panic(fmt.Sprintf("after non-zero read from cache, ofs is not block-aligned: %v %v", ofs, n))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if flags.ReadOnly {
|
||||||
|
return objReader.ReadAt(ctx, p, ofs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must do reads with offset & size that are multiples of the block size. Else
|
||||||
|
// later cache hits may return incorrect zeroed results from the cache.
|
||||||
|
firstBlockInd := c.bm.Block(ofs)
|
||||||
|
adjustedOfs := c.bm.BlockOffset(firstBlockInd)
|
||||||
|
|
||||||
|
// Take the length of what is left to read plus the length of the adjustment of
|
||||||
|
// the offset plus the size of a block minus one and divide by the size of a block
|
||||||
|
// to get the number of blocks to read from the object.
|
||||||
|
sizeOfOffAdjustment := int(ofs - adjustedOfs)
|
||||||
|
adjustedLen := int(c.bm.RoundUp(int64(len(p) + sizeOfOffAdjustment)))
|
||||||
|
adjustedP := make([]byte, adjustedLen)
|
||||||
|
|
||||||
|
// Read the rest from the object. We may need to cap the length to avoid past EOF reads.
|
||||||
|
eofCap := int64(adjustedLen)
|
||||||
|
if adjustedOfs+eofCap > objSize {
|
||||||
|
eofCap = objSize - adjustedOfs
|
||||||
|
}
|
||||||
|
if err := objReader.ReadAt(ctx, adjustedP[:eofCap], adjustedOfs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
copy(p, adjustedP[sizeOfOffAdjustment:])
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
c.writeWorkers.QueueWrite(fileNum, adjustedP, adjustedOfs)
|
||||||
|
c.metrics.queuePutLatency.Observe(float64(time.Since(start)))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get attempts to read the requested data from the cache, if it is already
|
||||||
|
// there.
|
||||||
|
//
|
||||||
|
// If all data is available, returns n = len(p).
|
||||||
|
//
|
||||||
|
// If data is partially available, a prefix of the data is read; returns n < len(p)
|
||||||
|
// and no error. If no prefix is available, returns n = 0 and no error.
|
||||||
|
func (c *Cache) get(fileNum base.DiskFileNum, p []byte, ofs int64) (n int, _ error) {
|
||||||
|
// The data extent might cross shard boundaries, hence the loop. In the hot
|
||||||
|
// path, max two iterations of this loop will be executed, since reads are sized
|
||||||
|
// in units of sstable block size.
|
||||||
|
var multiShard bool
|
||||||
|
for {
|
||||||
|
shard := c.getShard(fileNum, ofs+int64(n))
|
||||||
|
cappedLen := len(p[n:])
|
||||||
|
if toBoundary := int(c.shardingBlockSize - ((ofs + int64(n)) % c.shardingBlockSize)); cappedLen > toBoundary {
|
||||||
|
cappedLen = toBoundary
|
||||||
|
}
|
||||||
|
numRead, err := shard.get(fileNum, p[n:n+cappedLen], ofs+int64(n))
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += numRead
|
||||||
|
if numRead < cappedLen {
|
||||||
|
// We only read a prefix from this shard.
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
if n == len(p) {
|
||||||
|
// We are done.
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
// Data extent crosses shard boundary, continue with next shard.
|
||||||
|
if !multiShard {
|
||||||
|
c.metrics.multiShardReads.Add(1)
|
||||||
|
multiShard = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set attempts to write the requested data to the cache. Both ofs & len(p) must
|
||||||
|
// be multiples of the block size.
|
||||||
|
//
|
||||||
|
// If all of p is not written to the shard, set returns a non-nil error.
|
||||||
|
func (c *Cache) set(fileNum base.DiskFileNum, p []byte, ofs int64) error {
|
||||||
|
if invariants.Enabled {
|
||||||
|
if c.bm.Remainder(ofs) != 0 || c.bm.Remainder(int64(len(p))) != 0 {
|
||||||
|
panic(fmt.Sprintf("set with ofs & len not multiples of block size: %v %v", ofs, len(p)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The data extent might cross shard boundaries, hence the loop. In the hot
|
||||||
|
// path, max two iterations of this loop will be executed, since reads are sized
|
||||||
|
// in units of sstable block size.
|
||||||
|
n := 0
|
||||||
|
for {
|
||||||
|
shard := c.getShard(fileNum, ofs+int64(n))
|
||||||
|
cappedLen := len(p[n:])
|
||||||
|
if toBoundary := int(c.shardingBlockSize - ((ofs + int64(n)) % c.shardingBlockSize)); cappedLen > toBoundary {
|
||||||
|
cappedLen = toBoundary
|
||||||
|
}
|
||||||
|
err := shard.set(fileNum, p[n:n+cappedLen], ofs+int64(n))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// set returns an error if cappedLen bytes aren't written to the shard.
|
||||||
|
n += cappedLen
|
||||||
|
if n == len(p) {
|
||||||
|
// We are done.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Data extent crosses shard boundary, continue with next shard.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) getShard(fileNum base.DiskFileNum, ofs int64) *shard {
|
||||||
|
const prime64 = 1099511628211
|
||||||
|
hash := uint64(fileNum.FileNum())*prime64 + uint64(ofs/c.shardingBlockSize)
|
||||||
|
// TODO(josh): Instance change ops are often run in production. Such an operation
|
||||||
|
// updates len(c.shards); see openSharedCache. As a result, the behavior of this
|
||||||
|
// function changes, and the cache empties out at restart time. We may want a better
|
||||||
|
// story here eventually.
|
||||||
|
return &c.shards[hash%uint64(len(c.shards))]
|
||||||
|
}
|
||||||
|
|
||||||
|
type shard struct {
|
||||||
|
cache *Cache
|
||||||
|
file vfs.File
|
||||||
|
sizeInBlocks int64
|
||||||
|
bm blockMath
|
||||||
|
shardingBlockSize int64
|
||||||
|
mu struct {
|
||||||
|
sync.Mutex
|
||||||
|
// TODO(josh): None of these datastructures are space-efficient.
|
||||||
|
// Focusing on correctness to start.
|
||||||
|
where whereMap
|
||||||
|
blocks []cacheBlockState
|
||||||
|
// Head of LRU list (doubly-linked circular).
|
||||||
|
lruHead cacheBlockIndex
|
||||||
|
// Head of free list (singly-linked chain).
|
||||||
|
freeHead cacheBlockIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cacheBlockState struct {
|
||||||
|
lock lockState
|
||||||
|
logical logicalBlockID
|
||||||
|
|
||||||
|
// next is the next block in the LRU or free list (or invalidBlockIndex if it
|
||||||
|
// is the last block in the free list).
|
||||||
|
next cacheBlockIndex
|
||||||
|
|
||||||
|
// prev is the previous block in the LRU list. It is not used when the block
|
||||||
|
// is in the free list.
|
||||||
|
prev cacheBlockIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps a logical block in an SST to an index of the cache block with the
|
||||||
|
// file contents (to the "cache block index").
|
||||||
|
type whereMap map[logicalBlockID]cacheBlockIndex
|
||||||
|
|
||||||
|
type logicalBlockID struct {
|
||||||
|
filenum base.DiskFileNum
|
||||||
|
cacheBlockIdx cacheBlockIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
type lockState int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
unlocked lockState = 0
|
||||||
|
// >0 lockState tracks the number of distinct readers of some cache block / logical block
|
||||||
|
// which is in the secondary cache. It is used to ensure that a cache block is not evicted
|
||||||
|
// and overwritten, while there are active readers.
|
||||||
|
readLockTakenInc = 1
|
||||||
|
// -1 lockState indicates that some cache block is currently being populated with data from
|
||||||
|
// blob storage. It is used to ensure that a cache block is not read or evicted again, while
|
||||||
|
// it is being populated.
|
||||||
|
writeLockTaken = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *shard) init(
|
||||||
|
cache *Cache,
|
||||||
|
fs vfs.FS,
|
||||||
|
fsDir string,
|
||||||
|
shardIdx int,
|
||||||
|
sizeInBlocks int64,
|
||||||
|
blockSize int,
|
||||||
|
shardingBlockSize int64,
|
||||||
|
) error {
|
||||||
|
*s = shard{
|
||||||
|
cache: cache,
|
||||||
|
sizeInBlocks: sizeInBlocks,
|
||||||
|
}
|
||||||
|
if blockSize < 1024 || shardingBlockSize%int64(blockSize) != 0 {
|
||||||
|
return errors.Newf("invalid block size %d (must divide %d)", blockSize, shardingBlockSize)
|
||||||
|
}
|
||||||
|
s.bm = makeBlockMath(blockSize)
|
||||||
|
s.shardingBlockSize = shardingBlockSize
|
||||||
|
file, err := fs.OpenReadWrite(fs.PathJoin(fsDir, fmt.Sprintf("SHARED-CACHE-%03d", shardIdx)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO(radu): truncate file if necessary (especially important if we restart
|
||||||
|
// with more shards).
|
||||||
|
if err := file.Preallocate(0, int64(blockSize)*sizeInBlocks); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.file = file
|
||||||
|
|
||||||
|
// TODO(josh): Right now, the secondary cache is not persistent. All existing
|
||||||
|
// cache contents will be over-written, since all metadata is only stored in
|
||||||
|
// memory.
|
||||||
|
s.mu.where = make(whereMap)
|
||||||
|
s.mu.blocks = make([]cacheBlockState, sizeInBlocks)
|
||||||
|
s.mu.lruHead = invalidBlockIndex
|
||||||
|
s.mu.freeHead = invalidBlockIndex
|
||||||
|
for i := range s.mu.blocks {
|
||||||
|
s.freePush(cacheBlockIndex(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *shard) close() error {
|
||||||
|
defer func() {
|
||||||
|
s.file = nil
|
||||||
|
}()
|
||||||
|
return s.file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// freePush pushes a block to the front of the free list.
|
||||||
|
func (s *shard) freePush(index cacheBlockIndex) {
|
||||||
|
s.mu.blocks[index].next = s.mu.freeHead
|
||||||
|
s.mu.freeHead = index
|
||||||
|
}
|
||||||
|
|
||||||
|
// freePop removes the block from the front of the free list. Must not be called
|
||||||
|
// if the list is empty (i.e. freeHead = invalidBlockIndex).
|
||||||
|
func (s *shard) freePop() cacheBlockIndex {
|
||||||
|
index := s.mu.freeHead
|
||||||
|
s.mu.freeHead = s.mu.blocks[index].next
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
// lruInsertFront inserts a block at the front of the LRU list.
|
||||||
|
func (s *shard) lruInsertFront(index cacheBlockIndex) {
|
||||||
|
b := &s.mu.blocks[index]
|
||||||
|
if s.mu.lruHead == invalidBlockIndex {
|
||||||
|
b.next = index
|
||||||
|
b.prev = index
|
||||||
|
} else {
|
||||||
|
b.next = s.mu.lruHead
|
||||||
|
h := &s.mu.blocks[s.mu.lruHead]
|
||||||
|
b.prev = h.prev
|
||||||
|
s.mu.blocks[h.prev].next = index
|
||||||
|
h.prev = index
|
||||||
|
}
|
||||||
|
s.mu.lruHead = index
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *shard) lruNext(index cacheBlockIndex) cacheBlockIndex {
|
||||||
|
return s.mu.blocks[index].next
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *shard) lruPrev(index cacheBlockIndex) cacheBlockIndex {
|
||||||
|
return s.mu.blocks[index].prev
|
||||||
|
}
|
||||||
|
|
||||||
|
// lruUnlink removes a block from the LRU list.
|
||||||
|
func (s *shard) lruUnlink(index cacheBlockIndex) {
|
||||||
|
b := &s.mu.blocks[index]
|
||||||
|
if b.next == index {
|
||||||
|
s.mu.lruHead = invalidBlockIndex
|
||||||
|
} else {
|
||||||
|
s.mu.blocks[b.prev].next = b.next
|
||||||
|
s.mu.blocks[b.next].prev = b.prev
|
||||||
|
if s.mu.lruHead == index {
|
||||||
|
s.mu.lruHead = b.next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.next, b.prev = invalidBlockIndex, invalidBlockIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// get attempts to read the requested data from the shard. The data must not
|
||||||
|
// cross a shard boundary.
|
||||||
|
//
|
||||||
|
// If all data is available, returns n = len(p).
|
||||||
|
//
|
||||||
|
// If data is partially available, a prefix of the data is read; returns n < len(p)
|
||||||
|
// and no error. If no prefix is available, returns n = 0 and no error.
|
||||||
|
//
|
||||||
|
// TODO(josh): Today, if there are two cache blocks needed to satisfy a read, and the
|
||||||
|
// first block is not in the cache and the second one is, we will read both from
|
||||||
|
// blob storage. We should fix this. This is not an unlikely scenario if we are doing
|
||||||
|
// a reverse scan, since those iterate over sstable blocks in reverse order and due to
|
||||||
|
// cache block aligned reads will have read the suffix of the sstable block that will
|
||||||
|
// be needed next.
|
||||||
|
func (s *shard) get(fileNum base.DiskFileNum, p []byte, ofs int64) (n int, _ error) {
|
||||||
|
if invariants.Enabled {
|
||||||
|
if ofs/s.shardingBlockSize != (ofs+int64(len(p))-1)/s.shardingBlockSize {
|
||||||
|
panic(fmt.Sprintf("get crosses shard boundary: %v %v", ofs, len(p)))
|
||||||
|
}
|
||||||
|
s.assertShardStateIsConsistent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The data extent might cross cache block boundaries, hence the loop. In the hot
|
||||||
|
// path, max two iterations of this loop will be executed, since reads are sized
|
||||||
|
// in units of sstable block size.
|
||||||
|
var multiBlock bool
|
||||||
|
for {
|
||||||
|
k := logicalBlockID{
|
||||||
|
filenum: fileNum,
|
||||||
|
cacheBlockIdx: s.bm.Block(ofs + int64(n)),
|
||||||
|
}
|
||||||
|
s.mu.Lock()
|
||||||
|
cacheBlockIdx, ok := s.mu.where[k]
|
||||||
|
// TODO(josh): Multiple reads within the same few milliseconds (anything that is smaller
|
||||||
|
// than blob storage read latency) that miss on the same logical block ID will not necessarily
|
||||||
|
// be rare. We may want to do only one read, with the later readers blocking on the first read
|
||||||
|
// completing. This could be implemented either here or in the primary block cache. See
|
||||||
|
// https://github.com/cockroachdb/pebble/pull/2586 for additional discussion.
|
||||||
|
if !ok {
|
||||||
|
s.mu.Unlock()
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
if s.mu.blocks[cacheBlockIdx].lock == writeLockTaken {
|
||||||
|
// In practice, if we have two reads of the same SST block in close succession, we
|
||||||
|
// would expect the second to hit in the in-memory block cache. So it's not worth
|
||||||
|
// optimizing this case here.
|
||||||
|
s.mu.Unlock()
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
s.mu.blocks[cacheBlockIdx].lock += readLockTakenInc
|
||||||
|
// Move to front of the LRU list.
|
||||||
|
s.lruUnlink(cacheBlockIdx)
|
||||||
|
s.lruInsertFront(cacheBlockIdx)
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
readAt := s.bm.BlockOffset(cacheBlockIdx)
|
||||||
|
readSize := s.bm.BlockSize()
|
||||||
|
if n == 0 { // if first read
|
||||||
|
rem := s.bm.Remainder(ofs)
|
||||||
|
readAt += rem
|
||||||
|
readSize -= int(rem)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p[n:]) <= readSize {
|
||||||
|
start := time.Now()
|
||||||
|
numRead, err := s.file.ReadAt(p[n:], readAt)
|
||||||
|
s.cache.metrics.diskReadLatency.Observe(float64(time.Since(start)))
|
||||||
|
s.dropReadLock(cacheBlockIdx)
|
||||||
|
return n + numRead, err
|
||||||
|
}
|
||||||
|
start := time.Now()
|
||||||
|
numRead, err := s.file.ReadAt(p[n:n+readSize], readAt)
|
||||||
|
s.cache.metrics.diskReadLatency.Observe(float64(time.Since(start)))
|
||||||
|
s.dropReadLock(cacheBlockIdx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that numRead == readSize, since we checked for an error above.
|
||||||
|
n += numRead
|
||||||
|
|
||||||
|
if !multiBlock {
|
||||||
|
s.cache.metrics.multiBlockReads.Add(1)
|
||||||
|
multiBlock = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set attempts to write the requested data to the shard. The data must not
|
||||||
|
// cross a shard boundary, and both ofs & len(p) must be multiples of the
|
||||||
|
// block size.
|
||||||
|
//
|
||||||
|
// If all of p is not written to the shard, set returns a non-nil error.
|
||||||
|
func (s *shard) set(fileNum base.DiskFileNum, p []byte, ofs int64) error {
|
||||||
|
if invariants.Enabled {
|
||||||
|
if ofs/s.shardingBlockSize != (ofs+int64(len(p))-1)/s.shardingBlockSize {
|
||||||
|
panic(fmt.Sprintf("set crosses shard boundary: %v %v", ofs, len(p)))
|
||||||
|
}
|
||||||
|
if s.bm.Remainder(ofs) != 0 || s.bm.Remainder(int64(len(p))) != 0 {
|
||||||
|
panic(fmt.Sprintf("set with ofs & len not multiples of block size: %v %v", ofs, len(p)))
|
||||||
|
}
|
||||||
|
s.assertShardStateIsConsistent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The data extent might cross cache block boundaries, hence the loop. In the hot
|
||||||
|
// path, max two iterations of this loop will be executed, since reads are sized
|
||||||
|
// in units of sstable block size.
|
||||||
|
n := 0
|
||||||
|
for {
|
||||||
|
if n == len(p) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if invariants.Enabled {
|
||||||
|
if n > len(p) {
|
||||||
|
panic(fmt.Sprintf("set with n greater than len(p): %v %v", n, len(p)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the logical block is already in the cache, we should skip doing a set.
|
||||||
|
k := logicalBlockID{
|
||||||
|
filenum: fileNum,
|
||||||
|
cacheBlockIdx: s.bm.Block(ofs + int64(n)),
|
||||||
|
}
|
||||||
|
s.mu.Lock()
|
||||||
|
if _, ok := s.mu.where[k]; ok {
|
||||||
|
s.mu.Unlock()
|
||||||
|
n += s.bm.BlockSize()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheBlockIdx cacheBlockIndex
|
||||||
|
if s.mu.freeHead == invalidBlockIndex {
|
||||||
|
if invariants.Enabled && s.mu.lruHead == invalidBlockIndex {
|
||||||
|
panic("both LRU and free lists empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the last element in the LRU list which is not locked.
|
||||||
|
for idx := s.lruPrev(s.mu.lruHead); ; idx = s.lruPrev(idx) {
|
||||||
|
if lock := s.mu.blocks[idx].lock; lock == unlocked {
|
||||||
|
cacheBlockIdx = idx
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if idx == s.mu.lruHead {
|
||||||
|
// No unlocked block to evict.
|
||||||
|
//
|
||||||
|
// TODO(josh): We may want to block until a block frees up, instead of returning
|
||||||
|
// an error here. But I think we can do that later on, e.g. after running some production
|
||||||
|
// experiments.
|
||||||
|
s.mu.Unlock()
|
||||||
|
return errors.New("no block to evict so skipping write to cache")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.cache.metrics.evictions.Add(1)
|
||||||
|
s.lruUnlink(cacheBlockIdx)
|
||||||
|
delete(s.mu.where, s.mu.blocks[cacheBlockIdx].logical)
|
||||||
|
} else {
|
||||||
|
s.cache.metrics.count.Add(1)
|
||||||
|
cacheBlockIdx = s.freePop()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.lruInsertFront(cacheBlockIdx)
|
||||||
|
s.mu.where[k] = cacheBlockIdx
|
||||||
|
s.mu.blocks[cacheBlockIdx].logical = k
|
||||||
|
s.mu.blocks[cacheBlockIdx].lock = writeLockTaken
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
writeAt := s.bm.BlockOffset(cacheBlockIdx)
|
||||||
|
|
||||||
|
writeSize := s.bm.BlockSize()
|
||||||
|
if len(p[n:]) <= writeSize {
|
||||||
|
writeSize = len(p[n:])
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
_, err := s.file.WriteAt(p[n:n+writeSize], writeAt)
|
||||||
|
s.cache.metrics.diskWriteLatency.Observe(float64(time.Since(start)))
|
||||||
|
if err != nil {
|
||||||
|
// Free the block.
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
delete(s.mu.where, k)
|
||||||
|
s.lruUnlink(cacheBlockIdx)
|
||||||
|
s.freePush(cacheBlockIdx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.dropWriteLock(cacheBlockIdx)
|
||||||
|
n += writeSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doesn't inline currently. This might be okay, but something to keep in mind.
|
||||||
|
func (s *shard) dropReadLock(cacheBlockInd cacheBlockIndex) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.mu.blocks[cacheBlockInd].lock -= readLockTakenInc
|
||||||
|
if invariants.Enabled && s.mu.blocks[cacheBlockInd].lock < 0 {
|
||||||
|
panic(fmt.Sprintf("unexpected lock state %v in dropReadLock", s.mu.blocks[cacheBlockInd].lock))
|
||||||
|
}
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Doesn't inline currently. This might be okay, but something to keep in mind.
|
||||||
|
func (s *shard) dropWriteLock(cacheBlockInd cacheBlockIndex) {
|
||||||
|
s.mu.Lock()
|
||||||
|
if invariants.Enabled && s.mu.blocks[cacheBlockInd].lock != writeLockTaken {
|
||||||
|
panic(fmt.Sprintf("unexpected lock state %v in dropWriteLock", s.mu.blocks[cacheBlockInd].lock))
|
||||||
|
}
|
||||||
|
s.mu.blocks[cacheBlockInd].lock = unlocked
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *shard) assertShardStateIsConsistent() {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
lruLen := 0
|
||||||
|
if s.mu.lruHead != invalidBlockIndex {
|
||||||
|
for b := s.mu.lruHead; ; {
|
||||||
|
lruLen++
|
||||||
|
if idx, ok := s.mu.where[s.mu.blocks[b].logical]; !ok || idx != b {
|
||||||
|
panic("block in LRU list with no entry in where map")
|
||||||
|
}
|
||||||
|
b = s.lruNext(b)
|
||||||
|
if b == s.mu.lruHead {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lruLen != len(s.mu.where) {
|
||||||
|
panic(fmt.Sprintf("lru list len is %d but where map has %d entries", lruLen, len(s.mu.where)))
|
||||||
|
}
|
||||||
|
freeLen := 0
|
||||||
|
for n := s.mu.freeHead; n != invalidBlockIndex; n = s.mu.blocks[n].next {
|
||||||
|
freeLen++
|
||||||
|
}
|
||||||
|
|
||||||
|
if lruLen+freeLen != int(s.sizeInBlocks) {
|
||||||
|
panic(fmt.Sprintf("%d lru blocks and %d free blocks don't add up to %d", lruLen, freeLen, s.sizeInBlocks))
|
||||||
|
}
|
||||||
|
for i := range s.mu.blocks {
|
||||||
|
if state := s.mu.blocks[i].lock; state < writeLockTaken {
|
||||||
|
panic(fmt.Sprintf("lock state %v is not allowed", state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cacheBlockIndex is the index of a blockSize-aligned cache block.
|
||||||
|
type cacheBlockIndex int64
|
||||||
|
|
||||||
|
// invalidBlockIndex is used for the head of a list when the list is empty.
|
||||||
|
const invalidBlockIndex cacheBlockIndex = -1
|
||||||
|
|
||||||
|
// blockMath is a helper type for performing conversions between offsets and
|
||||||
|
// block indexes.
|
||||||
|
type blockMath struct {
|
||||||
|
blockSizeBits int8
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeBlockMath(blockSize int) blockMath {
|
||||||
|
bm := blockMath{
|
||||||
|
blockSizeBits: int8(bits.Len64(uint64(blockSize)) - 1),
|
||||||
|
}
|
||||||
|
if blockSize != (1 << bm.blockSizeBits) {
|
||||||
|
panic(fmt.Sprintf("blockSize %d is not a power of 2", blockSize))
|
||||||
|
}
|
||||||
|
return bm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bm blockMath) mask() int64 {
|
||||||
|
return (1 << bm.blockSizeBits) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockSize returns the block size.
|
||||||
|
func (bm blockMath) BlockSize() int {
|
||||||
|
return 1 << bm.blockSizeBits
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block returns the block index containing the given offset.
|
||||||
|
func (bm blockMath) Block(offset int64) cacheBlockIndex {
|
||||||
|
return cacheBlockIndex(offset >> bm.blockSizeBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remainder returns the offset relative to the start of the cache block.
|
||||||
|
func (bm blockMath) Remainder(offset int64) int64 {
|
||||||
|
return offset & bm.mask()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockOffset returns the object offset where the given block starts.
|
||||||
|
func (bm blockMath) BlockOffset(block cacheBlockIndex) int64 {
|
||||||
|
return int64(block) << bm.blockSizeBits
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundUp rounds up the given value to the closest multiple of block size.
|
||||||
|
func (bm blockMath) RoundUp(x int64) int64 {
|
||||||
|
return (x + bm.mask()) & ^(bm.mask())
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeWorkers struct {
|
||||||
|
doneCh chan struct{}
|
||||||
|
doneWaitGroup sync.WaitGroup
|
||||||
|
|
||||||
|
numWorkers int
|
||||||
|
tasksCh chan writeTask
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeTask struct {
|
||||||
|
fileNum base.DiskFileNum
|
||||||
|
p []byte
|
||||||
|
offset int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the worker goroutines.
|
||||||
|
func (w *writeWorkers) Start(c *Cache, numWorkers int) {
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
tasksCh := make(chan writeTask, numWorkers*writeTasksPerWorker)
|
||||||
|
|
||||||
|
w.numWorkers = numWorkers
|
||||||
|
w.doneCh = doneCh
|
||||||
|
w.tasksCh = tasksCh
|
||||||
|
w.doneWaitGroup.Add(numWorkers)
|
||||||
|
for i := 0; i < numWorkers; i++ {
|
||||||
|
go func() {
|
||||||
|
defer w.doneWaitGroup.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-doneCh:
|
||||||
|
return
|
||||||
|
case task, ok := <-tasksCh:
|
||||||
|
if !ok {
|
||||||
|
// The tasks channel was closed; this is used in testing code to
|
||||||
|
// ensure all writes are completed.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO(radu): set() can perform multiple writes; perhaps each one
|
||||||
|
// should be its own task.
|
||||||
|
start := time.Now()
|
||||||
|
err := c.set(task.fileNum, task.p, task.offset)
|
||||||
|
c.metrics.putLatency.Observe(float64(time.Since(start)))
|
||||||
|
if err != nil {
|
||||||
|
c.metrics.writeBackFailures.Add(1)
|
||||||
|
// TODO(radu): throttle logs.
|
||||||
|
c.logger.Infof("writing back to cache after miss failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop waits for any in-progress writes to complete and stops the worker
|
||||||
|
// goroutines and waits for any in-pro. Any queued writes not yet started are
|
||||||
|
// discarded.
|
||||||
|
func (w *writeWorkers) Stop() {
|
||||||
|
close(w.doneCh)
|
||||||
|
w.doneCh = nil
|
||||||
|
w.tasksCh = nil
|
||||||
|
w.doneWaitGroup.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueWrite adds a write task to the queue. Can block if the queue is full.
|
||||||
|
func (w *writeWorkers) QueueWrite(fileNum base.DiskFileNum, p []byte, offset int64) {
|
||||||
|
w.tasksCh <- writeTask{
|
||||||
|
fileNum: fileNum,
|
||||||
|
p: p,
|
||||||
|
offset: offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,63 +2,63 @@
|
|||||||
// of this source code is governed by a BSD-style license that can be found in
|
// of this source code is governed by a BSD-style license that can be found in
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
|
|
||||||
package objstorage
|
package objstorageprovider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
"github.com/cockroachdb/errors"
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage"
|
||||||
"github.com/cockroachdb/pebble/vfs"
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *Provider) vfsPath(fileType base.FileType, fileNum base.FileNum) string {
|
func (p *provider) vfsPath(fileType base.FileType, fileNum base.DiskFileNum) string {
|
||||||
return base.MakeFilepath(p.st.FS, p.st.FSDirName, fileType, fileNum)
|
return base.MakeFilepath(p.st.FS, p.st.FSDirName, fileType, fileNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) vfsOpenForReading(
|
func (p *provider) vfsOpenForReading(
|
||||||
fileType base.FileType, fileNum base.FileNum, mustExist bool,
|
ctx context.Context,
|
||||||
) (Readable, error) {
|
fileType base.FileType,
|
||||||
|
fileNum base.DiskFileNum,
|
||||||
|
opts objstorage.OpenOptions,
|
||||||
|
) (objstorage.Readable, error) {
|
||||||
filename := p.vfsPath(fileType, fileNum)
|
filename := p.vfsPath(fileType, fileNum)
|
||||||
file, err := p.st.FS.Open(filename, vfs.RandomReadsOption)
|
file, err := p.st.FS.Open(filename, vfs.RandomReadsOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if mustExist {
|
if opts.MustExist {
|
||||||
base.MustExist(p.st.FS, filename, p.st.Logger, err)
|
base.MustExist(p.st.FS, filename, p.st.Logger, err)
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// TODO(radu): we use the existence of the file descriptor as an indication
|
return newFileReadable(file, p.st.FS, filename)
|
||||||
// that the File might support Prefetch and SequentialReadsOption. We should
|
|
||||||
// replace this with a cleaner way to obtain the capabilities of the FS / File.
|
|
||||||
if fd := file.Fd(); fd != vfs.InvalidFd {
|
|
||||||
return newFileReadable(file, p.st.FS, filename)
|
|
||||||
}
|
|
||||||
return newGenericFileReadable(file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) vfsCreate(
|
func (p *provider) vfsCreate(
|
||||||
fileType base.FileType, fileNum base.FileNum,
|
_ context.Context, fileType base.FileType, fileNum base.DiskFileNum,
|
||||||
) (Writable, ObjectMetadata, error) {
|
) (objstorage.Writable, objstorage.ObjectMetadata, error) {
|
||||||
filename := p.vfsPath(fileType, fileNum)
|
filename := p.vfsPath(fileType, fileNum)
|
||||||
file, err := p.st.FS.Create(filename)
|
file, err := p.st.FS.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ObjectMetadata{}, err
|
return nil, objstorage.ObjectMetadata{}, err
|
||||||
}
|
}
|
||||||
file = vfs.NewSyncingFile(file, vfs.SyncingFileOptions{
|
file = vfs.NewSyncingFile(file, vfs.SyncingFileOptions{
|
||||||
NoSyncOnClose: p.st.NoSyncOnClose,
|
NoSyncOnClose: p.st.NoSyncOnClose,
|
||||||
BytesPerSync: p.st.BytesPerSync,
|
BytesPerSync: p.st.BytesPerSync,
|
||||||
})
|
})
|
||||||
meta := ObjectMetadata{
|
meta := objstorage.ObjectMetadata{
|
||||||
FileNum: fileNum,
|
DiskFileNum: fileNum,
|
||||||
FileType: fileType,
|
FileType: fileType,
|
||||||
}
|
}
|
||||||
return newFileBufferedWritable(file), meta, nil
|
return newFileBufferedWritable(file), meta, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) vfsRemove(fileType base.FileType, fileNum base.FileNum) error {
|
func (p *provider) vfsRemove(fileType base.FileType, fileNum base.DiskFileNum) error {
|
||||||
return p.st.FSCleaner.Clean(p.st.FS, fileType, p.vfsPath(fileType, fileNum))
|
return p.st.FSCleaner.Clean(p.st.FS, fileType, p.vfsPath(fileType, fileNum))
|
||||||
}
|
}
|
||||||
|
|
||||||
// vfsInit finds any local FS objects.
|
// vfsInit finds any local FS objects.
|
||||||
func (p *Provider) vfsInit() error {
|
func (p *provider) vfsInit() error {
|
||||||
listing := p.st.FSDirInitialListing
|
listing := p.st.FSDirInitialListing
|
||||||
if listing == nil {
|
if listing == nil {
|
||||||
var err error
|
var err error
|
||||||
@@ -71,17 +71,17 @@ func (p *Provider) vfsInit() error {
|
|||||||
for _, filename := range listing {
|
for _, filename := range listing {
|
||||||
fileType, fileNum, ok := base.ParseFilename(p.st.FS, filename)
|
fileType, fileNum, ok := base.ParseFilename(p.st.FS, filename)
|
||||||
if ok && fileType == base.FileTypeTable {
|
if ok && fileType == base.FileTypeTable {
|
||||||
o := ObjectMetadata{
|
o := objstorage.ObjectMetadata{
|
||||||
FileType: fileType,
|
FileType: fileType,
|
||||||
FileNum: fileNum,
|
DiskFileNum: fileNum,
|
||||||
}
|
}
|
||||||
p.mu.knownObjects[o.FileNum] = o
|
p.mu.knownObjects[o.DiskFileNum] = o
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) vfsSync() error {
|
func (p *provider) vfsSync() error {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
shouldSync := p.mu.localObjectsChanged
|
shouldSync := p.mu.localObjectsChanged
|
||||||
p.mu.localObjectsChanged = false
|
p.mu.localObjectsChanged = false
|
||||||
@@ -99,7 +99,7 @@ func (p *Provider) vfsSync() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) vfsSize(fileType base.FileType, fileNum base.FileNum) (int64, error) {
|
func (p *provider) vfsSize(fileType base.FileType, fileNum base.DiskFileNum) (int64, error) {
|
||||||
filename := p.vfsPath(fileType, fileNum)
|
filename := p.vfsPath(fileType, fileNum)
|
||||||
stat, err := p.st.FS.Stat(filename)
|
stat, err := p.st.FS.Stat(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2,19 +2,25 @@
|
|||||||
// of this source code is governed by a BSD-style license that can be found in
|
// of this source code is governed by a BSD-style license that can be found in
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
|
|
||||||
package objstorage
|
package objstorageprovider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/cockroachdb/pebble/internal/invariants"
|
"github.com/cockroachdb/pebble/internal/invariants"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage"
|
||||||
"github.com/cockroachdb/pebble/vfs"
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// fileReadable implements objstorage.Readable on top of a vfs.File that
|
const fileMaxReadaheadSize = 256 * 1024 /* 256KB */
|
||||||
// supports Prefetch and/or on an FS that supports SequentialReadsOption.
|
|
||||||
|
// fileReadable implements objstorage.Readable on top of a vfs.File.
|
||||||
|
//
|
||||||
|
// The implementation might use Prealloc and might reopen the file with
|
||||||
|
// SequentialReadsOption.
|
||||||
type fileReadable struct {
|
type fileReadable struct {
|
||||||
file vfs.File
|
file vfs.File
|
||||||
size int64
|
size int64
|
||||||
@@ -25,7 +31,7 @@ type fileReadable struct {
|
|||||||
fs vfs.FS
|
fs vfs.FS
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Readable = (*fileReadable)(nil)
|
var _ objstorage.Readable = (*fileReadable)(nil)
|
||||||
|
|
||||||
func newFileReadable(file vfs.File, fs vfs.FS, filename string) (*fileReadable, error) {
|
func newFileReadable(file vfs.File, fs vfs.FS, filename string) (*fileReadable, error) {
|
||||||
info, err := file.Stat()
|
info, err := file.Stat()
|
||||||
@@ -48,8 +54,12 @@ func newFileReadable(file vfs.File, fs vfs.FS, filename string) (*fileReadable,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadAt is part of the objstorage.Readable interface.
|
// ReadAt is part of the objstorage.Readable interface.
|
||||||
func (r *fileReadable) ReadAt(p []byte, off int64) (n int, err error) {
|
func (r *fileReadable) ReadAt(_ context.Context, p []byte, off int64) error {
|
||||||
return r.file.ReadAt(p, off)
|
n, err := r.file.ReadAt(p, off)
|
||||||
|
if invariants.Enabled && err == nil && n != len(p) {
|
||||||
|
panic("short read")
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close is part of the objstorage.Readable interface.
|
// Close is part of the objstorage.Readable interface.
|
||||||
@@ -64,10 +74,10 @@ func (r *fileReadable) Size() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewReadHandle is part of the objstorage.Readable interface.
|
// NewReadHandle is part of the objstorage.Readable interface.
|
||||||
func (r *fileReadable) NewReadHandle() ReadHandle {
|
func (r *fileReadable) NewReadHandle(_ context.Context) objstorage.ReadHandle {
|
||||||
rh := readHandlePool.Get().(*vfsReadHandle)
|
rh := readHandlePool.Get().(*vfsReadHandle)
|
||||||
rh.r = r
|
rh.r = r
|
||||||
rh.rs = makeReadaheadState()
|
rh.rs = makeReadaheadState(fileMaxReadaheadSize)
|
||||||
return rh
|
return rh
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +92,7 @@ type vfsReadHandle struct {
|
|||||||
sequentialFile vfs.File
|
sequentialFile vfs.File
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ ReadHandle = (*vfsReadHandle)(nil)
|
var _ objstorage.ReadHandle = (*vfsReadHandle)(nil)
|
||||||
|
|
||||||
var readHandlePool = sync.Pool{
|
var readHandlePool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() interface{} {
|
||||||
@@ -110,25 +120,36 @@ func (rh *vfsReadHandle) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadAt is part of the objstorage.ReadHandle interface.
|
// ReadAt is part of the objstorage.ReadHandle interface.
|
||||||
func (rh *vfsReadHandle) ReadAt(p []byte, offset int64) (n int, err error) {
|
func (rh *vfsReadHandle) ReadAt(_ context.Context, p []byte, offset int64) error {
|
||||||
|
var n int
|
||||||
|
var err error
|
||||||
if rh.sequentialFile != nil {
|
if rh.sequentialFile != nil {
|
||||||
// Use OS-level read-ahead.
|
// Use OS-level read-ahead.
|
||||||
return rh.sequentialFile.ReadAt(p, offset)
|
n, err = rh.sequentialFile.ReadAt(p, offset)
|
||||||
}
|
} else {
|
||||||
if readaheadSize := rh.rs.maybeReadahead(offset, int64(len(p))); readaheadSize > 0 {
|
if readaheadSize := rh.rs.maybeReadahead(offset, int64(len(p))); readaheadSize > 0 {
|
||||||
if readaheadSize >= maxReadaheadSize {
|
if readaheadSize >= fileMaxReadaheadSize {
|
||||||
// We've reached the maximum readahead size. Beyond this point, rely on
|
// We've reached the maximum readahead size. Beyond this point, rely on
|
||||||
// OS-level readahead.
|
// OS-level readahead.
|
||||||
rh.MaxReadahead()
|
rh.switchToOSReadahead()
|
||||||
} else {
|
} else {
|
||||||
_ = rh.r.file.Prefetch(offset, readaheadSize)
|
_ = rh.r.file.Prefetch(offset, readaheadSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
n, err = rh.r.file.ReadAt(p, offset)
|
||||||
}
|
}
|
||||||
return rh.r.file.ReadAt(p, offset)
|
if invariants.Enabled && err == nil && n != len(p) {
|
||||||
|
panic("short read")
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxReadahead is part of the objstorage.ReadHandle interface.
|
// SetupForCompaction is part of the objstorage.ReadHandle interface.
|
||||||
func (rh *vfsReadHandle) MaxReadahead() {
|
func (rh *vfsReadHandle) SetupForCompaction() {
|
||||||
|
rh.switchToOSReadahead()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rh *vfsReadHandle) switchToOSReadahead() {
|
||||||
if rh.sequentialFile != nil {
|
if rh.sequentialFile != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -142,7 +163,7 @@ func (rh *vfsReadHandle) MaxReadahead() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RecordCacheHit is part of the objstorage.ReadHandle interface.
|
// RecordCacheHit is part of the objstorage.ReadHandle interface.
|
||||||
func (rh *vfsReadHandle) RecordCacheHit(offset, size int64) {
|
func (rh *vfsReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {
|
||||||
if rh.sequentialFile != nil {
|
if rh.sequentialFile != nil {
|
||||||
// Using OS-level readahead, so do nothing.
|
// Using OS-level readahead, so do nothing.
|
||||||
return
|
return
|
||||||
@@ -150,60 +171,9 @@ func (rh *vfsReadHandle) RecordCacheHit(offset, size int64) {
|
|||||||
rh.rs.recordCacheHit(offset, size)
|
rh.rs.recordCacheHit(offset, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// genericFileReadable implements objstorage.Readable on top of any vfs.File.
|
|
||||||
// This implementation does not support read-ahead.
|
|
||||||
type genericFileReadable struct {
|
|
||||||
file vfs.File
|
|
||||||
size int64
|
|
||||||
|
|
||||||
rh NoopReadHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Readable = (*genericFileReadable)(nil)
|
|
||||||
|
|
||||||
func newGenericFileReadable(file vfs.File) (*genericFileReadable, error) {
|
|
||||||
info, err := file.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r := &genericFileReadable{
|
|
||||||
file: file,
|
|
||||||
size: info.Size(),
|
|
||||||
rh: MakeNoopReadHandle(file),
|
|
||||||
}
|
|
||||||
invariants.SetFinalizer(r, func(obj interface{}) {
|
|
||||||
if obj.(*genericFileReadable).file != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Readable was not closed")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadAt is part of the objstorage.Readable interface.
|
|
||||||
func (r *genericFileReadable) ReadAt(p []byte, off int64) (n int, err error) {
|
|
||||||
return r.file.ReadAt(p, off)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close is part of the objstorage.Readable interface.
|
|
||||||
func (r *genericFileReadable) Close() error {
|
|
||||||
defer func() { r.file = nil }()
|
|
||||||
return r.file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size is part of the objstorage.Readable interface.
|
|
||||||
func (r *genericFileReadable) Size() int64 {
|
|
||||||
return r.size
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReadHandle is part of the objstorage.Readable interface.
|
|
||||||
func (r *genericFileReadable) NewReadHandle() ReadHandle {
|
|
||||||
return &r.rh
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestingCheckMaxReadahead returns true if the ReadHandle has switched to
|
// TestingCheckMaxReadahead returns true if the ReadHandle has switched to
|
||||||
// OS-level read-ahead.
|
// OS-level read-ahead.
|
||||||
func TestingCheckMaxReadahead(rh ReadHandle) bool {
|
func TestingCheckMaxReadahead(rh objstorage.ReadHandle) bool {
|
||||||
switch rh := rh.(type) {
|
switch rh := rh.(type) {
|
||||||
case *vfsReadHandle:
|
case *vfsReadHandle:
|
||||||
return rh.sequentialFile != nil
|
return rh.sequentialFile != nil
|
||||||
@@ -234,11 +204,13 @@ func (rh *PreallocatedReadHandle) Close() error {
|
|||||||
// but uses the existing storage of a PreallocatedReadHandle when possible
|
// but uses the existing storage of a PreallocatedReadHandle when possible
|
||||||
// (currently this happens if we are reading from a local file).
|
// (currently this happens if we are reading from a local file).
|
||||||
// The returned handle still needs to be closed.
|
// The returned handle still needs to be closed.
|
||||||
func UsePreallocatedReadHandle(readable Readable, rh *PreallocatedReadHandle) ReadHandle {
|
func UsePreallocatedReadHandle(
|
||||||
|
ctx context.Context, readable objstorage.Readable, rh *PreallocatedReadHandle,
|
||||||
|
) objstorage.ReadHandle {
|
||||||
if r, ok := readable.(*fileReadable); ok {
|
if r, ok := readable.(*fileReadable); ok {
|
||||||
// See fileReadable.NewReadHandle.
|
// See fileReadable.NewReadHandle.
|
||||||
rh.vfsReadHandle = vfsReadHandle{r: r}
|
rh.vfsReadHandle = vfsReadHandle{r: r}
|
||||||
return rh
|
return rh
|
||||||
}
|
}
|
||||||
return readable.NewReadHandle()
|
return readable.NewReadHandle(ctx)
|
||||||
}
|
}
|
||||||
@@ -2,16 +2,17 @@
|
|||||||
// of this source code is governed by a BSD-style license that can be found in
|
// of this source code is governed by a BSD-style license that can be found in
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
|
|
||||||
package objstorage
|
package objstorageprovider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble/objstorage"
|
||||||
"github.com/cockroachdb/pebble/vfs"
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewFileWritable returns a Writable that uses a file as underlying storage.
|
// NewFileWritable returns a Writable that uses a file as underlying storage.
|
||||||
func NewFileWritable(file vfs.File) Writable {
|
func NewFileWritable(file vfs.File) objstorage.Writable {
|
||||||
return newFileBufferedWritable(file)
|
return newFileBufferedWritable(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ type fileBufferedWritable struct {
|
|||||||
bw *bufio.Writer
|
bw *bufio.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Writable = (*fileBufferedWritable)(nil)
|
var _ objstorage.Writable = (*fileBufferedWritable)(nil)
|
||||||
|
|
||||||
func newFileBufferedWritable(file vfs.File) *fileBufferedWritable {
|
func newFileBufferedWritable(file vfs.File) *fileBufferedWritable {
|
||||||
return &fileBufferedWritable{
|
return &fileBufferedWritable{
|
||||||
446
vendor/github.com/cockroachdb/pebble/objstorage/provider.go
generated
vendored
446
vendor/github.com/cockroachdb/pebble/objstorage/provider.go
generated
vendored
@@ -1,446 +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 objstorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
|
||||||
"github.com/cockroachdb/errors/oserror"
|
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
|
||||||
"github.com/cockroachdb/pebble/objstorage/shared"
|
|
||||||
"github.com/cockroachdb/pebble/objstorage/sharedobjcat"
|
|
||||||
"github.com/cockroachdb/pebble/vfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Provider is a singleton object used to access and manage objects.
|
|
||||||
//
|
|
||||||
// An object is conceptually like a large immutable file. The main use of
|
|
||||||
// objects is for storing sstables; in the future it could also be used for blob
|
|
||||||
// storage.
|
|
||||||
//
|
|
||||||
// The Provider can only manage objects that it knows about - either objects
|
|
||||||
// created by the provider, or existing objects the Provider was informed about
|
|
||||||
// via AddObjects.
|
|
||||||
//
|
|
||||||
// Objects are currently backed by a vfs.File.
|
|
||||||
type Provider struct {
|
|
||||||
st Settings
|
|
||||||
|
|
||||||
fsDir vfs.File
|
|
||||||
|
|
||||||
shared sharedSubsystem
|
|
||||||
|
|
||||||
mu struct {
|
|
||||||
sync.RWMutex
|
|
||||||
|
|
||||||
shared struct {
|
|
||||||
// catalogBatch accumulates shared object creations and deletions until
|
|
||||||
// Sync is called.
|
|
||||||
catalogBatch sharedobjcat.Batch
|
|
||||||
}
|
|
||||||
|
|
||||||
// localObjectsChanged is set if non-shared objects were created or deleted
|
|
||||||
// but Sync was not yet called.
|
|
||||||
localObjectsChanged bool
|
|
||||||
|
|
||||||
// knownObjects maintains information about objects that are known to the provider.
|
|
||||||
// It is initialized with the list of files in the manifest when we open a DB.
|
|
||||||
knownObjects map[base.FileNum]ObjectMetadata
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readable is the handle for an object that is open for reading.
|
|
||||||
type Readable interface {
|
|
||||||
io.ReaderAt
|
|
||||||
io.Closer
|
|
||||||
|
|
||||||
// Size returns the size of the object.
|
|
||||||
Size() int64
|
|
||||||
|
|
||||||
// NewReadHandle creates a read handle for ReadAt requests that are related
|
|
||||||
// and can benefit from optimizations like read-ahead.
|
|
||||||
//
|
|
||||||
// The ReadHandle must be closed before the Readable is closed.
|
|
||||||
//
|
|
||||||
// Multiple separate ReadHandles can be used.
|
|
||||||
NewReadHandle() ReadHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadHandle is used to perform reads that are related and might benefit from
|
|
||||||
// optimizations like read-ahead.
|
|
||||||
type ReadHandle interface {
|
|
||||||
io.ReaderAt
|
|
||||||
io.Closer
|
|
||||||
|
|
||||||
// MaxReadahead configures the implementation to expect large sequential
|
|
||||||
// reads. Used to skip any initial read-ahead ramp-up.
|
|
||||||
MaxReadahead()
|
|
||||||
|
|
||||||
// RecordCacheHit informs the implementation that we were able to retrieve a
|
|
||||||
// block from cache.
|
|
||||||
RecordCacheHit(offset, size int64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writable is the handle for an object that is open for writing.
|
|
||||||
// Either Finish or Abort must be called.
|
|
||||||
type Writable interface {
|
|
||||||
// Write writes len(p) bytes from p to the underlying object. The data is not
|
|
||||||
// guaranteed to be durable until Finish is called.
|
|
||||||
//
|
|
||||||
// Note that Write *is* allowed to modify the slice passed in, whether
|
|
||||||
// temporarily or permanently. Callers of Write need to take this into
|
|
||||||
// account.
|
|
||||||
Write(p []byte) error
|
|
||||||
|
|
||||||
// Finish completes the object and makes the data durable.
|
|
||||||
// No further calls are allowed after calling Finish.
|
|
||||||
Finish() error
|
|
||||||
|
|
||||||
// Abort gives up on finishing the object. There is no guarantee about whether
|
|
||||||
// the object exists after calling Abort.
|
|
||||||
// No further calls are allowed after calling Abort.
|
|
||||||
Abort()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Settings that must be specified when creating the Provider.
|
|
||||||
type Settings struct {
|
|
||||||
Logger base.Logger
|
|
||||||
|
|
||||||
// Local filesystem configuration.
|
|
||||||
FS vfs.FS
|
|
||||||
FSDirName string
|
|
||||||
|
|
||||||
// FSDirInitialListing is a listing of FSDirName at the time of calling Open.
|
|
||||||
//
|
|
||||||
// This is an optional optimization to avoid double listing on Open when the
|
|
||||||
// higher layer already has a listing. When nil, we obtain the listing on
|
|
||||||
// Open.
|
|
||||||
FSDirInitialListing []string
|
|
||||||
|
|
||||||
// Cleaner cleans obsolete files from the local filesystem.
|
|
||||||
//
|
|
||||||
// The default cleaner uses the DeleteCleaner.
|
|
||||||
FSCleaner base.Cleaner
|
|
||||||
|
|
||||||
// NoSyncOnClose decides whether the implementation will enforce a
|
|
||||||
// close-time synchronization (e.g., fdatasync() or sync_file_range())
|
|
||||||
// on files it writes to. Setting this to true removes the guarantee for a
|
|
||||||
// sync on close. Some implementations can still issue a non-blocking sync.
|
|
||||||
NoSyncOnClose bool
|
|
||||||
|
|
||||||
// BytesPerSync enables periodic syncing of files in order to smooth out
|
|
||||||
// writes to disk. This option does not provide any persistence guarantee, but
|
|
||||||
// is used to avoid latency spikes if the OS automatically decides to write
|
|
||||||
// out a large chunk of dirty filesystem buffers.
|
|
||||||
BytesPerSync int
|
|
||||||
|
|
||||||
// Fields here are set only if the provider is to support shared objects
|
|
||||||
// (experimental).
|
|
||||||
Shared struct {
|
|
||||||
Storage shared.Storage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultSettings initializes default settings (with no shared storage),
|
|
||||||
// suitable for tests and tools.
|
|
||||||
func DefaultSettings(fs vfs.FS, dirName string) Settings {
|
|
||||||
return Settings{
|
|
||||||
Logger: base.DefaultLogger,
|
|
||||||
FS: fs,
|
|
||||||
FSDirName: dirName,
|
|
||||||
FSCleaner: base.DeleteCleaner{},
|
|
||||||
NoSyncOnClose: false,
|
|
||||||
BytesPerSync: 512 * 1024, // 512KB
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ObjectMetadata contains the metadata required to be able to access an object.
|
|
||||||
type ObjectMetadata struct {
|
|
||||||
FileNum base.FileNum
|
|
||||||
FileType base.FileType
|
|
||||||
|
|
||||||
// The fields below are only set if the object is on shared storage.
|
|
||||||
Shared struct {
|
|
||||||
// CreatorID identifies the DB instance that originally created the object.
|
|
||||||
CreatorID CreatorID
|
|
||||||
// CreatorFileNum is the identifier for the object within the context of the
|
|
||||||
// DB instance that originally created the object.
|
|
||||||
CreatorFileNum base.FileNum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatorID identifies the DB instance that originally created a shared object.
|
|
||||||
// This ID is incorporated in backing object names.
|
|
||||||
// Must be non-zero.
|
|
||||||
type CreatorID = sharedobjcat.CreatorID
|
|
||||||
|
|
||||||
// IsShared returns true if the object is on shared storage.
|
|
||||||
func (meta *ObjectMetadata) IsShared() bool {
|
|
||||||
return meta.Shared.CreatorID.IsSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open creates the Provider.
|
|
||||||
func Open(settings Settings) (p *Provider, _ error) {
|
|
||||||
fsDir, err := settings.FS.OpenDir(settings.FSDirName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if p == nil {
|
|
||||||
fsDir.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
p = &Provider{
|
|
||||||
st: settings,
|
|
||||||
fsDir: fsDir,
|
|
||||||
}
|
|
||||||
p.mu.knownObjects = make(map[base.FileNum]ObjectMetadata)
|
|
||||||
|
|
||||||
// Add local FS objects.
|
|
||||||
if err := p.vfsInit(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize shared subsystem (if configured) and add shared objects.
|
|
||||||
if err := p.sharedInit(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the provider.
|
|
||||||
func (p *Provider) Close() error {
|
|
||||||
var err error
|
|
||||||
if p.fsDir != nil {
|
|
||||||
err = p.fsDir.Close()
|
|
||||||
p.fsDir = nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenForReading opens an existing object.
|
|
||||||
func (p *Provider) OpenForReading(fileType base.FileType, fileNum base.FileNum) (Readable, error) {
|
|
||||||
meta, err := p.Lookup(fileType, fileNum)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !meta.IsShared() {
|
|
||||||
return p.vfsOpenForReading(fileType, fileNum, false /* mustExist */)
|
|
||||||
}
|
|
||||||
return p.sharedOpenForReading(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenForReadingMustExist is a variant of OpenForReading which causes a fatal
|
|
||||||
// error if the file does not exist. The fatal error message contains
|
|
||||||
// information helpful for debugging.
|
|
||||||
func (p *Provider) OpenForReadingMustExist(
|
|
||||||
fileType base.FileType, fileNum base.FileNum,
|
|
||||||
) (Readable, error) {
|
|
||||||
meta, err := p.Lookup(fileType, fileNum)
|
|
||||||
if err != nil {
|
|
||||||
p.st.Logger.Fatalf("%v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !meta.IsShared() {
|
|
||||||
return p.vfsOpenForReading(fileType, fileNum, true /* mustExist */)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(radu): implement "must exist" behavior.
|
|
||||||
return p.sharedOpenForReading(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateOptions contains optional arguments for Create.
|
|
||||||
type CreateOptions struct {
|
|
||||||
// PreferSharedStorage causes the object to be created on shared storage if
|
|
||||||
// the provider has shared storage configured.
|
|
||||||
PreferSharedStorage bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates a new object and opens it for writing.
|
|
||||||
//
|
|
||||||
// The object is not guaranteed to be durable (accessible in case of crashes)
|
|
||||||
// until Sync is called.
|
|
||||||
func (p *Provider) Create(
|
|
||||||
fileType base.FileType, fileNum base.FileNum, opts CreateOptions,
|
|
||||||
) (w Writable, meta ObjectMetadata, err error) {
|
|
||||||
if opts.PreferSharedStorage && p.st.Shared.Storage != nil {
|
|
||||||
w, meta, err = p.sharedCreate(fileType, fileNum)
|
|
||||||
} else {
|
|
||||||
w, meta, err = p.vfsCreate(fileType, fileNum)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
err = errors.Wrapf(err, "creating object %s", errors.Safe(fileNum))
|
|
||||||
return nil, ObjectMetadata{}, err
|
|
||||||
}
|
|
||||||
p.addMetadata(meta)
|
|
||||||
return w, meta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes an object.
|
|
||||||
//
|
|
||||||
// The object is not guaranteed to be durably removed until Sync is called.
|
|
||||||
func (p *Provider) Remove(fileType base.FileType, fileNum base.FileNum) error {
|
|
||||||
meta, err := p.Lookup(fileType, fileNum)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !meta.IsShared() {
|
|
||||||
err = p.vfsRemove(fileType, fileNum)
|
|
||||||
}
|
|
||||||
// TODO(radu): implement shared object removal (i.e. deref).
|
|
||||||
|
|
||||||
if err != nil && !IsNotExistError(err) {
|
|
||||||
// We want to be able to retry a Remove, so we keep the object in our list.
|
|
||||||
// TODO(radu): we should mark the object as "zombie" and not allow any other
|
|
||||||
// operations.
|
|
||||||
return errors.Wrapf(err, "removing object %s", errors.Safe(fileNum))
|
|
||||||
}
|
|
||||||
p.removeMetadata(fileNum)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNotExistError indicates whether the error is known to report that a file or
|
|
||||||
// directory does not exist.
|
|
||||||
func IsNotExistError(err error) bool {
|
|
||||||
return oserror.IsNotExist(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sync flushes the metadata from creation or removal of objects since the last Sync.
|
|
||||||
func (p *Provider) Sync() error {
|
|
||||||
if err := p.vfsSync(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := p.sharedSync(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkOrCopyFromLocal creates a new object that is either a copy of a given
|
|
||||||
// local file or a hard link (if the new object is created on the same FS, and
|
|
||||||
// if the FS supports it).
|
|
||||||
//
|
|
||||||
// The object is not guaranteed to be durable (accessible in case of crashes)
|
|
||||||
// until Sync is called.
|
|
||||||
func (p *Provider) LinkOrCopyFromLocal(
|
|
||||||
srcFS vfs.FS, srcFilePath string, dstFileType base.FileType, dstFileNum base.FileNum,
|
|
||||||
) (ObjectMetadata, error) {
|
|
||||||
if srcFS == p.st.FS {
|
|
||||||
// Wrap the normal filesystem with one which wraps newly created files with
|
|
||||||
// vfs.NewSyncingFile.
|
|
||||||
fs := vfs.NewSyncingFS(p.st.FS, vfs.SyncingFileOptions{
|
|
||||||
NoSyncOnClose: p.st.NoSyncOnClose,
|
|
||||||
BytesPerSync: p.st.BytesPerSync,
|
|
||||||
})
|
|
||||||
dstPath := p.vfsPath(dstFileType, dstFileNum)
|
|
||||||
if err := vfs.LinkOrCopy(fs, srcFilePath, dstPath); err != nil {
|
|
||||||
return ObjectMetadata{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
meta := ObjectMetadata{
|
|
||||||
FileNum: dstFileNum,
|
|
||||||
FileType: dstFileType,
|
|
||||||
}
|
|
||||||
p.addMetadata(meta)
|
|
||||||
return meta, nil
|
|
||||||
}
|
|
||||||
// TODO(radu): for the copy case, we should use `p.Create` and do the copy ourselves.
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup returns the metadata of an object that is already known to the Provider.
|
|
||||||
// Does not perform any I/O.
|
|
||||||
func (p *Provider) Lookup(fileType base.FileType, fileNum base.FileNum) (ObjectMetadata, error) {
|
|
||||||
p.mu.RLock()
|
|
||||||
defer p.mu.RUnlock()
|
|
||||||
meta, ok := p.mu.knownObjects[fileNum]
|
|
||||||
if !ok {
|
|
||||||
return ObjectMetadata{}, errors.Wrapf(
|
|
||||||
os.ErrNotExist,
|
|
||||||
"file %s (type %d) unknown to the objstorage provider",
|
|
||||||
errors.Safe(fileNum), errors.Safe(fileType),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if meta.FileType != fileType {
|
|
||||||
return ObjectMetadata{}, errors.AssertionFailedf(
|
|
||||||
"file %s type mismatch (known type %d, expected type %d)",
|
|
||||||
errors.Safe(fileNum), errors.Safe(meta.FileType), errors.Safe(fileType),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return meta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path returns an internal, implementation-dependent path for the object. It is
|
|
||||||
// meant to be used for informational purposes (like logging).
|
|
||||||
func (p *Provider) Path(meta ObjectMetadata) string {
|
|
||||||
if !meta.IsShared() {
|
|
||||||
return p.vfsPath(meta.FileType, meta.FileNum)
|
|
||||||
}
|
|
||||||
return p.sharedPath(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the size of the object.
|
|
||||||
func (p *Provider) Size(meta ObjectMetadata) (int64, error) {
|
|
||||||
if !meta.IsShared() {
|
|
||||||
return p.vfsSize(meta.FileType, meta.FileNum)
|
|
||||||
}
|
|
||||||
return p.sharedSize(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns the objects currently known to the provider. Does not perform any I/O.
|
|
||||||
func (p *Provider) List() []ObjectMetadata {
|
|
||||||
p.mu.RLock()
|
|
||||||
defer p.mu.RUnlock()
|
|
||||||
res := make([]ObjectMetadata, 0, len(p.mu.knownObjects))
|
|
||||||
for _, meta := range p.mu.knownObjects {
|
|
||||||
res = append(res, meta)
|
|
||||||
}
|
|
||||||
sort.Slice(res, func(i, j int) bool {
|
|
||||||
return res[i].FileNum < res[j].FileNum
|
|
||||||
})
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) addMetadata(meta ObjectMetadata) {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
p.mu.knownObjects[meta.FileNum] = meta
|
|
||||||
if meta.IsShared() {
|
|
||||||
p.mu.shared.catalogBatch.AddObject(sharedobjcat.SharedObjectMetadata{
|
|
||||||
FileNum: meta.FileNum,
|
|
||||||
FileType: meta.FileType,
|
|
||||||
CreatorID: meta.Shared.CreatorID,
|
|
||||||
CreatorFileNum: meta.Shared.CreatorFileNum,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
p.mu.localObjectsChanged = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) removeMetadata(fileNum base.FileNum) {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
meta, ok := p.mu.knownObjects[fileNum]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delete(p.mu.knownObjects, fileNum)
|
|
||||||
if meta.IsShared() {
|
|
||||||
p.mu.shared.catalogBatch.DeleteObject(fileNum)
|
|
||||||
} else {
|
|
||||||
p.mu.localObjectsChanged = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
vendor/github.com/cockroachdb/pebble/objstorage/remote/factory.go
generated
vendored
Normal file
25
vendor/github.com/cockroachdb/pebble/objstorage/remote/factory.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// 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 remote
|
||||||
|
|
||||||
|
import "github.com/pkg/errors"
|
||||||
|
|
||||||
|
// MakeSimpleFactory returns a StorageFactory implementation that produces the given
|
||||||
|
// Storage objects.
|
||||||
|
func MakeSimpleFactory(m map[Locator]Storage) StorageFactory {
|
||||||
|
return simpleFactory(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleFactory map[Locator]Storage
|
||||||
|
|
||||||
|
var _ StorageFactory = simpleFactory{}
|
||||||
|
|
||||||
|
// CreateStorage is part of the StorageFactory interface.
|
||||||
|
func (sf simpleFactory) CreateStorage(locator Locator) (Storage, error) {
|
||||||
|
if s, ok := sf[locator]; ok {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
return nil, errors.Errorf("unknown locator '%s'", locator)
|
||||||
|
}
|
||||||
118
vendor/github.com/cockroachdb/pebble/objstorage/remote/localfs.go
generated
vendored
Normal file
118
vendor/github.com/cockroachdb/pebble/objstorage/remote/localfs.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
// 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 remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLocalFS returns a vfs-backed implementation of the remote.Storage
|
||||||
|
// interface (for testing). All objects will be stored at the directory
|
||||||
|
// dirname.
|
||||||
|
func NewLocalFS(dirname string, fs vfs.FS) Storage {
|
||||||
|
store := &localFSStore{
|
||||||
|
dirname: dirname,
|
||||||
|
vfs: fs,
|
||||||
|
}
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
// localFSStore is a vfs-backed implementation of the remote.Storage
|
||||||
|
// interface (for testing).
|
||||||
|
type localFSStore struct {
|
||||||
|
dirname string
|
||||||
|
vfs vfs.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Storage = (*localFSStore)(nil)
|
||||||
|
|
||||||
|
// Close is part of the remote.Storage interface.
|
||||||
|
func (s *localFSStore) Close() error {
|
||||||
|
*s = localFSStore{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadObject is part of the remote.Storage interface.
|
||||||
|
func (s *localFSStore) ReadObject(
|
||||||
|
ctx context.Context, objName string,
|
||||||
|
) (_ ObjectReader, objSize int64, _ error) {
|
||||||
|
f, err := s.vfs.Open(path.Join(s.dirname, objName))
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
stat, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &localFSReader{f}, stat.Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type localFSReader struct {
|
||||||
|
file vfs.File
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ObjectReader = (*localFSReader)(nil)
|
||||||
|
|
||||||
|
// ReadAt is part of the shared.ObjectReader interface.
|
||||||
|
func (r *localFSReader) ReadAt(_ context.Context, p []byte, offset int64) error {
|
||||||
|
n, err := r.file.ReadAt(p, offset)
|
||||||
|
// https://pkg.go.dev/io#ReaderAt
|
||||||
|
if err == io.EOF && n == len(p) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is part of the shared.ObjectReader interface.
|
||||||
|
func (r *localFSReader) Close() error {
|
||||||
|
r.file.Close()
|
||||||
|
r.file = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateObject is part of the remote.Storage interface.
|
||||||
|
func (s *localFSStore) CreateObject(objName string) (io.WriteCloser, error) {
|
||||||
|
file, err := s.vfs.Create(path.Join(s.dirname, objName))
|
||||||
|
return file, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// List is part of the remote.Storage interface.
|
||||||
|
func (s *localFSStore) List(prefix, delimiter string) ([]string, error) {
|
||||||
|
// TODO(josh): For the intended use case of localfs.go of running 'pebble bench',
|
||||||
|
// List can always return <nil, nil>, since this indicates a file has only one ref,
|
||||||
|
// and since `pebble bench` implies running in a single-pebble-instance context.
|
||||||
|
// https://github.com/cockroachdb/pebble/blob/a9a079d4fb6bf4a9ebc52e4d83a76ad4cbf676cb/objstorage/objstorageprovider/shared.go#L292
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete is part of the remote.Storage interface.
|
||||||
|
func (s *localFSStore) Delete(objName string) error {
|
||||||
|
return s.vfs.Remove(path.Join(s.dirname, objName))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size is part of the remote.Storage interface.
|
||||||
|
func (s *localFSStore) Size(objName string) (int64, error) {
|
||||||
|
f, err := s.vfs.Open(path.Join(s.dirname, objName))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
stat, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return stat.Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotExistError is part of the remote.Storage interface.
|
||||||
|
func (s *localFSStore) IsNotExistError(err error) bool {
|
||||||
|
return err == os.ErrNotExist
|
||||||
|
}
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
// of this source code is governed by a BSD-style license that can be found in
|
// of this source code is governed by a BSD-style license that can be found in
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
|
|
||||||
package shared
|
package remote
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -19,7 +20,7 @@ func WithLogging(wrapped Storage, logf func(fmt string, args ...interface{})) St
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loggingStore wraps a shared.Storage implementation and emits logs of the
|
// loggingStore wraps a remote.Storage implementation and emits logs of the
|
||||||
// operations.
|
// operations.
|
||||||
type loggingStore struct {
|
type loggingStore struct {
|
||||||
logf func(fmt string, args ...interface{})
|
logf func(fmt string, args ...interface{})
|
||||||
@@ -33,41 +34,52 @@ func (l *loggingStore) Close() error {
|
|||||||
return l.wrapped.Close()
|
return l.wrapped.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loggingStore) ReadObjectAt(
|
func (l *loggingStore) ReadObject(
|
||||||
basename string, offset int64,
|
ctx context.Context, objName string,
|
||||||
) (_ io.ReadCloser, totalSize int64, _ error) {
|
) (_ ObjectReader, objSize int64, _ error) {
|
||||||
r, totalSize, err := l.wrapped.ReadObjectAt(basename, offset)
|
r, size, err := l.wrapped.ReadObject(ctx, objName)
|
||||||
l.logf("read object %q at %d: %s", basename, offset, errOrPrintf(err, "%d bytes", totalSize))
|
l.logf("create reader for object %q: %s", objName, errOrPrintf(err, "%d bytes", size))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
return &loggingReader{
|
return &loggingReader{
|
||||||
l: l,
|
l: l,
|
||||||
name: basename,
|
name: objName,
|
||||||
ReadCloser: r,
|
wrapped: r,
|
||||||
}, totalSize, nil
|
}, size, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type loggingReader struct {
|
type loggingReader struct {
|
||||||
l *loggingStore
|
l *loggingStore
|
||||||
name string
|
name string
|
||||||
io.ReadCloser
|
wrapped ObjectReader
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ObjectReader = (*loggingReader)(nil)
|
||||||
|
|
||||||
|
func (l *loggingReader) ReadAt(ctx context.Context, p []byte, offset int64) error {
|
||||||
|
if err := l.wrapped.ReadAt(ctx, p, offset); err != nil {
|
||||||
|
l.l.logf("read object %q at %d (length %d): error %v", l.name, offset, len(p), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.l.logf("read object %q at %d (length %d)", l.name, offset, len(p))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loggingReader) Close() error {
|
func (l *loggingReader) Close() error {
|
||||||
l.l.logf("close reader for %q", l.name)
|
l.l.logf("close reader for %q", l.name)
|
||||||
return l.ReadCloser.Close()
|
return l.wrapped.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loggingStore) CreateObject(basename string) (io.WriteCloser, error) {
|
func (l *loggingStore) CreateObject(objName string) (io.WriteCloser, error) {
|
||||||
l.logf("create object %q", basename)
|
l.logf("create object %q", objName)
|
||||||
writer, err := l.wrapped.CreateObject(basename)
|
writer, err := l.wrapped.CreateObject(objName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &loggingWriter{
|
return &loggingWriter{
|
||||||
l: l,
|
l: l,
|
||||||
name: basename,
|
name: objName,
|
||||||
WriteCloser: writer,
|
WriteCloser: writer,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -104,14 +116,14 @@ func (l *loggingStore) List(prefix, delimiter string) ([]string, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loggingStore) Delete(basename string) error {
|
func (l *loggingStore) Delete(objName string) error {
|
||||||
l.logf("delete object %q", basename)
|
l.logf("delete object %q", objName)
|
||||||
return l.wrapped.Delete(basename)
|
return l.wrapped.Delete(objName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *loggingStore) Size(basename string) (int64, error) {
|
func (l *loggingStore) Size(objName string) (int64, error) {
|
||||||
size, err := l.wrapped.Size(basename)
|
size, err := l.wrapped.Size(objName)
|
||||||
l.logf("size of object %q: %s", basename, errOrPrintf(err, "%d", size))
|
l.logf("size of object %q: %s", objName, errOrPrintf(err, "%d", size))
|
||||||
return size, err
|
return size, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,3 +133,7 @@ func errOrPrintf(err error, format string, args ...interface{}) string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf(format, args...)
|
return fmt.Sprintf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *loggingStore) IsNotExistError(err error) bool {
|
||||||
|
return l.wrapped.IsNotExistError(err)
|
||||||
|
}
|
||||||
@@ -2,17 +2,18 @@
|
|||||||
// of this source code is governed by a BSD-style license that can be found in
|
// of this source code is governed by a BSD-style license that can be found in
|
||||||
// the LICENSE file.
|
// the LICENSE file.
|
||||||
|
|
||||||
package shared
|
package remote
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewInMem returns an in-memory implementation of the shared.Storage
|
// NewInMem returns an in-memory implementation of the remote.Storage
|
||||||
// interface (for testing).
|
// interface (for testing).
|
||||||
func NewInMem() Storage {
|
func NewInMem() Storage {
|
||||||
store := &inMemStore{}
|
store := &inMemStore{}
|
||||||
@@ -20,7 +21,7 @@ func NewInMem() Storage {
|
|||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|
||||||
// inMemStore is an in-memory implementation of the shared.Storage interface
|
// inMemStore is an in-memory implementation of the remote.Storage interface
|
||||||
// (for testing).
|
// (for testing).
|
||||||
type inMemStore struct {
|
type inMemStore struct {
|
||||||
mu struct {
|
mu struct {
|
||||||
@@ -41,39 +42,39 @@ func (s *inMemStore) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *inMemStore) ReadObjectAt(basename string, offset int64) (io.ReadCloser, int64, error) {
|
func (s *inMemStore) ReadObject(
|
||||||
obj, err := s.getObj(basename)
|
ctx context.Context, objName string,
|
||||||
|
) (_ ObjectReader, objSize int64, _ error) {
|
||||||
|
obj, err := s.getObj(objName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
remaining := int64(len(obj.data)) - offset
|
return &inMemReader{data: obj.data}, int64(len(obj.data)), nil
|
||||||
if remaining < 0 {
|
|
||||||
return nil, 0, io.EOF
|
|
||||||
}
|
|
||||||
return newInMemReader(obj.data[int(offset):]), remaining, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type inMemReader struct {
|
type inMemReader struct {
|
||||||
bytes.Reader
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInMemReader(b []byte) *inMemReader {
|
var _ ObjectReader = (*inMemReader)(nil)
|
||||||
r := &inMemReader{}
|
|
||||||
r.Reset(b)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ io.ReadCloser = (*inMemReader)(nil)
|
func (r *inMemReader) ReadAt(ctx context.Context, p []byte, offset int64) error {
|
||||||
|
if offset+int64(len(p)) > int64(len(r.data)) {
|
||||||
func (r *inMemReader) Close() error {
|
return io.EOF
|
||||||
r.Reset(nil)
|
}
|
||||||
|
copy(p, r.data[offset:])
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *inMemStore) CreateObject(basename string) (io.WriteCloser, error) {
|
func (r *inMemReader) Close() error {
|
||||||
|
r.data = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *inMemStore) CreateObject(objName string) (io.WriteCloser, error) {
|
||||||
return &inMemWriter{
|
return &inMemWriter{
|
||||||
store: s,
|
store: s,
|
||||||
name: basename,
|
name: objName,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,20 +120,24 @@ func (s *inMemStore) List(prefix, delimiter string) ([]string, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *inMemStore) Delete(basename string) error {
|
func (s *inMemStore) Delete(objName string) error {
|
||||||
s.rmObj(basename)
|
s.rmObj(objName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size returns the length of the named object in bytesWritten.
|
// Size returns the length of the named object in bytesWritten.
|
||||||
func (s *inMemStore) Size(basename string) (int64, error) {
|
func (s *inMemStore) Size(objName string) (int64, error) {
|
||||||
obj, err := s.getObj(basename)
|
obj, err := s.getObj(objName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return int64(len(obj.data)), nil
|
return int64(len(obj.data)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *inMemStore) IsNotExistError(err error) bool {
|
||||||
|
return err == os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
func (s *inMemStore) getObj(name string) (*inMemObj, error) {
|
func (s *inMemStore) getObj(name string) (*inMemObj, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
126
vendor/github.com/cockroachdb/pebble/objstorage/remote/storage.go
generated
vendored
Normal file
126
vendor/github.com/cockroachdb/pebble/objstorage/remote/storage.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// 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 remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Locator is an opaque string identifying a remote.Storage implementation.
|
||||||
|
//
|
||||||
|
// The Locator must not contain secrets (like authentication keys). Locators are
|
||||||
|
// stored on disk in the shared object catalog and are passed around as part of
|
||||||
|
// RemoteObjectBacking; they can also appear in error messages.
|
||||||
|
type Locator string
|
||||||
|
|
||||||
|
// StorageFactory is used to return Storage implementations based on locators. A
|
||||||
|
// Pebble store that uses shared storage is configured with a StorageFactory.
|
||||||
|
type StorageFactory interface {
|
||||||
|
CreateStorage(locator Locator) (Storage, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SharedLevelsStart denotes the highest (i.e. lowest numbered) level that will
|
||||||
|
// have sstables shared across Pebble instances when doing skip-shared
|
||||||
|
// iteration (see db.ScanInternal) or shared file ingestion (see
|
||||||
|
// db.IngestAndExcise).
|
||||||
|
const SharedLevelsStart = 5
|
||||||
|
|
||||||
|
// CreateOnSharedStrategy specifies what table files should be created on shared
|
||||||
|
// storage. For use with CreateOnShared in options.
|
||||||
|
type CreateOnSharedStrategy int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CreateOnSharedNone denotes no files being created on shared storage.
|
||||||
|
CreateOnSharedNone CreateOnSharedStrategy = iota
|
||||||
|
// CreateOnSharedLower denotes the creation of files in lower levels of the
|
||||||
|
// LSM (specifically, L5 and L6 as they're below SharedLevelsStart) on
|
||||||
|
// shared storage, and higher levels on local storage.
|
||||||
|
CreateOnSharedLower
|
||||||
|
// CreateOnSharedAll denotes the creation of all sstables on shared storage.
|
||||||
|
CreateOnSharedAll
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShouldCreateShared returns whether new table files at the specified level
|
||||||
|
// should be created on shared storage.
|
||||||
|
func ShouldCreateShared(strategy CreateOnSharedStrategy, level int) bool {
|
||||||
|
switch strategy {
|
||||||
|
case CreateOnSharedAll:
|
||||||
|
return true
|
||||||
|
case CreateOnSharedNone:
|
||||||
|
return false
|
||||||
|
case CreateOnSharedLower:
|
||||||
|
return level >= SharedLevelsStart
|
||||||
|
default:
|
||||||
|
panic("unexpected CreateOnSharedStrategy value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage is an interface for a blob storage driver. This is lower-level
|
||||||
|
// than an FS-like interface, however FS/File-like abstractions can be built on
|
||||||
|
// top of these methods.
|
||||||
|
//
|
||||||
|
// TODO(bilal): Consider pushing shared file obsoletion as well as path
|
||||||
|
// generation behind this interface.
|
||||||
|
type Storage interface {
|
||||||
|
io.Closer
|
||||||
|
|
||||||
|
// ReadObject returns an ObjectReader that can be used to perform reads on an
|
||||||
|
// object, along with the total size of the object.
|
||||||
|
ReadObject(ctx context.Context, objName string) (_ ObjectReader, objSize int64, _ error)
|
||||||
|
|
||||||
|
// CreateObject returns a writer for the object at the request name. A new
|
||||||
|
// empty object is created if CreateObject is called on an existing object.
|
||||||
|
//
|
||||||
|
// A Writer *must* be closed via either Close, and if closing returns a
|
||||||
|
// non-nil error, that error should be handled or reported to the user -- an
|
||||||
|
// implementation may buffer written data until Close and only then return
|
||||||
|
// an error, or Write may return an opaque io.EOF with the underlying cause
|
||||||
|
// returned by the subsequent Close().
|
||||||
|
//
|
||||||
|
// TODO(radu): if we encounter some unrelated error while writing to the
|
||||||
|
// WriteCloser, we'd want to abort the whole thing rather than letting Close
|
||||||
|
// finalize the upload.
|
||||||
|
CreateObject(objName string) (io.WriteCloser, error)
|
||||||
|
|
||||||
|
// List enumerates files within the supplied prefix, returning a list of
|
||||||
|
// objects within that prefix. If delimiter is non-empty, names which have the
|
||||||
|
// same prefix, prior to the delimiter but after the prefix, are grouped into a
|
||||||
|
// single result which is that prefix. The order that results are returned is
|
||||||
|
// undefined. If a prefix is specified, the prefix is trimmed from the result
|
||||||
|
// list.
|
||||||
|
//
|
||||||
|
// An example would be, if the storage contains objects a, b/4, b/5 and b/6,
|
||||||
|
// these would be the return values:
|
||||||
|
// List("", "") -> ["a", "b/4", "b/5", "b/6"]
|
||||||
|
// List("", "/") -> ["a", "b"]
|
||||||
|
// List("b", "/") -> ["4", "5", "6"]
|
||||||
|
// List("b", "") -> ["/4", "/5", "/6"]
|
||||||
|
List(prefix, delimiter string) ([]string, error)
|
||||||
|
|
||||||
|
// Delete removes the named object from the store.
|
||||||
|
Delete(objName string) error
|
||||||
|
|
||||||
|
// Size returns the length of the named object in bytesWritten.
|
||||||
|
Size(objName string) (int64, error)
|
||||||
|
|
||||||
|
// IsNotExistError returns true if the given error (returned by a method in
|
||||||
|
// this interface) indicates that the object does not exist.
|
||||||
|
IsNotExistError(err error) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectReader is used to perform reads on an object.
|
||||||
|
type ObjectReader interface {
|
||||||
|
// ReadAt reads len(p) bytes into p starting at offset off.
|
||||||
|
//
|
||||||
|
// Does not return partial results; if offset + len(p) is past the end of the
|
||||||
|
// object, an error is returned.
|
||||||
|
//
|
||||||
|
// Clients of ReadAt can execute parallel ReadAt calls on the same
|
||||||
|
// ObjectReader.
|
||||||
|
ReadAt(ctx context.Context, p []byte, offset int64) error
|
||||||
|
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
176
vendor/github.com/cockroachdb/pebble/objstorage/shared.go
generated
vendored
176
vendor/github.com/cockroachdb/pebble/objstorage/shared.go
generated
vendored
@@ -1,176 +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 objstorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
|
||||||
"github.com/cockroachdb/pebble/objstorage/sharedobjcat"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// initialized guards access to the creatorID field.
|
|
||||||
initialized atomic.Bool
|
|
||||||
creatorID CreatorID
|
|
||||||
initOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *sharedSubsystem) init(creatorID CreatorID) {
|
|
||||||
ss.initOnce.Do(func() {
|
|
||||||
ss.creatorID = creatorID
|
|
||||||
ss.initialized.Store(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// sharedInit initializes the shared object subsystem (if configured) and finds
|
|
||||||
// any shared objects.
|
|
||||||
func (p *Provider) sharedInit() error {
|
|
||||||
if p.st.Shared.Storage == 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
|
|
||||||
|
|
||||||
// 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 := ObjectMetadata{
|
|
||||||
FileNum: meta.FileNum,
|
|
||||||
FileType: meta.FileType,
|
|
||||||
}
|
|
||||||
o.Shared.CreatorID = meta.CreatorID
|
|
||||||
o.Shared.CreatorFileNum = meta.CreatorFileNum
|
|
||||||
p.mu.knownObjects[o.FileNum] = o
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCreatorID sets the CreatorID which is needed in order to use shared
|
|
||||||
// objects. Shared 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.
|
|
||||||
func (p *Provider) SetCreatorID(creatorID CreatorID) error {
|
|
||||||
if p.st.Shared.Storage == 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.st.Shared.Storage == 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 ObjectMetadata) string {
|
|
||||||
return "shared://" + sharedObjectName(meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sharedObjectName(meta ObjectMetadata) string {
|
|
||||||
// TODO(radu): prepend a "shard" value for better distribution within the bucket?
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%s-%s",
|
|
||||||
meta.Shared.CreatorID, base.MakeFilename(meta.FileType, meta.Shared.CreatorFileNum),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) sharedCreate(
|
|
||||||
fileType base.FileType, fileNum base.FileNum,
|
|
||||||
) (Writable, ObjectMetadata, error) {
|
|
||||||
if err := p.sharedCheckInitialized(); err != nil {
|
|
||||||
return nil, ObjectMetadata{}, err
|
|
||||||
}
|
|
||||||
meta := ObjectMetadata{
|
|
||||||
FileNum: fileNum,
|
|
||||||
FileType: fileType,
|
|
||||||
}
|
|
||||||
meta.Shared.CreatorID = p.shared.creatorID
|
|
||||||
meta.Shared.CreatorFileNum = fileNum
|
|
||||||
|
|
||||||
objName := sharedObjectName(meta)
|
|
||||||
writer, err := p.st.Shared.Storage.CreateObject(objName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, ObjectMetadata{}, err
|
|
||||||
}
|
|
||||||
return &sharedWritable{
|
|
||||||
storageWriter: writer,
|
|
||||||
}, meta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) sharedOpenForReading(meta ObjectMetadata) (Readable, error) {
|
|
||||||
if err := p.sharedCheckInitialized(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
objName := sharedObjectName(meta)
|
|
||||||
size, err := p.st.Shared.Storage.Size(objName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newSharedReadable(p.st.Shared.Storage, objName, size), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) sharedSize(meta ObjectMetadata) (int64, error) {
|
|
||||||
if err := p.sharedCheckInitialized(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
objName := sharedObjectName(meta)
|
|
||||||
return p.st.Shared.Storage.Size(objName)
|
|
||||||
}
|
|
||||||
61
vendor/github.com/cockroachdb/pebble/objstorage/shared/storage.go
generated
vendored
61
vendor/github.com/cockroachdb/pebble/objstorage/shared/storage.go
generated
vendored
@@ -1,61 +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 shared
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// Storage is an interface for a blob storage driver. This is lower-level
|
|
||||||
// than an FS-like interface, however FS/File-like abstractions can be built on
|
|
||||||
// top of these methods.
|
|
||||||
//
|
|
||||||
// TODO(bilal): Consider pushing shared file obsoletion as well as path
|
|
||||||
// generation behind this interface.
|
|
||||||
type Storage interface {
|
|
||||||
io.Closer
|
|
||||||
|
|
||||||
// ReadObjectAt returns a Reader for reading the object at the requested name
|
|
||||||
// and offset, along with the total size of the object.
|
|
||||||
ReadObjectAt(basename string, offset int64) (_ io.ReadCloser, totalSize int64, _ error)
|
|
||||||
|
|
||||||
// CreateObject returns a writer for the object at the request name. A new
|
|
||||||
// empty object is created if CreateObject is called on an existing object.
|
|
||||||
//
|
|
||||||
// A Writer *must* be closed via either Close, and if closing returns a
|
|
||||||
// non-nil error, that error should be handled or reported to the user -- an
|
|
||||||
// implementation may buffer written data until Close and only then return
|
|
||||||
// an error, or Write may return an opaque io.EOF with the underlying cause
|
|
||||||
// returned by the subsequent Close().
|
|
||||||
//
|
|
||||||
// TODO(radu): if we encounter some unrelated error while writing to the
|
|
||||||
// WriteCloser, we'd want to abort the whole thing rather than letting Close
|
|
||||||
// finalize the upload.
|
|
||||||
CreateObject(basename string) (io.WriteCloser, error)
|
|
||||||
|
|
||||||
// List enumerates files within the supplied prefix, returning a list of
|
|
||||||
// objects within that prefix. If delimiter is non-empty, names which have the
|
|
||||||
// same prefix, prior to the delimiter but after the prefix, are grouped into a
|
|
||||||
// single result which is that prefix. The order that results are returned is
|
|
||||||
// undefined. If a prefix is specified, the prefix is trimmed from the result
|
|
||||||
// list.
|
|
||||||
//
|
|
||||||
// An example would be, if the storage contains objects a, b/4, b/5 and b/6,
|
|
||||||
// these would be the return values:
|
|
||||||
// List("", "") -> ["a", "b/4", "b/5", "b/6"]
|
|
||||||
// List("", "/") -> ["a", "b"]
|
|
||||||
// List("b", "/") -> ["4", "5", "6"]
|
|
||||||
// List("b", "") -> ["/4", "/5", "/6"]
|
|
||||||
List(prefix, delimiter string) ([]string, error)
|
|
||||||
|
|
||||||
// Delete removes the named object from the store.
|
|
||||||
//
|
|
||||||
// TODO(radu): do we get an error when the object isn't there? How can we
|
|
||||||
// check for that error?
|
|
||||||
Delete(basename string) error
|
|
||||||
|
|
||||||
// Size returns the length of the named object in bytesWritten.
|
|
||||||
//
|
|
||||||
// TODO(radu): same as above - how can we tell if it's a "no such object" error?
|
|
||||||
Size(basename string) (int64, error)
|
|
||||||
}
|
|
||||||
145
vendor/github.com/cockroachdb/pebble/objstorage/shared_backing.go
generated
vendored
145
vendor/github.com/cockroachdb/pebble/objstorage/shared_backing.go
generated
vendored
@@ -1,145 +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 objstorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
|
||||||
"github.com/cockroachdb/pebble/objstorage/sharedobjcat"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SharedObjectBacking encodes the metadata necessary to incorporate a shared
|
|
||||||
// object into a different Pebble instance.
|
|
||||||
type SharedObjectBacking []byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
tagCreatorID = 1
|
|
||||||
tagCreatorFileNum = 2
|
|
||||||
|
|
||||||
// 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).
|
|
||||||
|
|
||||||
// Any new tags that have the tagNotSafeToIgnoreMask bit set cause errors if
|
|
||||||
// they are encountered by earlier code that doesn't know the tag.
|
|
||||||
tagNotSafeToIgnoreMask = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
// SharedObjectBacking encodes the shared object metadata.
|
|
||||||
func (meta *ObjectMetadata) SharedObjectBacking() (SharedObjectBacking, error) {
|
|
||||||
if !meta.IsShared() {
|
|
||||||
return nil, errors.AssertionFailedf("object %s not on shared storage", meta.FileNum)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 0, binary.MaxVarintLen64*4)
|
|
||||||
buf = binary.AppendUvarint(buf, tagCreatorID)
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(meta.Shared.CreatorID))
|
|
||||||
buf = binary.AppendUvarint(buf, tagCreatorFileNum)
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(meta.Shared.CreatorFileNum))
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fromSharedObjectBacking decodes the shared object metadata.
|
|
||||||
func fromSharedObjectBacking(
|
|
||||||
fileType base.FileType, fileNum base.FileNum, buf SharedObjectBacking,
|
|
||||||
) (ObjectMetadata, error) {
|
|
||||||
var creatorID uint64
|
|
||||||
var creatorFileNum uint64
|
|
||||||
br := bytes.NewReader(buf)
|
|
||||||
for {
|
|
||||||
tag, err := binary.ReadUvarint(br)
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return ObjectMetadata{}, err
|
|
||||||
}
|
|
||||||
switch tag {
|
|
||||||
case tagCreatorID:
|
|
||||||
creatorID, err = binary.ReadUvarint(br)
|
|
||||||
|
|
||||||
case tagCreatorFileNum:
|
|
||||||
creatorFileNum, err = binary.ReadUvarint(br)
|
|
||||||
|
|
||||||
// TODO(radu): encode file type as well?
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Ignore unknown tags, unless they're not safe to ignore.
|
|
||||||
if tag&tagNotSafeToIgnoreMask != 0 {
|
|
||||||
return ObjectMetadata{}, errors.Newf("unknown tag %d", tag)
|
|
||||||
}
|
|
||||||
var dataLen uint64
|
|
||||||
dataLen, err = binary.ReadUvarint(br)
|
|
||||||
if err == nil {
|
|
||||||
_, err = br.Seek(int64(dataLen), io.SeekCurrent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return ObjectMetadata{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if creatorID == 0 {
|
|
||||||
return ObjectMetadata{}, errors.Newf("shared object backing missing creator ID")
|
|
||||||
}
|
|
||||||
if creatorFileNum == 0 {
|
|
||||||
return ObjectMetadata{}, errors.Newf("shared object backing missing creator file num")
|
|
||||||
}
|
|
||||||
meta := ObjectMetadata{
|
|
||||||
FileNum: fileNum,
|
|
||||||
FileType: fileType,
|
|
||||||
}
|
|
||||||
meta.Shared.CreatorID = CreatorID(creatorID)
|
|
||||||
meta.Shared.CreatorFileNum = base.FileNum(creatorFileNum)
|
|
||||||
return meta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SharedObjectToAttach contains the arguments needed to attach an existing shared object.
|
|
||||||
type SharedObjectToAttach struct {
|
|
||||||
// FileNum is the file number that will be used to refer to this object (in
|
|
||||||
// the context of this instance).
|
|
||||||
FileNum base.FileNum
|
|
||||||
FileType base.FileType
|
|
||||||
// Backing contains the metadata for the share dobject backing (normally
|
|
||||||
// generated from a different instance).
|
|
||||||
Backing SharedObjectBacking
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttachSharedObjects registers existing shared objects with this provider.
|
|
||||||
func (p *Provider) AttachSharedObjects(objs []SharedObjectToAttach) ([]ObjectMetadata, error) {
|
|
||||||
metas := make([]ObjectMetadata, len(objs))
|
|
||||||
for i, o := range objs {
|
|
||||||
meta, err := fromSharedObjectBacking(o.FileType, o.FileNum, o.Backing)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
metas[i] = meta
|
|
||||||
}
|
|
||||||
|
|
||||||
func() {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
for _, meta := range metas {
|
|
||||||
p.mu.shared.catalogBatch.AddObject(sharedobjcat.SharedObjectMetadata{
|
|
||||||
FileNum: meta.FileNum,
|
|
||||||
FileType: meta.FileType,
|
|
||||||
CreatorID: meta.Shared.CreatorID,
|
|
||||||
CreatorFileNum: meta.Shared.CreatorFileNum,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if err := p.sharedSync(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
for _, meta := range metas {
|
|
||||||
p.mu.knownObjects[meta.FileNum] = meta
|
|
||||||
}
|
|
||||||
return metas, nil
|
|
||||||
}
|
|
||||||
97
vendor/github.com/cockroachdb/pebble/objstorage/shared_readable.go
generated
vendored
97
vendor/github.com/cockroachdb/pebble/objstorage/shared_readable.go
generated
vendored
@@ -1,97 +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 objstorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/pebble/objstorage/shared"
|
|
||||||
)
|
|
||||||
|
|
||||||
// sharedReadable is a very simple implementation of Readable on top of the
|
|
||||||
// ReadCloser returned by shared.Storage.CreateObject.
|
|
||||||
type sharedReadable struct {
|
|
||||||
storage shared.Storage
|
|
||||||
objName string
|
|
||||||
size int64
|
|
||||||
|
|
||||||
// rh is used for direct ReadAt calls without a read handle.
|
|
||||||
rh sharedReadHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Readable = (*sharedReadable)(nil)
|
|
||||||
|
|
||||||
func newSharedReadable(storage shared.Storage, objName string, size int64) *sharedReadable {
|
|
||||||
r := &sharedReadable{
|
|
||||||
storage: storage,
|
|
||||||
objName: objName,
|
|
||||||
size: size,
|
|
||||||
}
|
|
||||||
r.rh.readable = r
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *sharedReadable) ReadAt(p []byte, offset int64) (n int, err error) {
|
|
||||||
return r.rh.ReadAt(p, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *sharedReadable) Close() error {
|
|
||||||
err := r.rh.Close()
|
|
||||||
r.storage = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *sharedReadable) Size() int64 {
|
|
||||||
return r.size
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *sharedReadable) NewReadHandle() ReadHandle {
|
|
||||||
// TODO(radu): use a pool.
|
|
||||||
return &sharedReadHandle{readable: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
type sharedReadHandle struct {
|
|
||||||
readable *sharedReadable
|
|
||||||
lastReader io.ReadCloser
|
|
||||||
lastOffset int64
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ReadHandle = (*sharedReadHandle)(nil)
|
|
||||||
|
|
||||||
func (r *sharedReadHandle) ReadAt(p []byte, offset int64) (n int, err error) {
|
|
||||||
// See if this continues the previous read so that we can reuse the last reader.
|
|
||||||
if r.lastReader == nil || r.lastOffset != offset {
|
|
||||||
// We need to create a new reader.
|
|
||||||
if r.lastReader != nil {
|
|
||||||
if err := r.lastReader.Close(); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
r.lastReader = nil
|
|
||||||
}
|
|
||||||
reader, _, err := r.readable.storage.ReadObjectAt(r.readable.objName, offset)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
r.lastReader = reader
|
|
||||||
r.lastOffset = offset
|
|
||||||
}
|
|
||||||
n, err = io.ReadFull(r.lastReader, p)
|
|
||||||
r.lastOffset += int64(n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *sharedReadHandle) Close() error {
|
|
||||||
var err error
|
|
||||||
if r.lastReader != nil {
|
|
||||||
err = r.lastReader.Close()
|
|
||||||
r.lastReader = nil
|
|
||||||
}
|
|
||||||
r.readable = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *sharedReadHandle) MaxReadahead() {}
|
|
||||||
|
|
||||||
func (r *sharedReadHandle) RecordCacheHit(offset, size int64) {}
|
|
||||||
34
vendor/github.com/cockroachdb/pebble/objstorage/shared_writable.go
generated
vendored
34
vendor/github.com/cockroachdb/pebble/objstorage/shared_writable.go
generated
vendored
@@ -1,34 +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 objstorage
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// sharedWritable is a very simple implementation of Writable on top of the
|
|
||||||
// WriteCloser returned by shared.Storage.CreateObject.
|
|
||||||
type sharedWritable struct {
|
|
||||||
storageWriter io.WriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Writable = (*sharedWritable)(nil)
|
|
||||||
|
|
||||||
// Write is part of the Writable interface.
|
|
||||||
func (w *sharedWritable) Write(p []byte) error {
|
|
||||||
_, err := w.storageWriter.Write(p)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish is part of the Writable interface.
|
|
||||||
func (w *sharedWritable) Finish() error {
|
|
||||||
err := w.storageWriter.Close()
|
|
||||||
w.storageWriter = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Abort is part of the Writable interface.
|
|
||||||
func (w *sharedWritable) Abort() {
|
|
||||||
_ = w.storageWriter.Close()
|
|
||||||
w.storageWriter = nil
|
|
||||||
}
|
|
||||||
158
vendor/github.com/cockroachdb/pebble/objstorage/sharedobjcat/version_edit.go
generated
vendored
158
vendor/github.com/cockroachdb/pebble/objstorage/sharedobjcat/version_edit.go
generated
vendored
@@ -1,158 +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 sharedobjcat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/cockroachdb/errors"
|
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
|
||||||
)
|
|
||||||
|
|
||||||
// versionEdit is a modification to the shared 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
|
|
||||||
DeletedObjects []base.FileNum
|
|
||||||
CreatorID CreatorID
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// tagNewObject is followed by the FileNum, creator ID and creator FileNum.
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
// Object type values. We don't want to encode FileType directly because it is
|
|
||||||
// more general (and we want freedom to change it in the future).
|
|
||||||
const (
|
|
||||||
objTypeTable = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
func objTypeToFileType(objType uint64) (base.FileType, error) {
|
|
||||||
switch objType {
|
|
||||||
case objTypeTable:
|
|
||||||
return base.FileTypeTable, nil
|
|
||||||
default:
|
|
||||||
return 0, errors.Newf("unknown object type %d", objType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileTypeToObjType(fileType base.FileType) (uint64, error) {
|
|
||||||
switch fileType {
|
|
||||||
case base.FileTypeTable:
|
|
||||||
return objTypeTable, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0, errors.Newf("unknown object type for file type %d", fileType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
for _, meta := range v.NewObjects {
|
|
||||||
objType, err := fileTypeToObjType(meta.FileType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(tagNewObject))
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(meta.FileNum))
|
|
||||||
buf = binary.AppendUvarint(buf, objType)
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(meta.CreatorID))
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(meta.CreatorFileNum))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fileNum := range v.DeletedObjects {
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(tagDeletedObject))
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(fileNum))
|
|
||||||
}
|
|
||||||
if v.CreatorID.IsSet() {
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(tagCreatorID))
|
|
||||||
buf = binary.AppendUvarint(buf, uint64(v.CreatorID))
|
|
||||||
}
|
|
||||||
_, err := w.Write(buf)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode decodes an edit from the specified reader.
|
|
||||||
func (v *versionEdit) Decode(r io.Reader) error {
|
|
||||||
br, ok := r.(io.ByteReader)
|
|
||||||
if !ok {
|
|
||||||
br = bufio.NewReader(r)
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
tag, err := binary.ReadUvarint(br)
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = nil
|
|
||||||
switch tag {
|
|
||||||
case tagNewObject:
|
|
||||||
var fileNum, creatorID, creatorFileNum uint64
|
|
||||||
var fileType base.FileType
|
|
||||||
fileNum, err = binary.ReadUvarint(br)
|
|
||||||
if err == nil {
|
|
||||||
var objType uint64
|
|
||||||
objType, err = binary.ReadUvarint(br)
|
|
||||||
if err == nil {
|
|
||||||
fileType, err = objTypeToFileType(objType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
creatorID, err = binary.ReadUvarint(br)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
creatorFileNum, err = binary.ReadUvarint(br)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
v.NewObjects = append(v.NewObjects, SharedObjectMetadata{
|
|
||||||
FileNum: base.FileNum(fileNum),
|
|
||||||
FileType: fileType,
|
|
||||||
CreatorID: CreatorID(creatorID),
|
|
||||||
CreatorFileNum: base.FileNum(creatorFileNum),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
case tagDeletedObject:
|
|
||||||
var fileNum uint64
|
|
||||||
fileNum, err = binary.ReadUvarint(br)
|
|
||||||
if err == nil {
|
|
||||||
v.DeletedObjects = append(v.DeletedObjects, base.FileNum(fileNum))
|
|
||||||
}
|
|
||||||
|
|
||||||
case tagCreatorID:
|
|
||||||
var id uint64
|
|
||||||
id, err = binary.ReadUvarint(br)
|
|
||||||
if err == nil {
|
|
||||||
v.CreatorID = CreatorID(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Ignore unknown tags.
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return errCorruptCatalog
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var errCorruptCatalog = base.CorruptionErrorf("pebble: corrupt shared object catalog")
|
|
||||||
224
vendor/github.com/cockroachdb/pebble/open.go
generated
vendored
224
vendor/github.com/cockroachdb/pebble/open.go
generated
vendored
@@ -6,6 +6,7 @@ package pebble
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -20,11 +21,14 @@ import (
|
|||||||
"github.com/cockroachdb/pebble/internal/arenaskl"
|
"github.com/cockroachdb/pebble/internal/arenaskl"
|
||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
"github.com/cockroachdb/pebble/internal/cache"
|
"github.com/cockroachdb/pebble/internal/cache"
|
||||||
|
"github.com/cockroachdb/pebble/internal/constants"
|
||||||
"github.com/cockroachdb/pebble/internal/invariants"
|
"github.com/cockroachdb/pebble/internal/invariants"
|
||||||
"github.com/cockroachdb/pebble/internal/manifest"
|
"github.com/cockroachdb/pebble/internal/manifest"
|
||||||
"github.com/cockroachdb/pebble/internal/manual"
|
"github.com/cockroachdb/pebble/internal/manual"
|
||||||
"github.com/cockroachdb/pebble/objstorage"
|
"github.com/cockroachdb/pebble/objstorage"
|
||||||
|
"github.com/cockroachdb/pebble/objstorage/objstorageprovider"
|
||||||
"github.com/cockroachdb/pebble/record"
|
"github.com/cockroachdb/pebble/record"
|
||||||
|
"github.com/cockroachdb/pebble/sstable"
|
||||||
"github.com/cockroachdb/pebble/vfs"
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
@@ -34,11 +38,23 @@ const (
|
|||||||
|
|
||||||
// The max batch size is limited by the uint32 offsets stored in
|
// The max batch size is limited by the uint32 offsets stored in
|
||||||
// internal/batchskl.node, DeferredBatchOp, and flushableBatchEntry.
|
// internal/batchskl.node, DeferredBatchOp, and flushableBatchEntry.
|
||||||
maxBatchSize = 4 << 30 // 4 GB
|
//
|
||||||
|
// We limit the size to MaxUint32 (just short of 4GB) so that the exclusive
|
||||||
|
// end of an allocation fits in uint32.
|
||||||
|
//
|
||||||
|
// On 32-bit systems, slices are naturally limited to MaxInt (just short of
|
||||||
|
// 2GB).
|
||||||
|
maxBatchSize = constants.MaxUint32OrInt
|
||||||
|
|
||||||
// The max memtable size is limited by the uint32 offsets stored in
|
// The max memtable size is limited by the uint32 offsets stored in
|
||||||
// internal/arenaskl.node, DeferredBatchOp, and flushableBatchEntry.
|
// internal/arenaskl.node, DeferredBatchOp, and flushableBatchEntry.
|
||||||
maxMemTableSize = 4 << 30 // 4 GB
|
//
|
||||||
|
// We limit the size to MaxUint32 (just short of 4GB) so that the exclusive
|
||||||
|
// end of an allocation fits in uint32.
|
||||||
|
//
|
||||||
|
// On 32-bit systems, slices are naturally limited to MaxInt (just short of
|
||||||
|
// 2GB).
|
||||||
|
maxMemTableSize = constants.MaxUint32OrInt
|
||||||
)
|
)
|
||||||
|
|
||||||
// TableCacheSize can be used to determine the table
|
// TableCacheSize can be used to determine the table
|
||||||
@@ -157,7 +173,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
merge: opts.Merger.Merge,
|
merge: opts.Merger.Merge,
|
||||||
split: opts.Comparer.Split,
|
split: opts.Comparer.Split,
|
||||||
abbreviatedKey: opts.Comparer.AbbreviatedKey,
|
abbreviatedKey: opts.Comparer.AbbreviatedKey,
|
||||||
largeBatchThreshold: (opts.MemTableSize - int(memTableEmptySize)) / 2,
|
largeBatchThreshold: (opts.MemTableSize - uint64(memTableEmptySize)) / 2,
|
||||||
fileLock: fileLock,
|
fileLock: fileLock,
|
||||||
dataDir: dataDir,
|
dataDir: dataDir,
|
||||||
walDir: walDir,
|
walDir: walDir,
|
||||||
@@ -166,8 +182,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
closedCh: make(chan struct{}),
|
closedCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
d.mu.versions = &versionSet{}
|
d.mu.versions = &versionSet{}
|
||||||
d.atomic.diskAvailBytes = math.MaxUint64
|
d.diskAvailBytes.Store(math.MaxUint64)
|
||||||
d.mu.versions.diskAvailBytes = d.getDiskAvailableBytesCached
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// If an error or panic occurs during open, attempt to release the manually
|
// If an error or panic occurs during open, attempt to release the manually
|
||||||
@@ -192,6 +207,9 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
t.arenaBuf = nil
|
t.arenaBuf = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if d.cleanupManager != nil {
|
||||||
|
d.cleanupManager.Close()
|
||||||
|
}
|
||||||
if d.objProvider != nil {
|
if d.objProvider != nil {
|
||||||
d.objProvider.Close()
|
d.objProvider.Close()
|
||||||
}
|
}
|
||||||
@@ -202,34 +220,30 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
d.commit = newCommitPipeline(commitEnv{
|
d.commit = newCommitPipeline(commitEnv{
|
||||||
logSeqNum: &d.mu.versions.atomic.logSeqNum,
|
logSeqNum: &d.mu.versions.logSeqNum,
|
||||||
visibleSeqNum: &d.mu.versions.atomic.visibleSeqNum,
|
visibleSeqNum: &d.mu.versions.visibleSeqNum,
|
||||||
apply: d.commitApply,
|
apply: d.commitApply,
|
||||||
write: d.commitWrite,
|
write: d.commitWrite,
|
||||||
})
|
})
|
||||||
if d.opts.Experimental.MinDeletionRate > 0 {
|
|
||||||
d.deletionPacer = newDeletionPacer(int64(d.opts.Experimental.MinDeletionRate), d.getDeletionPacerInfo)
|
|
||||||
} else {
|
|
||||||
d.deletionPacer = nilPacer
|
|
||||||
}
|
|
||||||
d.mu.nextJobID = 1
|
d.mu.nextJobID = 1
|
||||||
d.mu.mem.nextSize = opts.MemTableSize
|
d.mu.mem.nextSize = opts.MemTableSize
|
||||||
if d.mu.mem.nextSize > initialMemTableSize {
|
if d.mu.mem.nextSize > initialMemTableSize {
|
||||||
d.mu.mem.nextSize = initialMemTableSize
|
d.mu.mem.nextSize = initialMemTableSize
|
||||||
}
|
}
|
||||||
d.mu.mem.cond.L = &d.mu.Mutex
|
|
||||||
d.mu.cleaner.cond.L = &d.mu.Mutex
|
|
||||||
d.mu.compact.cond.L = &d.mu.Mutex
|
d.mu.compact.cond.L = &d.mu.Mutex
|
||||||
d.mu.compact.inProgress = make(map[*compaction]struct{})
|
d.mu.compact.inProgress = make(map[*compaction]struct{})
|
||||||
d.mu.compact.noOngoingFlushStartTime = time.Now()
|
d.mu.compact.noOngoingFlushStartTime = time.Now()
|
||||||
d.mu.snapshots.init()
|
d.mu.snapshots.init()
|
||||||
// logSeqNum is the next sequence number that will be assigned. Start
|
// logSeqNum is the next sequence number that will be assigned.
|
||||||
// assigning sequence numbers from 1 to match rocksdb.
|
// Start assigning sequence numbers from base.SeqNumStart to leave
|
||||||
d.mu.versions.atomic.logSeqNum = 1
|
// room for reserved sequence numbers (see comments around
|
||||||
|
// SeqNumStart).
|
||||||
|
d.mu.versions.logSeqNum.Store(base.SeqNumStart)
|
||||||
d.mu.formatVers.vers.Store(uint64(formatVersion))
|
d.mu.formatVers.vers.Store(uint64(formatVersion))
|
||||||
d.mu.formatVers.marker = formatVersionMarker
|
d.mu.formatVers.marker = formatVersionMarker
|
||||||
|
|
||||||
d.timeNow = time.Now
|
d.timeNow = time.Now
|
||||||
|
d.openedAt = d.timeNow()
|
||||||
|
|
||||||
d.mu.Lock()
|
d.mu.Lock()
|
||||||
defer d.mu.Unlock()
|
defer d.mu.Unlock()
|
||||||
@@ -246,7 +260,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the DB.
|
// Create the DB.
|
||||||
if err := d.mu.versions.create(jobID, dirname, opts, manifestMarker, setCurrent, &d.mu.Mutex); err != nil {
|
if err := d.mu.versions.create(jobID, dirname, opts, manifestMarker, setCurrent, d.FormatMajorVersion, &d.mu.Mutex); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -254,11 +268,11 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
return nil, errors.Wrapf(ErrDBAlreadyExists, "dirname=%q", dirname)
|
return nil, errors.Wrapf(ErrDBAlreadyExists, "dirname=%q", dirname)
|
||||||
}
|
}
|
||||||
// Load the version set.
|
// Load the version set.
|
||||||
if err := d.mu.versions.load(dirname, opts, manifestFileNum, manifestMarker, setCurrent, &d.mu.Mutex); err != nil {
|
if err := d.mu.versions.load(dirname, opts, manifestFileNum.FileNum(), manifestMarker, setCurrent, d.FormatMajorVersion, &d.mu.Mutex); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if opts.ErrorIfNotPristine {
|
if opts.ErrorIfNotPristine {
|
||||||
liveFileNums := make(map[FileNum]struct{})
|
liveFileNums := make(map[base.DiskFileNum]struct{})
|
||||||
d.mu.versions.addLiveFileNums(liveFileNums)
|
d.mu.versions.addLiveFileNums(liveFileNums)
|
||||||
if len(liveFileNums) != 0 {
|
if len(liveFileNums) != 0 {
|
||||||
return nil, errors.Wrapf(ErrDBNotPristine, "dirname=%q", dirname)
|
return nil, errors.Wrapf(ErrDBNotPristine, "dirname=%q", dirname)
|
||||||
@@ -271,7 +285,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
// sequence number of the first batch that will be inserted.
|
// sequence number of the first batch that will be inserted.
|
||||||
if !d.opts.ReadOnly {
|
if !d.opts.ReadOnly {
|
||||||
var entry *flushableEntry
|
var entry *flushableEntry
|
||||||
d.mu.mem.mutable, entry = d.newMemTable(0 /* logNum */, d.mu.versions.atomic.logSeqNum)
|
d.mu.mem.mutable, entry = d.newMemTable(0 /* logNum */, d.mu.versions.logSeqNum.Load())
|
||||||
d.mu.mem.queue = append(d.mu.mem.queue, entry)
|
d.mu.mem.queue = append(d.mu.mem.queue, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +301,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
}
|
}
|
||||||
ls = append(ls, ls2...)
|
ls = append(ls, ls2...)
|
||||||
}
|
}
|
||||||
providerSettings := objstorage.Settings{
|
providerSettings := objstorageprovider.Settings{
|
||||||
Logger: opts.Logger,
|
Logger: opts.Logger,
|
||||||
FS: opts.FS,
|
FS: opts.FS,
|
||||||
FSDirName: dirname,
|
FSDirName: dirname,
|
||||||
@@ -296,13 +310,18 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
NoSyncOnClose: opts.NoSyncOnClose,
|
NoSyncOnClose: opts.NoSyncOnClose,
|
||||||
BytesPerSync: opts.BytesPerSync,
|
BytesPerSync: opts.BytesPerSync,
|
||||||
}
|
}
|
||||||
providerSettings.Shared.Storage = opts.Experimental.SharedStorage
|
providerSettings.Remote.StorageFactory = opts.Experimental.RemoteStorage
|
||||||
|
providerSettings.Remote.CreateOnShared = opts.Experimental.CreateOnShared
|
||||||
|
providerSettings.Remote.CreateOnSharedLocator = opts.Experimental.CreateOnSharedLocator
|
||||||
|
providerSettings.Remote.CacheSizeBytes = opts.Experimental.SecondaryCacheSizeBytes
|
||||||
|
|
||||||
d.objProvider, err = objstorage.Open(providerSettings)
|
d.objProvider, err = objstorageprovider.Open(providerSettings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.cleanupManager = openCleanupManager(opts, d.objProvider, d.onObsoleteTableDelete, d.getDeletionPacerInfo)
|
||||||
|
|
||||||
if manifestExists {
|
if manifestExists {
|
||||||
curVersion := d.mu.versions.currentVersion()
|
curVersion := d.mu.versions.currentVersion()
|
||||||
if err := checkConsistency(curVersion, dirname, d.objProvider); err != nil {
|
if err := checkConsistency(curVersion, dirname, d.objProvider); err != nil {
|
||||||
@@ -331,21 +350,21 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
|
|
||||||
// Don't reuse any obsolete file numbers to avoid modifying an
|
// Don't reuse any obsolete file numbers to avoid modifying an
|
||||||
// ingested sstable's original external file.
|
// ingested sstable's original external file.
|
||||||
if d.mu.versions.nextFileNum <= fn {
|
if d.mu.versions.nextFileNum <= fn.FileNum() {
|
||||||
d.mu.versions.nextFileNum = fn + 1
|
d.mu.versions.nextFileNum = fn.FileNum() + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ft {
|
switch ft {
|
||||||
case fileTypeLog:
|
case fileTypeLog:
|
||||||
if fn >= d.mu.versions.minUnflushedLogNum {
|
if fn.FileNum() >= d.mu.versions.minUnflushedLogNum {
|
||||||
logFiles = append(logFiles, fileNumAndName{fn, filename})
|
logFiles = append(logFiles, fileNumAndName{fn.FileNum(), filename})
|
||||||
}
|
}
|
||||||
if d.logRecycler.minRecycleLogNum <= fn {
|
if d.logRecycler.minRecycleLogNum <= fn.FileNum() {
|
||||||
d.logRecycler.minRecycleLogNum = fn + 1
|
d.logRecycler.minRecycleLogNum = fn.FileNum() + 1
|
||||||
}
|
}
|
||||||
case fileTypeOptions:
|
case fileTypeOptions:
|
||||||
if previousOptionsFileNum < fn {
|
if previousOptionsFileNum < fn.FileNum() {
|
||||||
previousOptionsFileNum = fn
|
previousOptionsFileNum = fn.FileNum()
|
||||||
previousOptionsFilename = filename
|
previousOptionsFilename = filename
|
||||||
}
|
}
|
||||||
case fileTypeTemp, fileTypeOldTemp:
|
case fileTypeTemp, fileTypeOldTemp:
|
||||||
@@ -362,6 +381,15 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ratchet d.mu.versions.nextFileNum ahead of all known objects in the
|
||||||
|
// objProvider. This avoids FileNum collisions with obsolete sstables.
|
||||||
|
objects := d.objProvider.List()
|
||||||
|
for _, obj := range objects {
|
||||||
|
if d.mu.versions.nextFileNum <= obj.DiskFileNum.FileNum() {
|
||||||
|
d.mu.versions.nextFileNum = obj.DiskFileNum.FileNum() + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate the most-recent OPTIONS file, if there is one.
|
// Validate the most-recent OPTIONS file, if there is one.
|
||||||
var strictWALTail bool
|
var strictWALTail bool
|
||||||
if previousOptionsFilename != "" {
|
if previousOptionsFilename != "" {
|
||||||
@@ -387,11 +415,11 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
}
|
}
|
||||||
toFlush = append(toFlush, flush...)
|
toFlush = append(toFlush, flush...)
|
||||||
d.mu.versions.markFileNumUsed(lf.num)
|
d.mu.versions.markFileNumUsed(lf.num)
|
||||||
if d.mu.versions.atomic.logSeqNum < maxSeqNum {
|
if d.mu.versions.logSeqNum.Load() < maxSeqNum {
|
||||||
d.mu.versions.atomic.logSeqNum = maxSeqNum
|
d.mu.versions.logSeqNum.Store(maxSeqNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.mu.versions.atomic.visibleSeqNum = d.mu.versions.atomic.logSeqNum
|
d.mu.versions.visibleSeqNum.Store(d.mu.versions.logSeqNum.Load())
|
||||||
|
|
||||||
if !d.opts.ReadOnly {
|
if !d.opts.ReadOnly {
|
||||||
// Create an empty .log file.
|
// Create an empty .log file.
|
||||||
@@ -417,8 +445,8 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
entry.readerUnrefLocked(true)
|
entry.readerUnrefLocked(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
newLogName := base.MakeFilepath(opts.FS, d.walDirname, fileTypeLog, newLogNum)
|
newLogName := base.MakeFilepath(opts.FS, d.walDirname, fileTypeLog, newLogNum.DiskFileNum())
|
||||||
d.mu.log.queue = append(d.mu.log.queue, fileInfo{fileNum: newLogNum, fileSize: 0})
|
d.mu.log.queue = append(d.mu.log.queue, fileInfo{fileNum: newLogNum.DiskFileNum(), fileSize: 0})
|
||||||
logFile, err := opts.FS.Create(newLogName)
|
logFile, err := opts.FS.Create(newLogName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -469,7 +497,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
|
|
||||||
if !d.opts.ReadOnly {
|
if !d.opts.ReadOnly {
|
||||||
// Write the current options to disk.
|
// Write the current options to disk.
|
||||||
d.optionsFileNum = d.mu.versions.getNextFileNum()
|
d.optionsFileNum = d.mu.versions.getNextFileNum().DiskFileNum()
|
||||||
tmpPath := base.MakeFilepath(opts.FS, dirname, fileTypeTemp, d.optionsFileNum)
|
tmpPath := base.MakeFilepath(opts.FS, dirname, fileTypeTemp, d.optionsFileNum)
|
||||||
optionsPath := base.MakeFilepath(opts.FS, dirname, fileTypeOptions, d.optionsFileNum)
|
optionsPath := base.MakeFilepath(opts.FS, dirname, fileTypeOptions, d.optionsFileNum)
|
||||||
|
|
||||||
@@ -504,7 +532,7 @@ func Open(dirname string, opts *Options) (db *DB, _ error) {
|
|||||||
|
|
||||||
if !d.opts.ReadOnly {
|
if !d.opts.ReadOnly {
|
||||||
d.scanObsoleteFiles(ls)
|
d.scanObsoleteFiles(ls)
|
||||||
d.deleteObsoleteFiles(jobID, true /* waitForOngoing */)
|
d.deleteObsoleteFiles(jobID)
|
||||||
} else {
|
} else {
|
||||||
// All the log files are obsolete.
|
// All the log files are obsolete.
|
||||||
d.mu.versions.metrics.WAL.Files = int64(len(logFiles))
|
d.mu.versions.metrics.WAL.Files = int64(len(logFiles))
|
||||||
@@ -613,9 +641,9 @@ func GetVersion(dir string, fs vfs.FS) (string, error) {
|
|||||||
// processed, reset version. This is because rocksdb often
|
// processed, reset version. This is because rocksdb often
|
||||||
// writes multiple options files without deleting previous ones.
|
// writes multiple options files without deleting previous ones.
|
||||||
// Otherwise, skip parsing this options file.
|
// Otherwise, skip parsing this options file.
|
||||||
if fn > lastOptionsSeen {
|
if fn.FileNum() > lastOptionsSeen {
|
||||||
version = ""
|
version = ""
|
||||||
lastOptionsSeen = fn
|
lastOptionsSeen = fn.FileNum()
|
||||||
} else {
|
} else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -677,8 +705,15 @@ func (d *DB) replayWAL(
|
|||||||
rr = record.NewReader(file, logNum)
|
rr = record.NewReader(file, logNum)
|
||||||
offset int64 // byte offset in rr
|
offset int64 // byte offset in rr
|
||||||
lastFlushOffset int64
|
lastFlushOffset int64
|
||||||
|
keysReplayed int64 // number of keys replayed
|
||||||
|
batchesReplayed int64 // number of batches replayed
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO(jackson): This function is interspersed with panics, in addition to
|
||||||
|
// corruption error propagation. Audit them to ensure we're truly only
|
||||||
|
// panicking where the error points to Pebble bug and not user or
|
||||||
|
// hardware-induced corruption.
|
||||||
|
|
||||||
if d.opts.ReadOnly {
|
if d.opts.ReadOnly {
|
||||||
// In read-only mode, we replay directly into the mutable memtable which will
|
// In read-only mode, we replay directly into the mutable memtable which will
|
||||||
// never be flushed.
|
// never be flushed.
|
||||||
@@ -725,7 +760,7 @@ func (d *DB) replayWAL(
|
|||||||
// TODO(bananabrick): See if we can use the actual base level here,
|
// TODO(bananabrick): See if we can use the actual base level here,
|
||||||
// instead of using 1.
|
// instead of using 1.
|
||||||
c := newFlush(d.opts, d.mu.versions.currentVersion(),
|
c := newFlush(d.opts, d.mu.versions.currentVersion(),
|
||||||
1 /* base level */, toFlush)
|
1 /* base level */, toFlush, d.timeNow())
|
||||||
newVE, _, _, err := d.runCompaction(jobID, c)
|
newVE, _, _, err := d.runCompaction(jobID, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "running compaction during WAL replay")
|
return errors.Wrapf(err, "running compaction during WAL replay")
|
||||||
@@ -733,6 +768,11 @@ func (d *DB) replayWAL(
|
|||||||
ve.NewFiles = append(ve.NewFiles, newVE.NewFiles...)
|
ve.NewFiles = append(ve.NewFiles, newVE.NewFiles...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
err = errors.WithDetailf(err, "replaying log %s, offset %d", logNum, offset)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
offset = rr.Offset()
|
offset = rr.Offset()
|
||||||
@@ -765,26 +805,33 @@ func (d *DB) replayWAL(
|
|||||||
|
|
||||||
// Specify Batch.db so that Batch.SetRepr will compute Batch.memTableSize
|
// Specify Batch.db so that Batch.SetRepr will compute Batch.memTableSize
|
||||||
// which is used below.
|
// which is used below.
|
||||||
b = Batch{db: d}
|
b = Batch{}
|
||||||
|
b.db = d
|
||||||
b.SetRepr(buf.Bytes())
|
b.SetRepr(buf.Bytes())
|
||||||
seqNum := b.SeqNum()
|
seqNum := b.SeqNum()
|
||||||
maxSeqNum = seqNum + uint64(b.Count())
|
maxSeqNum = seqNum + uint64(b.Count())
|
||||||
|
keysReplayed += int64(b.Count())
|
||||||
|
batchesReplayed++
|
||||||
{
|
{
|
||||||
br := b.Reader()
|
br := b.Reader()
|
||||||
if kind, encodedFileNum, _, _ := br.Next(); kind == InternalKeyKindIngestSST {
|
if kind, encodedFileNum, _, ok, err := br.Next(); err != nil {
|
||||||
fileNums := make([]FileNum, 0, b.Count())
|
return nil, 0, err
|
||||||
|
} else if ok && kind == InternalKeyKindIngestSST {
|
||||||
|
fileNums := make([]base.DiskFileNum, 0, b.Count())
|
||||||
addFileNum := func(encodedFileNum []byte) {
|
addFileNum := func(encodedFileNum []byte) {
|
||||||
fileNum, n := binary.Uvarint(encodedFileNum)
|
fileNum, n := binary.Uvarint(encodedFileNum)
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
panic("pebble: ingest sstable file num is invalid.")
|
panic("pebble: ingest sstable file num is invalid.")
|
||||||
}
|
}
|
||||||
fileNums = append(fileNums, base.FileNum(fileNum))
|
fileNums = append(fileNums, base.FileNum(fileNum).DiskFileNum())
|
||||||
}
|
}
|
||||||
addFileNum(encodedFileNum)
|
addFileNum(encodedFileNum)
|
||||||
|
|
||||||
for i := 1; i < int(b.Count()); i++ {
|
for i := 1; i < int(b.Count()); i++ {
|
||||||
kind, encodedFileNum, _, ok := br.Next()
|
kind, encodedFileNum, _, ok, err := br.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
if kind != InternalKeyKindIngestSST {
|
if kind != InternalKeyKindIngestSST {
|
||||||
panic("pebble: invalid batch key kind.")
|
panic("pebble: invalid batch key kind.")
|
||||||
}
|
}
|
||||||
@@ -794,21 +841,41 @@ func (d *DB) replayWAL(
|
|||||||
addFileNum(encodedFileNum)
|
addFileNum(encodedFileNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, _, _, ok := br.Next(); ok {
|
if _, _, _, ok, err := br.Next(); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
} else if ok {
|
||||||
panic("pebble: invalid number of entries in batch.")
|
panic("pebble: invalid number of entries in batch.")
|
||||||
}
|
}
|
||||||
|
|
||||||
paths := make([]string, len(fileNums))
|
meta := make([]*fileMetadata, len(fileNums))
|
||||||
for i, n := range fileNums {
|
for i, n := range fileNums {
|
||||||
paths[i] = base.MakeFilepath(d.opts.FS, d.dirname, fileTypeTable, n)
|
var readable objstorage.Readable
|
||||||
}
|
objMeta, err := d.objProvider.Lookup(fileTypeTable, n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.Wrap(err, "pebble: error when looking up ingested SSTs")
|
||||||
|
}
|
||||||
|
if objMeta.IsRemote() {
|
||||||
|
readable, err = d.objProvider.OpenForReading(context.TODO(), fileTypeTable, n, objstorage.OpenOptions{MustExist: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.Wrap(err, "pebble: error when opening flushable ingest files")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path := base.MakeFilepath(d.opts.FS, d.dirname, fileTypeTable, n)
|
||||||
|
f, err := d.opts.FS.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
var meta []*manifest.FileMetadata
|
readable, err = sstable.NewSimpleReadable(f)
|
||||||
meta, _, err = ingestLoad(
|
if err != nil {
|
||||||
d.opts, d.FormatMajorVersion(), paths, d.cacheID, fileNums,
|
return nil, 0, err
|
||||||
)
|
}
|
||||||
if err != nil {
|
}
|
||||||
return nil, 0, err
|
// NB: ingestLoad1 will close readable.
|
||||||
|
meta[i], err = ingestLoad1(d.opts, d.FormatMajorVersion(), readable, d.cacheID, n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.Wrap(err, "pebble: error when loading flushable ingest files")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if uint32(len(meta)) != b.Count() {
|
if uint32(len(meta)) != b.Count() {
|
||||||
@@ -854,9 +921,10 @@ func (d *DB) replayWAL(
|
|||||||
d.opts, d.mu.versions.currentVersion(),
|
d.opts, d.mu.versions.currentVersion(),
|
||||||
1, /* base level */
|
1, /* base level */
|
||||||
[]*flushableEntry{entry},
|
[]*flushableEntry{entry},
|
||||||
|
d.timeNow(),
|
||||||
)
|
)
|
||||||
for _, file := range c.flushing[0].flushable.(*ingestedFlushable).files {
|
for _, file := range c.flushing[0].flushable.(*ingestedFlushable).files {
|
||||||
ve.NewFiles = append(ve.NewFiles, newFileEntry{Level: 0, Meta: file})
|
ve.NewFiles = append(ve.NewFiles, newFileEntry{Level: 0, Meta: file.FileMetadata})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return toFlush, maxSeqNum, nil
|
return toFlush, maxSeqNum, nil
|
||||||
@@ -868,11 +936,14 @@ func (d *DB) replayWAL(
|
|||||||
// Make a copy of the data slice since it is currently owned by buf and will
|
// Make a copy of the data slice since it is currently owned by buf and will
|
||||||
// be reused in the next iteration.
|
// be reused in the next iteration.
|
||||||
b.data = append([]byte(nil), b.data...)
|
b.data = append([]byte(nil), b.data...)
|
||||||
b.flushable = newFlushableBatch(&b, d.opts.Comparer)
|
b.flushable, err = newFlushableBatch(&b, d.opts.Comparer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
entry := d.newFlushableEntry(b.flushable, logNum, b.SeqNum())
|
entry := d.newFlushableEntry(b.flushable, logNum, b.SeqNum())
|
||||||
// Disable memory accounting by adding a reader ref that will never be
|
// Disable memory accounting by adding a reader ref that will never be
|
||||||
// removed.
|
// removed.
|
||||||
entry.readerRefs++
|
entry.readerRefs.Add(1)
|
||||||
if d.opts.ReadOnly {
|
if d.opts.ReadOnly {
|
||||||
d.mu.mem.queue = append(d.mu.mem.queue, entry)
|
d.mu.mem.queue = append(d.mu.mem.queue, entry)
|
||||||
// We added the flushable batch to the flushable to the queue.
|
// We added the flushable batch to the flushable to the queue.
|
||||||
@@ -910,7 +981,10 @@ func (d *DB) replayWAL(
|
|||||||
}
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.opts.Logger.Infof("[JOB %d] WAL file %s with log number %s stopped reading at offset: %d; replayed %d keys in %d batches", jobID, filename, logNum.String(), offset, keysReplayed, batchesReplayed)
|
||||||
flushMem()
|
flushMem()
|
||||||
|
|
||||||
// mem is nil here.
|
// mem is nil here.
|
||||||
if !d.opts.ReadOnly {
|
if !d.opts.ReadOnly {
|
||||||
err = updateVE()
|
err = updateVE()
|
||||||
@@ -990,7 +1064,7 @@ func Peek(dirname string, fs vfs.FS) (*DBDesc, error) {
|
|||||||
// LockDirectory may be used to expand the critical section protected by the
|
// LockDirectory may be used to expand the critical section protected by the
|
||||||
// database lock to include setup before the call to Open.
|
// database lock to include setup before the call to Open.
|
||||||
func LockDirectory(dirname string, fs vfs.FS) (*Lock, error) {
|
func LockDirectory(dirname string, fs vfs.FS) (*Lock, error) {
|
||||||
fileLock, err := fs.Lock(base.MakeFilepath(fs, dirname, fileTypeLock, 0))
|
fileLock, err := fs.Lock(base.MakeFilepath(fs, dirname, fileTypeLock, base.FileNum(0).DiskFileNum()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1068,28 +1142,42 @@ func IsCorruptionError(err error) bool {
|
|||||||
return errors.Is(err, base.ErrCorruption)
|
return errors.Is(err, base.ErrCorruption)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkConsistency(v *manifest.Version, dirname string, objProvider *objstorage.Provider) error {
|
func checkConsistency(v *manifest.Version, dirname string, objProvider objstorage.Provider) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
|
|
||||||
|
dedup := make(map[base.DiskFileNum]struct{})
|
||||||
for level, files := range v.Levels {
|
for level, files := range v.Levels {
|
||||||
iter := files.Iter()
|
iter := files.Iter()
|
||||||
for f := iter.First(); f != nil; f = iter.Next() {
|
for f := iter.First(); f != nil; f = iter.Next() {
|
||||||
meta, err := objProvider.Lookup(base.FileTypeTable, f.FileNum)
|
backingState := f.FileBacking
|
||||||
|
if _, ok := dedup[backingState.DiskFileNum]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dedup[backingState.DiskFileNum] = struct{}{}
|
||||||
|
fileNum := backingState.DiskFileNum
|
||||||
|
fileSize := backingState.Size
|
||||||
|
// We allow foreign objects to have a mismatch between sizes. This is
|
||||||
|
// because we might skew the backing size stored by our objprovider
|
||||||
|
// to prevent us from over-prioritizing this file for compaction.
|
||||||
|
meta, err := objProvider.Lookup(base.FileTypeTable, fileNum)
|
||||||
var size int64
|
var size int64
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if objProvider.IsSharedForeign(meta) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
size, err = objProvider.Size(meta)
|
size, err = objProvider.Size(meta)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
buf.WriteString("L%d: %s: %v\n")
|
buf.WriteString("L%d: %s: %v\n")
|
||||||
args = append(args, errors.Safe(level), errors.Safe(f.FileNum), err)
|
args = append(args, errors.Safe(level), errors.Safe(fileNum), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if size != int64(f.Size) {
|
if size != int64(fileSize) {
|
||||||
buf.WriteString("L%d: %s: object size mismatch (%s): %d (disk) != %d (MANIFEST)\n")
|
buf.WriteString("L%d: %s: object size mismatch (%s): %d (disk) != %d (MANIFEST)\n")
|
||||||
args = append(args, errors.Safe(level), errors.Safe(f.FileNum), objProvider.Path(meta),
|
args = append(args, errors.Safe(level), errors.Safe(fileNum), objProvider.Path(meta),
|
||||||
errors.Safe(size), errors.Safe(f.Size))
|
errors.Safe(size), errors.Safe(fileSize))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
232
vendor/github.com/cockroachdb/pebble/options.go
generated
vendored
232
vendor/github.com/cockroachdb/pebble/options.go
generated
vendored
@@ -17,8 +17,10 @@ import (
|
|||||||
"github.com/cockroachdb/pebble/internal/base"
|
"github.com/cockroachdb/pebble/internal/base"
|
||||||
"github.com/cockroachdb/pebble/internal/cache"
|
"github.com/cockroachdb/pebble/internal/cache"
|
||||||
"github.com/cockroachdb/pebble/internal/humanize"
|
"github.com/cockroachdb/pebble/internal/humanize"
|
||||||
|
"github.com/cockroachdb/pebble/internal/keyspan"
|
||||||
"github.com/cockroachdb/pebble/internal/manifest"
|
"github.com/cockroachdb/pebble/internal/manifest"
|
||||||
"github.com/cockroachdb/pebble/objstorage/shared"
|
"github.com/cockroachdb/pebble/objstorage/remote"
|
||||||
|
"github.com/cockroachdb/pebble/rangekey"
|
||||||
"github.com/cockroachdb/pebble/sstable"
|
"github.com/cockroachdb/pebble/sstable"
|
||||||
"github.com/cockroachdb/pebble/vfs"
|
"github.com/cockroachdb/pebble/vfs"
|
||||||
)
|
)
|
||||||
@@ -117,11 +119,24 @@ type IterOptions struct {
|
|||||||
// false to skip scanning. This function must be thread-safe since the same
|
// false to skip scanning. This function must be thread-safe since the same
|
||||||
// function can be used by multiple iterators, if the iterator is cloned.
|
// function can be used by multiple iterators, if the iterator is cloned.
|
||||||
TableFilter func(userProps map[string]string) bool
|
TableFilter func(userProps map[string]string) bool
|
||||||
|
// SkipPoint may be used to skip over point keys that don't match an
|
||||||
|
// arbitrary predicate during iteration. If set, the Iterator invokes
|
||||||
|
// SkipPoint for keys encountered. If SkipPoint returns true, the iterator
|
||||||
|
// will skip the key without yielding it to the iterator operation in
|
||||||
|
// progress.
|
||||||
|
//
|
||||||
|
// SkipPoint must be a pure function and always return the same result when
|
||||||
|
// provided the same arguments. The iterator may call SkipPoint multiple
|
||||||
|
// times for the same user key.
|
||||||
|
SkipPoint func(userKey []byte) bool
|
||||||
// PointKeyFilters can be used to avoid scanning tables and blocks in tables
|
// PointKeyFilters can be used to avoid scanning tables and blocks in tables
|
||||||
// when iterating over point keys. It is requires that this slice is sorted in
|
// when iterating over point keys. This slice represents an intersection
|
||||||
// increasing order of the BlockPropertyFilter.ShortID. This slice represents
|
// across all filters, i.e., all filters must indicate that the block is
|
||||||
// an intersection across all filters, i.e., all filters must indicate that the
|
// relevant.
|
||||||
// block is relevant.
|
//
|
||||||
|
// Performance note: When len(PointKeyFilters) > 0, the caller should ensure
|
||||||
|
// that cap(PointKeyFilters) is at least len(PointKeyFilters)+1. This helps
|
||||||
|
// avoid allocations in Pebble internal code that mutates the slice.
|
||||||
PointKeyFilters []BlockPropertyFilter
|
PointKeyFilters []BlockPropertyFilter
|
||||||
// RangeKeyFilters can be usefd to avoid scanning tables and blocks in tables
|
// RangeKeyFilters can be usefd to avoid scanning tables and blocks in tables
|
||||||
// when iterating over range keys. The same requirements that apply to
|
// when iterating over range keys. The same requirements that apply to
|
||||||
@@ -180,6 +195,10 @@ type IterOptions struct {
|
|||||||
level manifest.Level
|
level manifest.Level
|
||||||
// disableLazyCombinedIteration is an internal testing option.
|
// disableLazyCombinedIteration is an internal testing option.
|
||||||
disableLazyCombinedIteration bool
|
disableLazyCombinedIteration bool
|
||||||
|
// snapshotForHideObsoletePoints is specified for/by levelIter when opening
|
||||||
|
// files and is used to decide whether to hide obsolete points. A value of 0
|
||||||
|
// implies obsolete points should not be hidden.
|
||||||
|
snapshotForHideObsoletePoints uint64
|
||||||
|
|
||||||
// NB: If adding new Options, you must account for them in iterator
|
// NB: If adding new Options, you must account for them in iterator
|
||||||
// construction and Iterator.SetOptions.
|
// construction and Iterator.SetOptions.
|
||||||
@@ -222,6 +241,38 @@ func (o *IterOptions) getLogger() Logger {
|
|||||||
return o.logger
|
return o.logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SpanIterOptions creates a SpanIterOptions from this IterOptions.
|
||||||
|
func (o *IterOptions) SpanIterOptions() keyspan.SpanIterOptions {
|
||||||
|
if o == nil {
|
||||||
|
return keyspan.SpanIterOptions{}
|
||||||
|
}
|
||||||
|
return keyspan.SpanIterOptions{
|
||||||
|
RangeKeyFilters: o.RangeKeyFilters,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanInternalOptions is similar to IterOptions, meant for use with
|
||||||
|
// scanInternalIterator.
|
||||||
|
type scanInternalOptions struct {
|
||||||
|
IterOptions
|
||||||
|
|
||||||
|
visitPointKey func(key *InternalKey, value LazyValue, iterInfo IteratorLevel) error
|
||||||
|
visitRangeDel func(start, end []byte, seqNum uint64) error
|
||||||
|
visitRangeKey func(start, end []byte, keys []rangekey.Key) error
|
||||||
|
visitSharedFile func(sst *SharedSSTMeta) error
|
||||||
|
|
||||||
|
// skipSharedLevels skips levels that are shareable (level >=
|
||||||
|
// sharedLevelStart).
|
||||||
|
skipSharedLevels bool
|
||||||
|
|
||||||
|
// includeObsoleteKeys specifies whether keys shadowed by newer internal keys
|
||||||
|
// are exposed. If false, only one internal key per user key is exposed.
|
||||||
|
includeObsoleteKeys bool
|
||||||
|
|
||||||
|
// rateLimitFunc is used to limit the amount of bytes read per second.
|
||||||
|
rateLimitFunc func(key *InternalKey, value LazyValue) error
|
||||||
|
}
|
||||||
|
|
||||||
// RangeKeyMasking configures automatic hiding of point keys by range keys. A
|
// RangeKeyMasking configures automatic hiding of point keys by range keys. A
|
||||||
// non-nil Suffix enables range-key masking. When enabled, range keys with
|
// non-nil Suffix enables range-key masking. When enabled, range keys with
|
||||||
// suffixes ≥ Suffix behave as masks. All point keys that are contained within a
|
// suffixes ≥ Suffix behave as masks. All point keys that are contained within a
|
||||||
@@ -497,18 +548,12 @@ type Options struct {
|
|||||||
// concurrent compaction is added. This works "on top" of
|
// concurrent compaction is added. This works "on top" of
|
||||||
// L0CompactionConcurrency, so the higher of the count of compaction
|
// L0CompactionConcurrency, so the higher of the count of compaction
|
||||||
// concurrency slots as determined by the two options is chosen.
|
// concurrency slots as determined by the two options is chosen.
|
||||||
CompactionDebtConcurrency int
|
CompactionDebtConcurrency uint64
|
||||||
|
|
||||||
// MinDeletionRate is the minimum number of bytes per second that would
|
// IngestSplit, if it returns true, allows for ingest-time splitting of
|
||||||
// be deleted. Deletion pacing is used to slow down deletions when
|
// existing sstables into two virtual sstables to allow ingestion sstables to
|
||||||
// compactions finish up or readers close, and newly-obsolete files need
|
// slot into a lower level than they otherwise would have.
|
||||||
// cleaning up. Deleting lots of files at once can cause disk latency to
|
IngestSplit func() bool
|
||||||
// go up on some SSDs, which this functionality guards against. This is a
|
|
||||||
// minimum as the maximum is theoretically unlimited; pacing is disabled
|
|
||||||
// when there are too many obsolete files relative to live bytes, or
|
|
||||||
// there isn't enough disk space available. Setting this to 0 disables
|
|
||||||
// deletion pacing, which is also the default.
|
|
||||||
MinDeletionRate int
|
|
||||||
|
|
||||||
// ReadCompactionRate controls the frequency of read triggered
|
// ReadCompactionRate controls the frequency of read triggered
|
||||||
// compactions by adjusting `AllowedSeeks` in manifest.FileMetadata:
|
// compactions by adjusting `AllowedSeeks` in manifest.FileMetadata:
|
||||||
@@ -573,10 +618,10 @@ type Options struct {
|
|||||||
// desired size of each level of the LSM. Defaults to 10.
|
// desired size of each level of the LSM. Defaults to 10.
|
||||||
LevelMultiplier int
|
LevelMultiplier int
|
||||||
|
|
||||||
// MultiLevelCompactionHueristic determines whether to add an additional
|
// MultiLevelCompactionHeuristic determines whether to add an additional
|
||||||
// level to a conventional two level compaction. If nil, a multilevel
|
// level to a conventional two level compaction. If nil, a multilevel
|
||||||
// compaction will never get triggered.
|
// compaction will never get triggered.
|
||||||
MultiLevelCompactionHueristic MultiLevelHeuristic
|
MultiLevelCompactionHeuristic MultiLevelHeuristic
|
||||||
|
|
||||||
// MaxWriterConcurrency is used to indicate the maximum number of
|
// MaxWriterConcurrency is used to indicate the maximum number of
|
||||||
// compression workers the compression queue is allowed to use. If
|
// compression workers the compression queue is allowed to use. If
|
||||||
@@ -597,8 +642,11 @@ type Options struct {
|
|||||||
CPUWorkPermissionGranter CPUWorkPermissionGranter
|
CPUWorkPermissionGranter CPUWorkPermissionGranter
|
||||||
|
|
||||||
// EnableValueBlocks is used to decide whether to enable writing
|
// EnableValueBlocks is used to decide whether to enable writing
|
||||||
// TableFormatPebblev3 sstables. WARNING: do not return true yet, since
|
// TableFormatPebblev3 sstables. This setting is only respected by a
|
||||||
// support for TableFormatPebblev3 is incomplete and not production ready.
|
// specific subset of format major versions: FormatSSTableValueBlocks,
|
||||||
|
// FormatFlushableIngest and FormatPrePebblev1MarkedCompacted. In lower
|
||||||
|
// format major versions, value blocks are never enabled. In higher
|
||||||
|
// format major versions, value blocks are always enabled.
|
||||||
EnableValueBlocks func() bool
|
EnableValueBlocks func() bool
|
||||||
|
|
||||||
// ShortAttributeExtractor is used iff EnableValueBlocks() returns true
|
// ShortAttributeExtractor is used iff EnableValueBlocks() returns true
|
||||||
@@ -607,18 +655,24 @@ type Options struct {
|
|||||||
ShortAttributeExtractor ShortAttributeExtractor
|
ShortAttributeExtractor ShortAttributeExtractor
|
||||||
|
|
||||||
// RequiredInPlaceValueBound specifies an optional span of user key
|
// RequiredInPlaceValueBound specifies an optional span of user key
|
||||||
// prefixes for which the values must be stored with the key. This is
|
// prefixes that are not-MVCC, but have a suffix. For these the values
|
||||||
// useful for statically known exclusions to value separation. In
|
// must be stored with the key, since the concept of "older versions" is
|
||||||
// CockroachDB, this will be used for the lock table key space that has
|
// not defined. It is also useful for statically known exclusions to value
|
||||||
// non-empty suffixes, but those locks don't represent actual MVCC
|
// separation. In CockroachDB, this will be used for the lock table key
|
||||||
// versions (the suffix ordering is arbitrary). We will also need to add
|
// space that has non-empty suffixes, but those locks don't represent
|
||||||
// support for dynamically configured exclusions (we want the default to
|
// actual MVCC versions (the suffix ordering is arbitrary). We will also
|
||||||
// be to allow Pebble to decide whether to separate the value or not,
|
// need to add support for dynamically configured exclusions (we want the
|
||||||
// hence this is structured as exclusions), for example, for users of
|
// default to be to allow Pebble to decide whether to separate the value
|
||||||
// CockroachDB to dynamically exclude certain tables.
|
// or not, hence this is structured as exclusions), for example, for users
|
||||||
|
// of CockroachDB to dynamically exclude certain tables.
|
||||||
//
|
//
|
||||||
// Any change in exclusion behavior takes effect only on future written
|
// Any change in exclusion behavior takes effect only on future written
|
||||||
// sstables, and does not start rewriting existing sstables.
|
// sstables, and does not start rewriting existing sstables.
|
||||||
|
//
|
||||||
|
// Even ignoring changes in this setting, exclusions are interpreted as a
|
||||||
|
// guidance by Pebble, and not necessarily honored. Specifically, user
|
||||||
|
// keys with multiple Pebble-versions *may* have the older versions stored
|
||||||
|
// in value blocks.
|
||||||
RequiredInPlaceValueBound UserKeyPrefixBound
|
RequiredInPlaceValueBound UserKeyPrefixBound
|
||||||
|
|
||||||
// DisableIngestAsFlushable disables lazy ingestion of sstables through
|
// DisableIngestAsFlushable disables lazy ingestion of sstables through
|
||||||
@@ -626,15 +680,49 @@ type Options struct {
|
|||||||
// major version is at least `FormatFlushableIngest`.
|
// major version is at least `FormatFlushableIngest`.
|
||||||
DisableIngestAsFlushable func() bool
|
DisableIngestAsFlushable func() bool
|
||||||
|
|
||||||
// SharedStorage is a second FS-like storage medium that can be shared
|
// RemoteStorage enables use of remote storage (e.g. S3) for storing
|
||||||
// between multiple Pebble instances. It is used to store sstables only, and
|
// sstables. Setting this option enables use of CreateOnShared option and
|
||||||
// is managed by objstorage.Provider. Each sstable might only be written to
|
// allows ingestion of external files.
|
||||||
// by one Pebble instance, but other Pebble instances can possibly read the
|
RemoteStorage remote.StorageFactory
|
||||||
// same files if they have the path to get to them. The pebble instance that
|
|
||||||
// wrote a file should not delete it if other Pebble instances are known to
|
// If CreateOnShared is non-zero, new sstables are created on remote storage
|
||||||
// be reading this file. This FS is expected to have slower read/write
|
// (using CreateOnSharedLocator and with the appropriate
|
||||||
// performance than the default FS above.
|
// CreateOnSharedStrategy). These sstables can be shared between different
|
||||||
SharedStorage shared.Storage
|
// Pebble instances; the lifecycle of such objects is managed by the
|
||||||
|
// remote.Storage constructed by options.RemoteStorage.
|
||||||
|
//
|
||||||
|
// Can only be used when RemoteStorage is set (and recognizes
|
||||||
|
// CreateOnSharedLocator).
|
||||||
|
CreateOnShared remote.CreateOnSharedStrategy
|
||||||
|
CreateOnSharedLocator remote.Locator
|
||||||
|
|
||||||
|
// CacheSizeBytesBytes is the size of the on-disk block cache for objects
|
||||||
|
// on shared storage in bytes. If it is 0, no cache is used.
|
||||||
|
SecondaryCacheSizeBytes int64
|
||||||
|
|
||||||
|
// IneffectualPointDeleteCallback is called in compactions/flushes if any
|
||||||
|
// single delete is being elided without deleting a point set/merge.
|
||||||
|
IneffectualSingleDeleteCallback func(userKey []byte)
|
||||||
|
|
||||||
|
// SingleDeleteInvariantViolationCallback is called in compactions/flushes if any
|
||||||
|
// single delete has consumed a Set/Merge, and there is another immediately older
|
||||||
|
// Set/SetWithDelete/Merge. The user of Pebble has violated the invariant under
|
||||||
|
// which SingleDelete can be used correctly.
|
||||||
|
//
|
||||||
|
// Consider the sequence SingleDelete#3, Set#2, Set#1. There are three
|
||||||
|
// ways some of these keys can first meet in a compaction.
|
||||||
|
//
|
||||||
|
// - All 3 keys in the same compaction: this callback will detect the
|
||||||
|
// violation.
|
||||||
|
//
|
||||||
|
// - SingleDelete#3, Set#2 meet in a compaction first: Both keys will
|
||||||
|
// disappear. The violation will not be detected, and the DB will have
|
||||||
|
// Set#1 which is likely incorrect (from the user's perspective).
|
||||||
|
//
|
||||||
|
// - Set#2, Set#1 meet in a compaction first: The output will be Set#2,
|
||||||
|
// which will later be consumed by SingleDelete#3. The violation will
|
||||||
|
// not be detected and the DB will be correct.
|
||||||
|
SingleDeleteInvariantViolationCallback func(userKey []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters is a map from filter policy name to filter policy. It is used for
|
// Filters is a map from filter policy name to filter policy. It is used for
|
||||||
@@ -749,12 +837,18 @@ type Options struct {
|
|||||||
// writing the contents of the old one in the
|
// writing the contents of the old one in the
|
||||||
// background. MemTableStopWritesThreshold places a hard limit on the size of
|
// background. MemTableStopWritesThreshold places a hard limit on the size of
|
||||||
// the queued MemTables.
|
// the queued MemTables.
|
||||||
MemTableSize int
|
//
|
||||||
|
// The default value is 4MB.
|
||||||
|
MemTableSize uint64
|
||||||
|
|
||||||
// Hard limit on the size of queued of MemTables. Writes are stopped when the
|
// Hard limit on the number of queued of MemTables. Writes are stopped when
|
||||||
// sum of the queued memtable sizes exceeds
|
// the sum of the queued memtable sizes exceeds:
|
||||||
// MemTableStopWritesThreshold*MemTableSize. This value should be at least 2
|
// MemTableStopWritesThreshold * MemTableSize.
|
||||||
// or writes will stop whenever a MemTable is being flushed.
|
//
|
||||||
|
// This value should be at least 2 or writes will stop whenever a MemTable is
|
||||||
|
// being flushed.
|
||||||
|
//
|
||||||
|
// The default value is 2.
|
||||||
MemTableStopWritesThreshold int
|
MemTableStopWritesThreshold int
|
||||||
|
|
||||||
// Merger defines the associative merge operation to use for merging values
|
// Merger defines the associative merge operation to use for merging values
|
||||||
@@ -839,6 +933,20 @@ type Options struct {
|
|||||||
// changing options dynamically?
|
// changing options dynamically?
|
||||||
WALMinSyncInterval func() time.Duration
|
WALMinSyncInterval func() time.Duration
|
||||||
|
|
||||||
|
// TargetByteDeletionRate is the rate (in bytes per second) at which sstable file
|
||||||
|
// deletions are limited to (under normal circumstances).
|
||||||
|
//
|
||||||
|
// Deletion pacing is used to slow down deletions when compactions finish up
|
||||||
|
// or readers close and newly-obsolete files need cleaning up. Deleting lots
|
||||||
|
// of files at once can cause disk latency to go up on some SSDs, which this
|
||||||
|
// functionality guards against.
|
||||||
|
//
|
||||||
|
// This value is only a best-effort target; the effective rate can be
|
||||||
|
// higher if deletions are falling behind or disk space is running low.
|
||||||
|
//
|
||||||
|
// Setting this to 0 disables deletion pacing, which is also the default.
|
||||||
|
TargetByteDeletionRate int
|
||||||
|
|
||||||
// private options are only used by internal tests or are used internally
|
// private options are only used by internal tests or are used internally
|
||||||
// for facilitating upgrade paths of unconfigurable functionality.
|
// for facilitating upgrade paths of unconfigurable functionality.
|
||||||
private struct {
|
private struct {
|
||||||
@@ -872,6 +980,10 @@ type Options struct {
|
|||||||
// A private option to disable stats collection.
|
// A private option to disable stats collection.
|
||||||
disableTableStats bool
|
disableTableStats bool
|
||||||
|
|
||||||
|
// testingAlwaysWaitForCleanup is set by some tests to force waiting for
|
||||||
|
// obsolete file deletion (to make events deterministic).
|
||||||
|
testingAlwaysWaitForCleanup bool
|
||||||
|
|
||||||
// fsCloser holds a closer that should be invoked after a DB using these
|
// fsCloser holds a closer that should be invoked after a DB using these
|
||||||
// Options is closed. This is used to automatically stop the
|
// Options is closed. This is used to automatically stop the
|
||||||
// long-running goroutine associated with the disk-health-checking FS.
|
// long-running goroutine associated with the disk-health-checking FS.
|
||||||
@@ -983,7 +1095,7 @@ func (o *Options) EnsureDefaults() *Options {
|
|||||||
o.MaxOpenFiles = 1000
|
o.MaxOpenFiles = 1000
|
||||||
}
|
}
|
||||||
if o.MemTableSize <= 0 {
|
if o.MemTableSize <= 0 {
|
||||||
o.MemTableSize = 4 << 20
|
o.MemTableSize = 4 << 20 // 4 MB
|
||||||
}
|
}
|
||||||
if o.MemTableStopWritesThreshold <= 0 {
|
if o.MemTableStopWritesThreshold <= 0 {
|
||||||
o.MemTableStopWritesThreshold = 2
|
o.MemTableStopWritesThreshold = 2
|
||||||
@@ -1024,8 +1136,8 @@ func (o *Options) EnsureDefaults() *Options {
|
|||||||
if o.Experimental.CPUWorkPermissionGranter == nil {
|
if o.Experimental.CPUWorkPermissionGranter == nil {
|
||||||
o.Experimental.CPUWorkPermissionGranter = defaultCPUWorkGranter{}
|
o.Experimental.CPUWorkPermissionGranter = defaultCPUWorkGranter{}
|
||||||
}
|
}
|
||||||
if o.Experimental.MultiLevelCompactionHueristic == nil {
|
if o.Experimental.MultiLevelCompactionHeuristic == nil {
|
||||||
o.Experimental.MultiLevelCompactionHueristic = NoMultiLevel{}
|
o.Experimental.MultiLevelCompactionHeuristic = WriteAmpHeuristic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
o.initMaps()
|
o.initMaps()
|
||||||
@@ -1144,7 +1256,7 @@ func (o *Options) String() string {
|
|||||||
fmt.Fprintf(&buf, " max_open_files=%d\n", o.MaxOpenFiles)
|
fmt.Fprintf(&buf, " max_open_files=%d\n", o.MaxOpenFiles)
|
||||||
fmt.Fprintf(&buf, " mem_table_size=%d\n", o.MemTableSize)
|
fmt.Fprintf(&buf, " mem_table_size=%d\n", o.MemTableSize)
|
||||||
fmt.Fprintf(&buf, " mem_table_stop_writes_threshold=%d\n", o.MemTableStopWritesThreshold)
|
fmt.Fprintf(&buf, " mem_table_stop_writes_threshold=%d\n", o.MemTableStopWritesThreshold)
|
||||||
fmt.Fprintf(&buf, " min_deletion_rate=%d\n", o.Experimental.MinDeletionRate)
|
fmt.Fprintf(&buf, " min_deletion_rate=%d\n", o.TargetByteDeletionRate)
|
||||||
fmt.Fprintf(&buf, " merger=%s\n", o.Merger.Name)
|
fmt.Fprintf(&buf, " merger=%s\n", o.Merger.Name)
|
||||||
fmt.Fprintf(&buf, " read_compaction_rate=%d\n", o.Experimental.ReadCompactionRate)
|
fmt.Fprintf(&buf, " read_compaction_rate=%d\n", o.Experimental.ReadCompactionRate)
|
||||||
fmt.Fprintf(&buf, " read_sampling_multiplier=%d\n", o.Experimental.ReadSamplingMultiplier)
|
fmt.Fprintf(&buf, " read_sampling_multiplier=%d\n", o.Experimental.ReadSamplingMultiplier)
|
||||||
@@ -1165,6 +1277,8 @@ func (o *Options) String() string {
|
|||||||
fmt.Fprintf(&buf, " wal_bytes_per_sync=%d\n", o.WALBytesPerSync)
|
fmt.Fprintf(&buf, " wal_bytes_per_sync=%d\n", o.WALBytesPerSync)
|
||||||
fmt.Fprintf(&buf, " max_writer_concurrency=%d\n", o.Experimental.MaxWriterConcurrency)
|
fmt.Fprintf(&buf, " max_writer_concurrency=%d\n", o.Experimental.MaxWriterConcurrency)
|
||||||
fmt.Fprintf(&buf, " force_writer_parallelism=%t\n", o.Experimental.ForceWriterParallelism)
|
fmt.Fprintf(&buf, " force_writer_parallelism=%t\n", o.Experimental.ForceWriterParallelism)
|
||||||
|
fmt.Fprintf(&buf, " secondary_cache_size_bytes=%d\n", o.Experimental.SecondaryCacheSizeBytes)
|
||||||
|
fmt.Fprintf(&buf, " create_on_shared=%d\n", o.Experimental.CreateOnShared)
|
||||||
|
|
||||||
// Private options.
|
// Private options.
|
||||||
//
|
//
|
||||||
@@ -1322,7 +1436,7 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "compaction_debt_concurrency":
|
case "compaction_debt_concurrency":
|
||||||
o.Experimental.CompactionDebtConcurrency, err = strconv.Atoi(value)
|
o.Experimental.CompactionDebtConcurrency, err = strconv.ParseUint(value, 10, 64)
|
||||||
case "delete_range_flush_delay":
|
case "delete_range_flush_delay":
|
||||||
// NB: This is a deprecated serialization of the
|
// NB: This is a deprecated serialization of the
|
||||||
// `flush_delay_delete_range`.
|
// `flush_delay_delete_range`.
|
||||||
@@ -1354,7 +1468,7 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
|
|||||||
// version is valid right here.
|
// version is valid right here.
|
||||||
var v uint64
|
var v uint64
|
||||||
v, err = strconv.ParseUint(value, 10, 64)
|
v, err = strconv.ParseUint(value, 10, 64)
|
||||||
if vers := FormatMajorVersion(v); vers > FormatNewest || vers == FormatDefault {
|
if vers := FormatMajorVersion(v); vers > internalFormatNewest || vers == FormatDefault {
|
||||||
err = errors.Newf("unknown format major version %d", o.FormatMajorVersion)
|
err = errors.Newf("unknown format major version %d", o.FormatMajorVersion)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -1387,14 +1501,14 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
|
|||||||
case "max_open_files":
|
case "max_open_files":
|
||||||
o.MaxOpenFiles, err = strconv.Atoi(value)
|
o.MaxOpenFiles, err = strconv.Atoi(value)
|
||||||
case "mem_table_size":
|
case "mem_table_size":
|
||||||
o.MemTableSize, err = strconv.Atoi(value)
|
o.MemTableSize, err = strconv.ParseUint(value, 10, 64)
|
||||||
case "mem_table_stop_writes_threshold":
|
case "mem_table_stop_writes_threshold":
|
||||||
o.MemTableStopWritesThreshold, err = strconv.Atoi(value)
|
o.MemTableStopWritesThreshold, err = strconv.Atoi(value)
|
||||||
case "min_compaction_rate":
|
case "min_compaction_rate":
|
||||||
// Do nothing; option existed in older versions of pebble, and
|
// Do nothing; option existed in older versions of pebble, and
|
||||||
// may be meaningful again eventually.
|
// may be meaningful again eventually.
|
||||||
case "min_deletion_rate":
|
case "min_deletion_rate":
|
||||||
o.Experimental.MinDeletionRate, err = strconv.Atoi(value)
|
o.TargetByteDeletionRate, err = strconv.Atoi(value)
|
||||||
case "min_flush_rate":
|
case "min_flush_rate":
|
||||||
// Do nothing; option existed in older versions of pebble, and
|
// Do nothing; option existed in older versions of pebble, and
|
||||||
// may be meaningful again eventually.
|
// may be meaningful again eventually.
|
||||||
@@ -1438,6 +1552,12 @@ func (o *Options) Parse(s string, hooks *ParseHooks) error {
|
|||||||
o.Experimental.MaxWriterConcurrency, err = strconv.Atoi(value)
|
o.Experimental.MaxWriterConcurrency, err = strconv.Atoi(value)
|
||||||
case "force_writer_parallelism":
|
case "force_writer_parallelism":
|
||||||
o.Experimental.ForceWriterParallelism, err = strconv.ParseBool(value)
|
o.Experimental.ForceWriterParallelism, err = strconv.ParseBool(value)
|
||||||
|
case "secondary_cache_size_bytes":
|
||||||
|
o.Experimental.SecondaryCacheSizeBytes, err = strconv.ParseInt(value, 10, 64)
|
||||||
|
case "create_on_shared":
|
||||||
|
var createOnSharedInt int64
|
||||||
|
createOnSharedInt, err = strconv.ParseInt(value, 10, 64)
|
||||||
|
o.Experimental.CreateOnShared = remote.CreateOnSharedStrategy(createOnSharedInt)
|
||||||
default:
|
default:
|
||||||
if hooks != nil && hooks.SkipUnknown != nil && hooks.SkipUnknown(section+"."+key, value) {
|
if hooks != nil && hooks.SkipUnknown != nil && hooks.SkipUnknown(section+"."+key, value) {
|
||||||
return nil
|
return nil
|
||||||
@@ -1568,15 +1688,15 @@ func (o *Options) Validate() error {
|
|||||||
}
|
}
|
||||||
if uint64(o.MemTableSize) >= maxMemTableSize {
|
if uint64(o.MemTableSize) >= maxMemTableSize {
|
||||||
fmt.Fprintf(&buf, "MemTableSize (%s) must be < %s\n",
|
fmt.Fprintf(&buf, "MemTableSize (%s) must be < %s\n",
|
||||||
humanize.Uint64(uint64(o.MemTableSize)), humanize.Uint64(maxMemTableSize))
|
humanize.Bytes.Uint64(uint64(o.MemTableSize)), humanize.Bytes.Uint64(maxMemTableSize))
|
||||||
}
|
}
|
||||||
if o.MemTableStopWritesThreshold < 2 {
|
if o.MemTableStopWritesThreshold < 2 {
|
||||||
fmt.Fprintf(&buf, "MemTableStopWritesThreshold (%d) must be >= 2\n",
|
fmt.Fprintf(&buf, "MemTableStopWritesThreshold (%d) must be >= 2\n",
|
||||||
o.MemTableStopWritesThreshold)
|
o.MemTableStopWritesThreshold)
|
||||||
}
|
}
|
||||||
if o.FormatMajorVersion > FormatNewest {
|
if o.FormatMajorVersion > internalFormatNewest {
|
||||||
fmt.Fprintf(&buf, "FormatMajorVersion (%d) must be <= %d\n",
|
fmt.Fprintf(&buf, "FormatMajorVersion (%d) must be <= %d\n",
|
||||||
o.FormatMajorVersion, FormatNewest)
|
o.FormatMajorVersion, internalFormatNewest)
|
||||||
}
|
}
|
||||||
if o.TableCache != nil && o.Cache != o.TableCache.cache {
|
if o.TableCache != nil && o.Cache != o.TableCache.cache {
|
||||||
fmt.Fprintf(&buf, "underlying cache in the TableCache and the Cache dont match\n")
|
fmt.Fprintf(&buf, "underlying cache in the TableCache and the Cache dont match\n")
|
||||||
@@ -1596,6 +1716,7 @@ func (o *Options) MakeReaderOptions() sstable.ReaderOptions {
|
|||||||
readerOpts.Comparer = o.Comparer
|
readerOpts.Comparer = o.Comparer
|
||||||
readerOpts.Filters = o.Filters
|
readerOpts.Filters = o.Filters
|
||||||
if o.Merger != nil {
|
if o.Merger != nil {
|
||||||
|
readerOpts.Merge = o.Merger.Merge
|
||||||
readerOpts.MergerName = o.Merger.Name
|
readerOpts.MergerName = o.Merger.Name
|
||||||
}
|
}
|
||||||
readerOpts.LoggerAndTracer = o.LoggerAndTracer
|
readerOpts.LoggerAndTracer = o.LoggerAndTracer
|
||||||
@@ -1620,6 +1741,9 @@ func (o *Options) MakeWriterOptions(level int, format sstable.TableFormat) sstab
|
|||||||
if format >= sstable.TableFormatPebblev3 {
|
if format >= sstable.TableFormatPebblev3 {
|
||||||
writerOpts.ShortAttributeExtractor = o.Experimental.ShortAttributeExtractor
|
writerOpts.ShortAttributeExtractor = o.Experimental.ShortAttributeExtractor
|
||||||
writerOpts.RequiredInPlaceValueBound = o.Experimental.RequiredInPlaceValueBound
|
writerOpts.RequiredInPlaceValueBound = o.Experimental.RequiredInPlaceValueBound
|
||||||
|
if format >= sstable.TableFormatPebblev4 && level == numLevels-1 {
|
||||||
|
writerOpts.WritingToLowestLevel = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
levelOpts := o.Level(level)
|
levelOpts := o.Level(level)
|
||||||
writerOpts.BlockRestartInterval = levelOpts.BlockRestartInterval
|
writerOpts.BlockRestartInterval = levelOpts.BlockRestartInterval
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user