From ad52b415f11744c4cbd37400bc5aae6692a913d6 Mon Sep 17 00:00:00 2001 From: cool-developer <51834436+cool-develope@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:59:54 -0400 Subject: [PATCH] chore(store/v2): refactor the pruning option (#20730) --- simapp/v2/app_di.go | 2 +- store/v2/commitment/store_bench_test.go | 129 ++++++++++++++++++++++++ store/v2/commitment/store_test_suite.go | 5 +- store/v2/options.go | 57 +++++++++-- store/v2/pruning/README.md | 4 +- store/v2/pruning/manager.go | 26 ++--- store/v2/pruning/manager_test.go | 46 +++------ store/v2/root/factory.go | 20 ++-- store/v2/storage/README.md | 2 +- 9 files changed, 219 insertions(+), 72 deletions(-) create mode 100644 store/v2/commitment/store_bench_test.go diff --git a/simapp/v2/app_di.go b/simapp/v2/app_di.go index a55d9ec328..bbcf4b66c4 100644 --- a/simapp/v2/app_di.go +++ b/simapp/v2/app_di.go @@ -114,7 +114,7 @@ func NewSimApp( RootDir: DefaultNodeHome, SSType: 0, SCType: 0, - SCPruneOptions: &store.PruneOptions{ + SCPruningOption: &store.PruningOption{ KeepRecent: 0, Interval: 0, }, diff --git a/store/v2/commitment/store_bench_test.go b/store/v2/commitment/store_bench_test.go new file mode 100644 index 0000000000..51b89fbde5 --- /dev/null +++ b/store/v2/commitment/store_bench_test.go @@ -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()) + } +} diff --git a/store/v2/commitment/store_test_suite.go b/store/v2/commitment/store_test_suite.go index 686327d2f6..4178f5944e 100644 --- a/store/v2/commitment/store_test_suite.go +++ b/store/v2/commitment/store_test_suite.go @@ -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) diff --git a/store/v2/options.go b/store/v2/options.go index 40467f76e3..9c20d84942 100644 --- a/store/v2/options.go +++ b/store/v2/options.go @@ -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 } diff --git a/store/v2/pruning/README.md b/store/v2/pruning/README.md index 31a4086eb7..fbf24130c6 100644 --- a/store/v2/pruning/README.md +++ b/store/v2/pruning/README.md @@ -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. diff --git a/store/v2/pruning/manager.go b/store/v2/pruning/manager.go index 5e3c0d0d02..424805bde7 100644 --- a/store/v2/pruning/manager.go +++ b/store/v2/pruning/manager.go @@ -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 } diff --git a/store/v2/pruning/manager_test.go b/store/v2/pruning/manager_test.go index a8010c10bf..d75aea8e51 100644 --- a/store/v2/pruning/manager_test.go +++ b/store/v2/pruning/manager_test.go @@ -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, diff --git a/store/v2/root/factory.go b/store/v2/root/factory.go index 327a033090..efe9cb7ddc 100644 --- a/store/v2/root/factory.go +++ b/store/v2/root/factory.go @@ -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) } diff --git a/store/v2/storage/README.md b/store/v2/storage/README.md index 1cc47b347b..48606d6c19 100644 --- a/store/v2/storage/README.md +++ b/store/v2/storage/README.md @@ -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