[#18] Add badgerstore substorage implementation #556
10 changed files with 607 additions and 5 deletions
|
@ -21,6 +21,7 @@ import (
|
||||||
contractsconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/contracts"
|
contractsconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/contracts"
|
||||||
engineconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine"
|
engineconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine"
|
||||||
shardconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard"
|
shardconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard"
|
||||||
|
badgerstoreconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/badgerstore"
|
||||||
blobovniczaconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/blobovnicza"
|
blobovniczaconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/blobovnicza"
|
||||||
fstreeconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/fstree"
|
fstreeconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/fstree"
|
||||||
loggerconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/logger"
|
loggerconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/logger"
|
||||||
|
@ -32,6 +33,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
netmapCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
netmapCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/badgerstore"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/engine"
|
||||||
|
@ -176,6 +178,20 @@ type subStorageCfg struct {
|
||||||
width uint64
|
width uint64
|
||||||
leafWidth uint64
|
leafWidth uint64
|
||||||
openedCacheSize int
|
openedCacheSize int
|
||||||
|
|
||||||
|
// badgerstore-specific
|
||||||
|
badger struct {
|
||||||
|
numCompactors int
|
||||||
|
numGoroutines int
|
||||||
|
numLevelZeroTables int
|
||||||
|
numLevelZeroTablesStall int
|
||||||
|
numMemtables int
|
||||||
|
memtableSize int64
|
||||||
|
maxLevels int
|
||||||
|
levelSizeMultiplier int
|
||||||
|
valueLogMaxEntries uint32
|
||||||
|
gcInterval time.Duration
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// readConfig fills applicationConfiguration with raw configuration values
|
// readConfig fills applicationConfiguration with raw configuration values
|
||||||
|
@ -291,6 +307,21 @@ func (a *applicationConfiguration) setShardStorageConfig(newConfig *shardCfg, ol
|
||||||
sCfg.width = sub.ShallowWidth()
|
sCfg.width = sub.ShallowWidth()
|
||||||
sCfg.leafWidth = sub.LeafWidth()
|
sCfg.leafWidth = sub.LeafWidth()
|
||||||
sCfg.openedCacheSize = sub.OpenedCacheSize()
|
sCfg.openedCacheSize = sub.OpenedCacheSize()
|
||||||
|
|
||||||
|
case badgerstore.Type:
|
||||||
|
sub := badgerstoreconfig.From((*config.Config)(storagesCfg[i]))
|
||||||
|
|
||||||
|
sCfg.badger.numCompactors = sub.NumCompactors()
|
||||||
|
sCfg.badger.numGoroutines = sub.NumGoroutines()
|
||||||
|
sCfg.badger.numLevelZeroTables = sub.NumLevelZeroTables()
|
||||||
|
sCfg.badger.numLevelZeroTablesStall = sub.NumLevelZeroTablesStall()
|
||||||
|
sCfg.badger.numMemtables = sub.NumMemtables()
|
||||||
|
sCfg.badger.memtableSize = sub.MemtableSize()
|
||||||
|
sCfg.badger.maxLevels = sub.MaxLevels()
|
||||||
|
sCfg.badger.levelSizeMultiplier = sub.LevelSizeMultiplier()
|
||||||
|
sCfg.badger.valueLogMaxEntries = sub.ValueLogMaxEntries()
|
||||||
|
sCfg.badger.gcInterval = sub.GCInterval()
|
||||||
|
|
||||||
case fstree.Type:
|
case fstree.Type:
|
||||||
sub := fstreeconfig.From((*config.Config)(storagesCfg[i]))
|
sub := fstreeconfig.From((*config.Config)(storagesCfg[i]))
|
||||||
sCfg.depth = sub.Depth()
|
sCfg.depth = sub.Depth()
|
||||||
|
@ -797,6 +828,27 @@ func (c *cfg) getSubstorageOpts(shCfg shardCfg) []blobstor.SubStorage {
|
||||||
return uint64(len(data)) < shCfg.smallSizeObjectLimit
|
return uint64(len(data)) < shCfg.smallSizeObjectLimit
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
case badgerstore.Type:
|
||||||
|
ss = append(ss, blobstor.SubStorage{
|
||||||
|
Storage: badgerstore.New(
|
||||||
|
badgerstore.WithPath(sRead.path),
|
||||||
|
badgerstore.WithPermissions(sRead.perm),
|
||||||
|
badgerstore.WithLogger(c.log),
|
||||||
|
badgerstore.WithNumCompactors(sRead.badger.numCompactors),
|
||||||
|
badgerstore.WithNumGoroutines(sRead.badger.numGoroutines),
|
||||||
|
badgerstore.WithNumLevelZeroTables(sRead.badger.numLevelZeroTables),
|
||||||
|
badgerstore.WithNumLevelZeroTablesStall(sRead.badger.numLevelZeroTablesStall),
|
||||||
|
badgerstore.WithNumMemTables(sRead.badger.numMemtables),
|
||||||
|
badgerstore.WithMemtableSize(sRead.badger.memtableSize),
|
||||||
|
badgerstore.WithMaxLevels(sRead.badger.maxLevels),
|
||||||
|
badgerstore.WithLevelSizeMultiplier(sRead.badger.levelSizeMultiplier),
|
||||||
|
badgerstore.WithValueLogMaxEntries(sRead.badger.valueLogMaxEntries),
|
||||||
|
badgerstore.WithGCInterval(sRead.badger.gcInterval),
|
||||||
|
),
|
||||||
|
Policy: func(_ *objectSDK.Object, data []byte) bool {
|
||||||
|
return uint64(len(data)) < shCfg.smallSizeObjectLimit
|
||||||
|
},
|
||||||
|
})
|
||||||
case fstree.Type:
|
case fstree.Type:
|
||||||
fstreeOpts := []fstree.Option{
|
fstreeOpts := []fstree.Option{
|
||||||
fstree.WithPath(sRead.path),
|
fstree.WithPath(sRead.path),
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package badgerstoreconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/badgerstore"
|
||||||
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is a wrapper over the config section
|
||||||
|
// which provides access to badger configuration.
|
||||||
|
// For information about specific parameters, see https://pkg.go.dev/github.com/dgraph-io/badger/v4.
|
||||||
|
type Config config.Config
|
||||||
|
|
||||||
|
var defaultOpts = badger.DefaultOptions("")
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultGCInterval = 10 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
func From(c *config.Config) *Config { return (*Config)(c) }
|
||||||
|
func (x *Config) Type() string { return badgerstore.Type }
|
||||||
|
|
||||||
|
func (x *Config) NumCompactors() int {
|
||||||
|
s := int(config.IntSafe((*config.Config)(x), "num_compactors"))
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultOpts.NumCompactors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) NumGoroutines() int {
|
||||||
|
s := int(config.IntSafe((*config.Config)(x), "num_goroutines"))
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultOpts.NumGoroutines
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) NumLevelZeroTables() int {
|
||||||
|
s := int(config.IntSafe((*config.Config)(x), "num_level_zero_tables"))
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultOpts.NumLevelZeroTables
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) NumLevelZeroTablesStall() int {
|
||||||
|
s := int(config.IntSafe((*config.Config)(x), "num_level_zero_tables_stall"))
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultOpts.NumLevelZeroTablesStall
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) NumMemtables() int {
|
||||||
|
s := int(config.IntSafe((*config.Config)(x), "num_memtables"))
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultOpts.NumMemtables
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) MemtableSize() int64 {
|
||||||
|
s := config.IntSafe((*config.Config)(x), "memtable_size")
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultOpts.MemTableSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) MaxLevels() int {
|
||||||
|
s := int(config.IntSafe((*config.Config)(x), "max_levels"))
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultOpts.MaxLevels
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) LevelSizeMultiplier() int {
|
||||||
|
s := int(config.IntSafe((*config.Config)(x), "level_size_multiplier"))
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultOpts.LevelSizeMultiplier
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) ValueLogMaxEntries() uint32 {
|
||||||
|
s := uint32(config.IntSafe((*config.Config)(x), "value_log_max_entries"))
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return defaultOpts.ValueLogMaxEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Config) GCInterval() time.Duration {
|
||||||
|
s := config.DurationSafe((*config.Config)(x), "gc_interval")
|
||||||
|
if s > 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return DefaultGCInterval
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
package blobstorconfig
|
package blobstorconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/storage"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard/blobstor/storage"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/badgerstore"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is a wrapper over the config section
|
// Config is a wrapper over the config section
|
||||||
|
@ -23,11 +27,14 @@ func (x *Config) Storages() []*storage.Config {
|
||||||
typ := config.String(
|
typ := config.String(
|
||||||
(*config.Config)(x),
|
(*config.Config)(x),
|
||||||
strconv.Itoa(i)+".type")
|
strconv.Itoa(i)+".type")
|
||||||
if typ == "" {
|
switch typ {
|
||||||
|
case "":
|
||||||
return ss
|
return ss
|
||||||
}
|
case fstree.Type, blobovniczatree.Type, badgerstore.Type:
|
||||||
|
|
||||||
sub := storage.From((*config.Config)(x).Sub(strconv.Itoa(i)))
|
sub := storage.From((*config.Config)(x).Sub(strconv.Itoa(i)))
|
||||||
ss = append(ss, sub)
|
ss = append(ss, sub)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid type: %q", typ))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
shardconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard"
|
shardconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/engine/shard"
|
||||||
loggerconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/logger"
|
loggerconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/logger"
|
||||||
treeconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/tree"
|
treeconfig "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-node/config/tree"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/badgerstore"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||||
|
@ -55,7 +56,7 @@ func validateConfig(c *config.Config) error {
|
||||||
}
|
}
|
||||||
for i := range blobstor {
|
for i := range blobstor {
|
||||||
switch blobstor[i].Type() {
|
switch blobstor[i].Type() {
|
||||||
case fstree.Type, blobovniczatree.Type:
|
case fstree.Type, blobovniczatree.Type, badgerstore.Type:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unexpected storage type: %s (shard %d)", blobstor[i].Type(), shardNum)
|
return fmt.Errorf("unexpected storage type: %s (shard %d)", blobstor[i].Type(), shardNum)
|
||||||
}
|
}
|
||||||
|
|
201
pkg/local_object_storage/blobstor/badgerstore/badgerstore.go
Normal file
201
pkg/local_object_storage/blobstor/badgerstore/badgerstore.go
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
package badgerstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/util/logicerr"
|
||||||
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Type = "badgerstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
type badgerstoreImpl struct {
|
||||||
|
*cfg
|
||||||
|
db *badger.DB
|
||||||
|
closeCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(opts ...Option) common.Storage {
|
||||||
|
st := &badgerstoreImpl{
|
||||||
|
cfg: defaultConfig(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(st.cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
st.badgerOptions = st.badgerOptions.WithDir(st.cfg.path)
|
||||||
|
st.badgerOptions = st.badgerOptions.WithValueDir(st.cfg.path)
|
||||||
|
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) Get(_ context.Context, req common.GetPrm) (common.GetRes, error) {
|
||||||
|
addrKey := addressKey(req.Address)
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
if err := s.db.View(func(tx *badger.Txn) error {
|
||||||
|
it, err := tx.Get(addrKey[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err = it.ValueCopy(nil)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
if err == badger.ErrKeyNotFound {
|
||||||
|
return common.GetRes{}, logicerr.Wrap(new(apistatus.ObjectNotFound))
|
||||||
|
}
|
||||||
|
return common.GetRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if data, err = s.compression.Decompress(data); err != nil {
|
||||||
|
return common.GetRes{}, fmt.Errorf("could not decompress object data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := objectSDK.New()
|
||||||
|
if err := obj.Unmarshal(data); err != nil {
|
||||||
|
return common.GetRes{}, fmt.Errorf("could not unmarshal the object: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return common.GetRes{Object: obj, RawData: data}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) GetRange(ctx context.Context, req common.GetRangePrm) (common.GetRangeRes, error) {
|
||||||
|
getResp, err := s.Get(ctx, common.GetPrm{
|
||||||
|
Address: req.Address,
|
||||||
|
StorageID: req.StorageID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return common.GetRangeRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := getResp.Object.Payload()
|
||||||
|
from := req.Range.GetOffset()
|
||||||
|
to := from + req.Range.GetLength()
|
||||||
|
|
||||||
|
if pLen := uint64(len(payload)); to < from || pLen < from || pLen < to {
|
||||||
|
return common.GetRangeRes{}, logicerr.Wrap(new(apistatus.ObjectOutOfRange))
|
||||||
|
}
|
||||||
|
|
||||||
|
return common.GetRangeRes{
|
||||||
|
Data: payload[from:to],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) Exists(_ context.Context, req common.ExistsPrm) (common.ExistsRes, error) {
|
||||||
|
addrKey := addressKey(req.Address)
|
||||||
|
exists := true
|
||||||
|
|
||||||
|
if err := s.db.View(func(tx *badger.Txn) error {
|
||||||
|
_, err := tx.Get(addrKey[:])
|
||||||
|
if err == badger.ErrKeyNotFound {
|
||||||
|
exists = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
|
return common.ExistsRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return common.ExistsRes{Exists: exists}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) Put(_ context.Context, req common.PutPrm) (common.PutRes, error) {
|
||||||
|
if s.badgerOptions.ReadOnly {
|
||||||
|
return common.PutRes{}, common.ErrReadOnly
|
||||||
|
}
|
||||||
|
if !req.DontCompress {
|
||||||
|
req.RawData = s.compression.Compress(req.RawData)
|
||||||
|
}
|
||||||
|
|
||||||
|
addrKey := addressKey(req.Address)
|
||||||
|
|
||||||
|
wb := s.db.NewWriteBatch()
|
||||||
|
defer wb.Cancel()
|
||||||
|
_ = wb.Set(addrKey[:], req.RawData)
|
||||||
|
return common.PutRes{}, wb.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) Delete(_ context.Context, req common.DeletePrm) (common.DeleteRes, error) {
|
||||||
|
if s.badgerOptions.ReadOnly {
|
||||||
|
return common.DeleteRes{}, common.ErrReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
addrKey := addressKey(req.Address)
|
||||||
|
|
||||||
|
err := s.db.Update(func(tx *badger.Txn) error {
|
||||||
|
if _, err := tx.Get(addrKey[:]); err != nil {
|
||||||
|
|||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Delete(addrKey[:])
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == badger.ErrKeyNotFound {
|
||||||
|
err = logicerr.Wrap(new(apistatus.ObjectNotFound))
|
||||||
|
}
|
||||||
|
|
||||||
|
return common.DeleteRes{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) iterateKeyValue(req common.IteratePrm, k, v []byte) error {
|
||||||
|
elem := common.IterationElement{
|
||||||
|
ObjectData: v,
|
||||||
|
}
|
||||||
|
if err := decodeAddress(k, &elem.Address); err != nil {
|
||||||
|
if req.IgnoreErrors {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return logicerr.Wrap(fmt.Errorf("(%T) decoding address string %q: %v", s, string(k), err))
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if elem.ObjectData, err = s.compression.Decompress(elem.ObjectData); err != nil {
|
||||||
|
if req.IgnoreErrors {
|
||||||
|
if req.ErrorHandler != nil {
|
||||||
|
return req.ErrorHandler(elem.Address, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return logicerr.Wrap(fmt.Errorf("(%T) decompressing data for address %q: %v", s, elem.Address.String(), err))
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case req.Handler != nil:
|
||||||
|
if err := req.Handler(elem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case req.LazyHandler != nil:
|
||||||
|
if err := req.LazyHandler(elem.Address, func() ([]byte, error) { return elem.ObjectData, nil }); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if !req.IgnoreErrors {
|
||||||
|
return logicerr.Wrap(fmt.Errorf("(%T) no Handler or LazyHandler set for IteratePrm", s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) Iterate(_ context.Context, req common.IteratePrm) (common.IterateRes, error) {
|
||||||
|
err := s.db.View(func(tx *badger.Txn) error {
|
||||||
|
it := tx.NewIterator(badger.DefaultIteratorOptions)
|
||||||
|
defer it.Close()
|
||||||
|
for it.Rewind(); it.Valid(); it.Next() {
|
||||||
|
if err := it.Item().Value(func(val []byte) error {
|
||||||
|
return s.iterateKeyValue(req, it.Item().Key(), val)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return common.IterateRes{}, err
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package badgerstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/internal/blobstortest"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGeneric(t *testing.T) {
|
||||||
|
ctor := func(t *testing.T) common.Storage {
|
||||||
|
return New(
|
||||||
|
WithPath(filepath.Join(t.TempDir(), "badgerstore")),
|
||||||
|
WithLogger(&logger.Logger{Logger: zaptest.NewLogger(t)}),
|
||||||
|
WithGCInterval(10),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
blobstortest.TestAll(t, ctor, 2048, 16*1024)
|
||||||
|
}
|
49
pkg/local_object_storage/blobstor/badgerstore/control.go
Normal file
49
pkg/local_object_storage/blobstor/badgerstore/control.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package badgerstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/compression"
|
||||||
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) Open(readOnly bool) (err error) {
|
||||||
|
s.badgerOptions = s.badgerOptions.WithReadOnly(readOnly)
|
||||||
|
s.db, err = badger.Open(s.badgerOptions)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) gc() {
|
||||||
|
t := time.NewTicker(s.gcInterval)
|
||||||
|
defer t.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-s.closeCh:
|
||||||
|
return
|
||||||
|
case <-t.C:
|
||||||
|
for {
|
||||||
|
if err := s.db.RunValueLogGC(0.5); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) Init() error {
|
||||||
|
s.closeCh = make(chan struct{})
|
||||||
|
go s.gc()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) Close() error {
|
||||||
|
close(s.closeCh)
|
||||||
|
return s.db.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *badgerstoreImpl) Type() string { return Type }
|
||||||
|
func (s *badgerstoreImpl) Path() string { return s.path }
|
||||||
|
func (s *badgerstoreImpl) SetCompressor(cc *compression.Config) { s.compression = cc }
|
||||||
|
func (s *badgerstoreImpl) Compressor() *compression.Config { return s.compression }
|
||||||
|
func (s *badgerstoreImpl) SetReportErrorFunc(f func(string, error)) { s.reportError = f }
|
||||||
|
func (s *badgerstoreImpl) SetParentID(string) {}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package badgerstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
type internalKey [len(cid.ID{}) + len(oid.ID{})]byte
|
||||||
|
|
||||||
|
func decodeAddress(k []byte, addr *oid.Address) error {
|
||||||
|
if got, want := len(k), len(internalKey{}); got != want {
|
||||||
|
return fmt.Errorf("unexpected internal key len: got %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
var cnr cid.ID
|
||||||
|
var obj oid.ID
|
||||||
|
copy(cnr[:], k[:len(cnr)])
|
||||||
|
copy(obj[:], k[len(cnr):])
|
||||||
|
addr.SetContainer(cnr)
|
||||||
|
addr.SetObject(obj)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addressKey(addr oid.Address) internalKey {
|
||||||
|
var key internalKey
|
||||||
|
cnr, obj := addr.Container(), addr.Object()
|
||||||
|
copy(key[:len(cnr)], cnr[:])
|
||||||
|
copy(key[len(cnr):], obj[:])
|
||||||
|
return key
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package badgerstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInternalKey(t *testing.T) {
|
||||||
|
wantAddr := oidtest.Address()
|
||||||
|
k := addressKey(wantAddr)
|
||||||
|
var gotAddr oid.Address
|
||||||
|
|
||||||
|
require.NoError(t, decodeAddress(k[:], &gotAddr))
|
||||||
|
require.True(t, gotAddr.Equals(wantAddr))
|
||||||
|
}
|
117
pkg/local_object_storage/blobstor/badgerstore/option.go
Normal file
117
pkg/local_object_storage/blobstor/badgerstore/option.go
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
package badgerstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/compression"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
||||||
|
"github.com/dgraph-io/badger/v4"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cfg struct {
|
||||||
|
path string
|
||||||
|
perm fs.FileMode
|
||||||
|
log *logger.Logger
|
||||||
|
badgerOptions badger.Options
|
||||||
|
compression *compression.Config
|
||||||
|
reportError func(string, error)
|
||||||
|
gcInterval time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfig() *cfg {
|
||||||
|
return &cfg{
|
||||||
|
perm: os.ModePerm, // 0777
|
||||||
|
log: &logger.Logger{Logger: zap.L()},
|
||||||
|
reportError: func(string, error) {},
|
||||||
|
badgerOptions: badger.DefaultOptions(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(*cfg)
|
||||||
|
|
||||||
|
func WithLogger(l *logger.Logger) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.log = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPath(p string) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.path = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithReadOnly(ro bool) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions = c.badgerOptions.WithReadOnly(ro)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPermissions(perm fs.FileMode) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.perm = perm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNumCompactors(v int) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions.NumCompactors = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNumGoroutines(v int) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions.NumGoroutines = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNumLevelZeroTables(v int) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions.NumLevelZeroTables = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNumLevelZeroTablesStall(v int) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions.NumLevelZeroTablesStall = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNumMemTables(v int) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions.NumMemtables = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithMemtableSize(v int64) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions.MemTableSize = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithMaxLevels(v int) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions.MaxLevels = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLevelSizeMultiplier(v int) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions.LevelSizeMultiplier = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithValueLogMaxEntries(v uint32) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.badgerOptions.ValueLogMaxEntries = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithGCInterval(v time.Duration) Option {
|
||||||
|
return func(c *cfg) {
|
||||||
|
c.gcInterval = v
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue
Please explain why Get first and only then Delete?
Our
Delete
must returnapistatus.ObjectNotFound
. But badger doesn't returnbadger.ErrKeyNotFound
when deleting, so I callGet
first to check that.