chore(store/v2): refactor the pruning option (#20730)

This commit is contained in:
cool-developer 2024-06-26 10:59:54 -04:00 committed by GitHub
parent 43a7b99f67
commit ad52b415f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 219 additions and 72 deletions

View File

@ -114,7 +114,7 @@ func NewSimApp(
RootDir: DefaultNodeHome,
SSType: 0,
SCType: 0,
SCPruneOptions: &store.PruneOptions{
SCPruningOption: &store.PruningOption{
KeepRecent: 0,
Interval: 0,
},

View File

@ -0,0 +1,129 @@
//go:build rocksdb
// +build rocksdb
package commitment_test
import (
"fmt"
"math/rand"
"testing"
"github.com/stretchr/testify/require"
"cosmossdk.io/core/log"
corestore "cosmossdk.io/core/store"
"cosmossdk.io/store/v2/commitment"
"cosmossdk.io/store/v2/commitment/iavl"
dbm "cosmossdk.io/store/v2/db"
)
var (
storeKeys = []string{"store1", "store2", "store3"}
dbBackends = map[string]func(dataDir string) (corestore.KVStoreWithBatch, error){
"rocksdb_opts": func(dataDir string) (corestore.KVStoreWithBatch, error) {
return dbm.NewRocksDB("test", dataDir)
},
"pebbledb_opts": func(dataDir string) (corestore.KVStoreWithBatch, error) {
return dbm.NewPebbleDB("test", dataDir)
},
"goleveldb_opts": func(dataDir string) (corestore.KVStoreWithBatch, error) {
return dbm.NewGoLevelDB("test", dataDir, nil)
},
}
rng = rand.New(rand.NewSource(543210))
changesets = make([]*corestore.Changeset, 1000)
)
func init() {
for i := 0; i < 1000; i++ {
cs := corestore.NewChangeset()
for _, storeKey := range storeKeys {
for j := 0; j < 100; j++ {
key := make([]byte, 16)
val := make([]byte, 16)
_, err := rng.Read(key)
if err != nil {
panic(err)
}
_, err = rng.Read(val)
if err != nil {
panic(err)
}
cs.AddKVPair([]byte(storeKey), corestore.KVPair{Key: key, Value: val})
}
}
changesets[i] = cs
}
}
func getCommitStore(b *testing.B, db corestore.KVStoreWithBatch) *commitment.CommitStore {
b.Helper()
multiTrees := make(map[string]commitment.Tree)
for _, storeKey := range storeKeys {
prefixDB := dbm.NewPrefixDB(db, []byte(storeKey))
multiTrees[storeKey] = iavl.NewIavlTree(prefixDB, log.NewNopLogger(), iavl.DefaultConfig())
}
sc, err := commitment.NewCommitStore(multiTrees, db, log.NewNopLogger())
require.NoError(b, err)
return sc
}
func BenchmarkCommit(b *testing.B) {
for ty, fn := range dbBackends {
b.Run(fmt.Sprintf("backend_%s", ty), func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
b.StopTimer()
for i := 0; i < b.N; i++ {
db, err := fn(b.TempDir())
require.NoError(b, err)
sc := getCommitStore(b, db)
b.StartTimer()
for j, cs := range changesets {
require.NoError(b, sc.WriteChangeset(cs))
_, err := sc.Commit(uint64(j + 1))
require.NoError(b, err)
}
b.StopTimer()
require.NoError(b, db.Close())
}
})
}
}
func BenchmarkGetProof(b *testing.B) {
for ty, fn := range dbBackends {
db, err := fn(b.TempDir())
require.NoError(b, err)
sc := getCommitStore(b, db)
b.Run(fmt.Sprintf("backend_%s", ty), func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
b.StopTimer()
// commit some changesets
for i, cs := range changesets {
require.NoError(b, sc.WriteChangeset(cs))
_, err = sc.Commit(uint64(i + 1))
require.NoError(b, err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
// non-existing proof
p, err := sc.GetProof([]byte(storeKeys[0]), 500, []byte("key-1-1"))
require.NoError(b, err)
require.NotNil(b, p)
// existing proof
p, err = sc.GetProof([]byte(storeKeys[1]), 500, changesets[499].Changes[1].StateChanges[1].Key)
require.NoError(b, err)
require.NotNil(b, p)
}
})
require.NoError(b, db.Close())
}
}

View File

@ -125,10 +125,7 @@ func (s *CommitStoreTestSuite) TestStore_Snapshotter() {
func (s *CommitStoreTestSuite) TestStore_Pruning() {
storeKeys := []string{storeKey1, storeKey2}
pruneOpts := &store.PruneOptions{
KeepRecent: 10,
Interval: 5,
}
pruneOpts := store.NewPruningOptionWithCustom(10, 5)
commitStore, err := s.NewStore(dbm.NewMemDB(), storeKeys, log.NewNopLogger())
s.Require().NoError(err)

View File

@ -1,7 +1,24 @@
package store
// PruneOptions defines the pruning configuration.
type PruneOptions struct {
type PruningStrategy int
const (
// PruningDefault defines a pruning strategy where the last 362880 heights are
// kept where to-be pruned heights are pruned at every 10th height.
// The last 362880 heights are kept(approximately 3.5 weeks worth of state) assuming the typical
// block time is 6s. If these values do not match the applications' requirements, use the "custom" option.
PruningDefault PruningStrategy = iota
// PruningEverything defines a pruning strategy where all committed heights are
// deleted, storing only the current height and last 2 states. To-be pruned heights are
// pruned at every 10th height.
PruningEverything
// PruningNothing defines a pruning strategy where all heights are kept on disk.
// This is the only stretegy where KeepEvery=1 is allowed with state-sync snapshots disabled.
PruningNothing
)
// PruningOption defines the pruning configuration.
type PruningOption struct {
// KeepRecent sets the number of recent versions to keep.
KeepRecent uint64
@ -10,19 +27,41 @@ type PruneOptions struct {
Interval uint64
}
// DefaultPruneOptions returns the default pruning options.
// Interval is set to 0, which means no pruning will be done.
func DefaultPruneOptions() *PruneOptions {
return &PruneOptions{
KeepRecent: 0,
Interval: 0,
// NewPruningOption returns a new PruningOption instance based on the given pruning strategy.
func NewPruningOption(pruningStrategy PruningStrategy) *PruningOption {
switch pruningStrategy {
case PruningDefault:
return &PruningOption{
KeepRecent: 362880,
Interval: 10,
}
case PruningEverything:
return &PruningOption{
KeepRecent: 2,
Interval: 10,
}
case PruningNothing:
return &PruningOption{
KeepRecent: 0,
Interval: 0,
}
default:
return nil
}
}
// NewPruningOptionWithCustom returns a new PruningOption based on the given parameters.
func NewPruningOptionWithCustom(keepRecent, interval uint64) *PruningOption {
return &PruningOption{
KeepRecent: keepRecent,
Interval: interval,
}
}
// ShouldPrune returns true if the given version should be pruned.
// If true, it also returns the version to prune up to.
// NOTE: The current version is not pruned.
func (opts *PruneOptions) ShouldPrune(version uint64) (bool, uint64) {
func (opts *PruningOption) ShouldPrune(version uint64) (bool, uint64) {
if opts.Interval == 0 {
return false, 0
}

View File

@ -2,12 +2,12 @@
The `pruning` package defines the `PruningManager` struct which is responsible for
pruning the state storage (SS) and the state commitment (SC) based on the current
height of the chain. The `PruneOptions` struct defines the configuration for pruning
height of the chain. The `PruningOption` struct defines the configuration for pruning
and is passed to the `PruningManager` during initialization.
## Prune Options
The `PruneOptions` struct includes the following fields:
The `PruningOption` struct includes the following fields:
* `KeepRecent` (uint64): The number of recent heights to keep in the state.
* `Interval` (uint64): The interval of how often to prune the state. 0 means no pruning.

View File

@ -6,21 +6,21 @@ import "cosmossdk.io/store/v2"
type Manager struct {
// scPruner is the pruner for the SC.
scPruner store.Pruner
// scPruningOptions are the pruning options for the SC.
scPruningOptions *store.PruneOptions
// scPruningOption are the pruning options for the SC.
scPruningOption *store.PruningOption
// ssPruner is the pruner for the SS.
ssPruner store.Pruner
// ssPruningOptions are the pruning options for the SS.
ssPruningOptions *store.PruneOptions
// ssPruningOption are the pruning options for the SS.
ssPruningOption *store.PruningOption
}
// NewManager creates a new Pruning Manager.
func NewManager(scPruner, ssPruner store.Pruner, scPruningOptions, ssPruningOptions *store.PruneOptions) *Manager {
func NewManager(scPruner, ssPruner store.Pruner, scPruningOption, ssPruningOption *store.PruningOption) *Manager {
return &Manager{
scPruner: scPruner,
scPruningOptions: scPruningOptions,
ssPruner: ssPruner,
ssPruningOptions: ssPruningOptions,
scPruner: scPruner,
scPruningOption: scPruningOption,
ssPruner: ssPruner,
ssPruningOption: ssPruningOption,
}
}
@ -29,8 +29,8 @@ func NewManager(scPruner, ssPruner store.Pruner, scPruningOptions, ssPruningOpti
// NOTE: It can be called outside of the store manually.
func (m *Manager) Prune(version uint64) error {
// Prune the SC.
if m.scPruningOptions != nil {
if prune, pruneTo := m.scPruningOptions.ShouldPrune(version); prune {
if m.scPruningOption != nil {
if prune, pruneTo := m.scPruningOption.ShouldPrune(version); prune {
if err := m.scPruner.Prune(pruneTo); err != nil {
return err
}
@ -38,8 +38,8 @@ func (m *Manager) Prune(version uint64) error {
}
// Prune the SS.
if m.ssPruningOptions != nil {
if prune, pruneTo := m.ssPruningOptions.ShouldPrune(version); prune {
if m.ssPruningOption != nil {
if prune, pruneTo := m.ssPruningOption.ShouldPrune(version); prune {
if err := m.ssPruner.Prune(pruneTo); err != nil {
return err
}

View File

@ -48,15 +48,9 @@ func (s *PruningManagerTestSuite) SetupTest() {
sqliteDB, err := sqlite.New(s.T().TempDir())
s.Require().NoError(err)
s.ss = storage.NewStorageStore(sqliteDB, nopLog)
scPruneOptions := &store.PruneOptions{
KeepRecent: 0,
Interval: 1,
} // prune all
ssPruneOptions := &store.PruneOptions{
KeepRecent: 5,
Interval: 10,
} // prune some
s.manager = NewManager(s.sc, s.ss, scPruneOptions, ssPruneOptions)
scPruningOption := store.NewPruningOptionWithCustom(0, 1) // prune all
ssPruningOption := store.NewPruningOptionWithCustom(5, 10) // prune some
s.manager = NewManager(s.sc, s.ss, scPruningOption, ssPruningOption)
}
func (s *PruningManagerTestSuite) TestPrune() {
@ -94,7 +88,7 @@ func (s *PruningManagerTestSuite) TestPrune() {
s.Require().Eventually(checkSCPrune, 10*time.Second, 1*time.Second)
// check the storage store
_, pruneVersion := s.manager.ssPruningOptions.ShouldPrune(toVersion)
_, pruneVersion := s.manager.ssPruningOption.ShouldPrune(toVersion)
for version := uint64(1); version <= toVersion; version++ {
for _, storeKey := range storeKeys {
for i := 0; i < keyCount; i++ {
@ -112,50 +106,38 @@ func (s *PruningManagerTestSuite) TestPrune() {
}
}
func TestPruneOptions(t *testing.T) {
func TestPruningOption(t *testing.T) {
testCases := []struct {
name string
options *store.PruneOptions
options *store.PruningOption
version uint64
pruning bool
pruneVersion uint64
}{
{
name: "no pruning",
options: &store.PruneOptions{
KeepRecent: 100,
Interval: 0,
},
name: "no pruning",
options: store.NewPruningOptionWithCustom(100, 0),
version: 100,
pruning: false,
pruneVersion: 0,
},
{
name: "prune all",
options: &store.PruneOptions{
KeepRecent: 0,
Interval: 1,
},
name: "prune all",
options: store.NewPruningOptionWithCustom(0, 1),
version: 19,
pruning: true,
pruneVersion: 18,
},
{
name: "prune none",
options: &store.PruneOptions{
KeepRecent: 100,
Interval: 10,
},
name: "prune none",
options: store.NewPruningOptionWithCustom(100, 10),
version: 19,
pruning: false,
pruneVersion: 0,
},
{
name: "prune some",
options: &store.PruneOptions{
KeepRecent: 10,
Interval: 50,
},
name: "prune some",
options: store.NewPruningOptionWithCustom(10, 50),
version: 100,
pruning: true,
pruneVersion: 89,

View File

@ -32,15 +32,15 @@ const (
)
type FactoryOptions struct {
Logger log.Logger
RootDir string
SSType SSType
SCType SCType
SSPruneOptions *store.PruneOptions
SCPruneOptions *store.PruneOptions
IavlConfig *iavl.Config
StoreKeys []string
SCRawDB corestore.KVStoreWithBatch
Logger log.Logger
RootDir string
SSType SSType
SCType SCType
SSPruningOption *store.PruningOption
SCPruningOption *store.PruningOption
IavlConfig *iavl.Config
StoreKeys []string
SCRawDB corestore.KVStoreWithBatch
}
// CreateRootStore is a convenience function to create a root store based on the
@ -101,7 +101,7 @@ func CreateRootStore(opts *FactoryOptions) (store.RootStore, error) {
return nil, err
}
pm := pruning.NewManager(sc, ss, opts.SCPruneOptions, opts.SSPruneOptions)
pm := pruning.NewManager(sc, ss, opts.SCPruningOption, opts.SSPruningOption)
return New(opts.Logger, ss, sc, pm, nil, nil)
}

View File

@ -71,7 +71,7 @@ Iterate/backend_rocksdb_versiondb_opts-10 778ms ± 0%
## Pruning
Pruning is an implementation and responsibility of the underlying SS backend.
Specifically, the `StorageStore` accepts `store.PruneOptions` which defines the
Specifically, the `StorageStore` accepts `store.PruningOption` which defines the
pruning configuration. During `ApplyChangeset`, the `StorageStore` will check if
pruning should occur based on the current height being committed. If so, it will
delegate a `Prune` call on the underlying SS backend, which can be defined specific