cosmos-sdk/store/storage/rocksdb/db.go
Aleksandr Bezobchuk 20b1da7a2e
refactor(store/v2): Use Core Iterator (#18957)
Co-authored-by: marbar3778 <marbar3778@yahoo.com>
Co-authored-by: cool-developer <51834436+cool-develope@users.noreply.github.com>
2024-01-08 19:26:10 +00:00

235 lines
5.7 KiB
Go

//go:build rocksdb
// +build rocksdb
package rocksdb
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/linxGnu/grocksdb"
"golang.org/x/exp/slices"
corestore "cosmossdk.io/core/store"
"cosmossdk.io/store/v2"
"cosmossdk.io/store/v2/storage"
"cosmossdk.io/store/v2/storage/util"
)
const (
TimestampSize = 8
StorePrefixTpl = "s/k:%s/"
latestVersionKey = "s/latest"
)
var (
_ storage.Database = (*Database)(nil)
defaultWriteOpts = grocksdb.NewDefaultWriteOptions()
defaultReadOpts = grocksdb.NewDefaultReadOptions()
)
type Database struct {
storage *grocksdb.DB
cfHandle *grocksdb.ColumnFamilyHandle
// tsLow reflects the full_history_ts_low CF value, which is earliest version
// supported
tsLow uint64
}
func New(dataDir string) (*Database, error) {
storage, cfHandle, err := OpenRocksDB(dataDir)
if err != nil {
return nil, fmt.Errorf("failed to open RocksDB: %w", err)
}
slice, err := storage.GetFullHistoryTsLow(cfHandle)
if err != nil {
return nil, fmt.Errorf("failed to get full_history_ts_low: %w", err)
}
var tsLow uint64
tsLowBz := copyAndFreeSlice(slice)
if len(tsLowBz) > 0 {
tsLow = binary.LittleEndian.Uint64(tsLowBz)
}
return &Database{
storage: storage,
cfHandle: cfHandle,
tsLow: tsLow,
}, nil
}
func NewWithDB(storage *grocksdb.DB, cfHandle *grocksdb.ColumnFamilyHandle) (*Database, error) {
slice, err := storage.GetFullHistoryTsLow(cfHandle)
if err != nil {
return nil, fmt.Errorf("failed to get full_history_ts_low: %w", err)
}
var tsLow uint64
tsLowBz := copyAndFreeSlice(slice)
if len(tsLowBz) > 0 {
tsLow = binary.LittleEndian.Uint64(tsLowBz)
}
return &Database{
storage: storage,
cfHandle: cfHandle,
tsLow: tsLow,
}, nil
}
func (db *Database) Close() error {
db.storage.Close()
db.storage = nil
db.cfHandle = nil
return nil
}
func (db *Database) NewBatch(version uint64) (store.Batch, error) {
return NewBatch(db, version), nil
}
func (db *Database) getSlice(storeKey string, version uint64, key []byte) (*grocksdb.Slice, error) {
if version < db.tsLow {
return nil, store.ErrVersionPruned{EarliestVersion: db.tsLow}
}
return db.storage.GetCF(
newTSReadOptions(version),
db.cfHandle,
prependStoreKey(storeKey, key),
)
}
func (db *Database) SetLatestVersion(version uint64) error {
var ts [TimestampSize]byte
binary.LittleEndian.PutUint64(ts[:], version)
return db.storage.Put(defaultWriteOpts, []byte(latestVersionKey), ts[:])
}
func (db *Database) GetLatestVersion() (uint64, error) {
bz, err := db.storage.GetBytes(defaultReadOpts, []byte(latestVersionKey))
if err != nil {
return 0, err
}
if len(bz) == 0 {
// in case of a fresh database
return 0, nil
}
return binary.LittleEndian.Uint64(bz), nil
}
func (db *Database) Has(storeKey string, version uint64, key []byte) (bool, error) {
slice, err := db.getSlice(storeKey, version, key)
if err != nil {
return false, err
}
return slice.Exists(), nil
}
func (db *Database) Get(storeKey string, version uint64, key []byte) ([]byte, error) {
slice, err := db.getSlice(storeKey, version, key)
if err != nil {
return nil, fmt.Errorf("failed to get RocksDB slice: %w", err)
}
return copyAndFreeSlice(slice), nil
}
// Prune prunes all versions up to and including the provided version argument.
// Internally, this performs a manual compaction, the data with older timestamp
// will be GCed by compaction.
func (db *Database) Prune(version uint64) error {
tsLow := version + 1 // we increment by 1 to include the provided version
var ts [TimestampSize]byte
binary.LittleEndian.PutUint64(ts[:], tsLow)
compactOpts := grocksdb.NewCompactRangeOptions()
compactOpts.SetFullHistoryTsLow(ts[:])
db.storage.CompactRangeCFOpt(db.cfHandle, grocksdb.Range{}, compactOpts)
db.tsLow = tsLow
return nil
}
func (db *Database) Iterator(storeKey string, version uint64, start, end []byte) (corestore.Iterator, error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, store.ErrKeyEmpty
}
if start != nil && end != nil && bytes.Compare(start, end) > 0 {
return nil, store.ErrStartAfterEnd
}
prefix := storePrefix(storeKey)
start, end = util.IterateWithPrefix(prefix, start, end)
itr := db.storage.NewIteratorCF(newTSReadOptions(version), db.cfHandle)
return newRocksDBIterator(itr, prefix, start, end, false), nil
}
func (db *Database) ReverseIterator(storeKey string, version uint64, start, end []byte) (corestore.Iterator, error) {
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
return nil, store.ErrKeyEmpty
}
if start != nil && end != nil && bytes.Compare(start, end) > 0 {
return nil, store.ErrStartAfterEnd
}
prefix := storePrefix(storeKey)
start, end = util.IterateWithPrefix(prefix, start, end)
itr := db.storage.NewIteratorCF(newTSReadOptions(version), db.cfHandle)
return newRocksDBIterator(itr, prefix, start, end, true), nil
}
// newTSReadOptions returns ReadOptions used in the RocksDB column family read.
func newTSReadOptions(version uint64) *grocksdb.ReadOptions {
var ts [TimestampSize]byte
binary.LittleEndian.PutUint64(ts[:], version)
readOpts := grocksdb.NewDefaultReadOptions()
readOpts.SetTimestamp(ts[:])
return readOpts
}
func storePrefix(storeKey string) []byte {
return []byte(fmt.Sprintf(StorePrefixTpl, storeKey))
}
func prependStoreKey(storeKey string, key []byte) []byte {
return append(storePrefix(storeKey), key...)
}
// copyAndFreeSlice will copy a given RocksDB slice and free it. If the slice does
// not exist, <nil> will be returned.
func copyAndFreeSlice(s *grocksdb.Slice) []byte {
defer s.Free()
if !s.Exists() {
return nil
}
return slices.Clone(s.Data())
}
func readOnlySlice(s *grocksdb.Slice) []byte {
if !s.Exists() {
return nil
}
return s.Data()
}