diff --git a/Makefile b/Makefile index cf2a83553703..f632ea558e9f 100644 --- a/Makefile +++ b/Makefile @@ -65,8 +65,8 @@ ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=sim \ -X github.com/tendermint/tendermint/version.TMCoreSemVer=$(TMVERSION) ifeq ($(ENABLE_ROCKSDB),true) - BUILD_TAGS += rocksdb_build - test_tags += rocksdb_build + BUILD_TAGS += rocksdb + test_tags += rocksdb else $(warning RocksDB support is disabled; to build and test with RocksDB support, set ENABLE_ROCKSDB=true) endif diff --git a/db/badgerdb/db.go b/db/badgerdb/db.go index 6f0fb1fb3c41..f342743bfc83 100644 --- a/db/badgerdb/db.go +++ b/db/badgerdb/db.go @@ -359,14 +359,34 @@ func (tx *badgerWriter) Set(key, value []byte) error { if err := dbutil.ValidateKv(key, value); err != nil { return err } - return tx.txn.Set(key, value) + err := tx.txn.Set(key, value) + if errors.Is(err, badger.ErrTxnTooBig) { + err = tx.Commit() + if err != nil { + return err + } + newtx := tx.db.ReadWriter().(*badgerWriter) + *tx = *newtx + err = tx.txn.Set(key, value) + } + return err } func (tx *badgerWriter) Delete(key []byte) error { if len(key) == 0 { return db.ErrKeyEmpty } - return tx.txn.Delete(key) + err := tx.txn.Delete(key) + if errors.Is(err, badger.ErrTxnTooBig) { + err = tx.Commit() + if err != nil { + return err + } + newtx := tx.db.ReadWriter().(*badgerWriter) + *tx = *newtx + err = tx.txn.Delete(key) + } + return err } func (tx *badgerWriter) Commit() (err error) { diff --git a/db/rocksdb/batch.go b/db/rocksdb/batch.go index 7e19cae46d68..69e081f169ee 100644 --- a/db/rocksdb/batch.go +++ b/db/rocksdb/batch.go @@ -1,4 +1,4 @@ -//go:build rocksdb_build +//go:build rocksdb package rocksdb diff --git a/db/rocksdb/db.go b/db/rocksdb/db.go index cb312b625f03..227a73b0600a 100644 --- a/db/rocksdb/db.go +++ b/db/rocksdb/db.go @@ -1,4 +1,4 @@ -//go:build rocksdb_build +//go:build rocksdb package rocksdb diff --git a/db/rocksdb/db_test.go b/db/rocksdb/db_test.go index b6268c1ed586..20983dac120f 100644 --- a/db/rocksdb/db_test.go +++ b/db/rocksdb/db_test.go @@ -1,4 +1,4 @@ -//go:build rocksdb_build +//go:build rocksdb package rocksdb diff --git a/db/rocksdb/iterator.go b/db/rocksdb/iterator.go index e760c7507ed5..cf174019e1c1 100644 --- a/db/rocksdb/iterator.go +++ b/db/rocksdb/iterator.go @@ -1,4 +1,4 @@ -//go:build rocksdb_build +//go:build rocksdb package rocksdb diff --git a/go.mod b/go.mod index cc485e143bbb..f425cad57ddb 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 github.com/tendermint/go-amino v0.16.0 github.com/tendermint/tendermint v0.35.2 - github.com/tendermint/tm-db v0.6.6 + github.com/tendermint/tm-db v0.6.7 golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf google.golang.org/grpc v1.45.0 @@ -70,10 +70,12 @@ require ( github.com/cenkalti/backoff/v4 v4.1.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cosmos/gorocksdb v1.2.0 // indirect github.com/cosmos/ledger-go v0.9.2 // indirect github.com/danieljoos/wincred v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect + github.com/dgraph-io/badger/v3 v3.2103.2 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/dustin/go-humanize v1.0.0 // indirect @@ -88,6 +90,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/flatbuffers v2.0.0+incompatible // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect diff --git a/go.sum b/go.sum index 9cc9cdf68906..24affec5722f 100644 --- a/go.sum +++ b/go.sum @@ -277,6 +277,7 @@ github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.3 h1:Ep7FHNViVwwGnwLFEPewZYsyN2C github.com/cosmos/cosmos-sdk/errors v1.0.0-beta.3/go.mod h1:HFea93YKmoMJ/mNKtkSeJZDtyJ4inxBsUK928KONcqo= github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY= github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw= +github.com/cosmos/gorocksdb v1.2.0 h1:d0l3jJG8M4hBouIZq0mDUHZ+zjOx044J3nGRskwTb4Y= github.com/cosmos/gorocksdb v1.2.0/go.mod h1:aaKvKItm514hKfNJpUJXnnOWeBnk2GL4+Qw9NHizILw= github.com/cosmos/iavl v0.18.0 h1:02ur4vnalMR2GuWCFNkuseUcl/BCVmg9tOeHOGiZOkE= github.com/cosmos/iavl v0.18.0/go.mod h1:L0VZHfq0tqMNJvXlslGExaaiZM7eSm+90Vh9QUbp6j4= @@ -313,6 +314,7 @@ github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFM github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= +github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8= github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= @@ -527,6 +529,7 @@ github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB github.com/google/certificate-transparency-go v1.1.1/go.mod h1:FDKqPvSXawb2ecErVRrD+nfy23RCzyl7eqVCEmlT1Zs= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI= github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -1233,6 +1236,8 @@ github.com/tendermint/tendermint v0.35.2 h1:AhPjef5hptLQP5i8vs+8zMCu9mczX5fvBd2F github.com/tendermint/tendermint v0.35.2/go.mod h1:0sVA1nOm5KKaxHar3aIzmMGKH9F/nBMn7T5ruQGZuHg= github.com/tendermint/tm-db v0.6.6 h1:EzhaOfR0bdKyATqcd5PNeyeq8r+V4bRPHBfyFdD9kGM= github.com/tendermint/tm-db v0.6.6/go.mod h1:wP8d49A85B7/erz/r4YbKssKw6ylsO/hKtFk7E1aWZI= +github.com/tendermint/tm-db v0.6.7 h1:fE00Cbl0jayAoqlExN6oyQJ7fR/ZtoVOmvPJ//+shu8= +github.com/tendermint/tm-db v0.6.7/go.mod h1:byQDzFkZV1syXr/ReXS808NxA2xvyuuVgXOJ/088L6I= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= diff --git a/store/bench_test.go b/store/bench_test.go new file mode 100644 index 000000000000..561ae5793865 --- /dev/null +++ b/store/bench_test.go @@ -0,0 +1,378 @@ +package store + +import ( + "encoding/binary" + "fmt" + "math" + "math/rand" + "os" + "path/filepath" + "sort" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/db" + "github.com/cosmos/cosmos-sdk/db/badgerdb" + "github.com/cosmos/cosmos-sdk/db/rocksdb" + storev1 "github.com/cosmos/cosmos-sdk/store/iavl" + "github.com/cosmos/cosmos-sdk/store/types" + storev2types "github.com/cosmos/cosmos-sdk/store/v2" + storev2 "github.com/cosmos/cosmos-sdk/store/v2/multi" + tmdb "github.com/tendermint/tm-db" +) + +var ( + cacheSize = 100 + skey_1 = types.NewKVStoreKey("store1") +) + +func randBytes(numBytes int) []byte { + b := make([]byte, numBytes) + _, _ = rand.Read(b) + return b +} + +type percentages struct { + has int + get int + set int + delete int +} + +type counts struct { + has int + get int + set int + delete int +} + +func generateGradedPercentages() []percentages { + var sampledPercentages []percentages + sampleX := percentages{has: 2, get: 55, set: 40, delete: 3} + sampledPercentages = append(sampledPercentages, sampleX) + for a := 0; a < 100; a += 20 { + for b := 0; b <= 100-a; b += 20 { + for c := 0; c < 100-a-b; c += 20 { + sample := percentages{ + has: a, + get: b, + set: c, + delete: 100 - a - b - c, + } + sampledPercentages = append(sampledPercentages, sample) + } + } + } + return sampledPercentages +} + +func generateExtremePercentages() []percentages { + return []percentages{ + {100, 0, 0, 0}, + {0, 100, 0, 0}, + {0, 0, 100, 0}, + {0, 0, 0, 100}, + } +} + +type benchmark struct { + name string + percentages percentages + dbType tmdb.BackendType + counts counts +} + +func generateBenchmarks(dbBackendTypes []tmdb.BackendType, sampledPercentages []percentages, sampledCounts []counts) []benchmark { + var benchmarks []benchmark + for _, dbType := range dbBackendTypes { + if len(sampledPercentages) > 0 { + for _, p := range sampledPercentages { + name := fmt.Sprintf("r-%s-%d-%d-%d-%d", dbType, p.has, p.get, p.set, p.delete) + benchmarks = append(benchmarks, benchmark{name: name, percentages: p, dbType: dbType, counts: counts{}}) + } + } else if len(sampledCounts) > 0 { + for _, c := range sampledCounts { + name := fmt.Sprintf("d-%s-%d-%d-%d-%d", dbType, c.has, c.get, c.set, c.delete) + benchmarks = append(benchmarks, benchmark{name: name, percentages: percentages{}, dbType: dbType, counts: c}) + } + } + } + return benchmarks +} + +type store interface { + Has(key []byte) bool + Get(key []byte) []byte + Set(key []byte, value []byte) + Delete(key []byte) + Commit() types.CommitID +} + +type storeV2 struct { + *storev2.Store + storev2types.KVStore +} + +func simpleStoreConfig() (storev2.StoreConfig, error) { + opts := storev2.DefaultStoreConfig() + err := opts.RegisterSubstore(skey_1.Name(), types.StoreTypePersistent) + if err != nil { + return storev2.StoreConfig{}, err + } + return opts, nil +} + +func sampleOperation(p percentages) string { + ops := []string{"Has", "Get", "Set", "Delete"} + thresholds := []int{p.has, p.has + p.get, p.has + p.get + p.set} + r := rand.Intn(100) + for i := 0; i < len(thresholds); i++ { + if r < thresholds[i] { + return ops[i] + } + } + return ops[3] +} + +func runRandomizedOperations(b *testing.B, s store, totalOpsCount int, p percentages) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + for j := 0; j < totalOpsCount; j++ { + b.StopTimer() + op := sampleOperation(p) + b.StartTimer() + + switch op { + case "Has": + s.Has(randBytes(12)) + case "Get": + s.Get(randBytes(12)) + case "Set": + s.Set(randBytes(12), randBytes(50)) + case "Delete": + s.Delete(randBytes(12)) + } + if j%200 == 0 || j == totalOpsCount-1 { + s.Commit() + } + } + } +} + +func prepareValues() [][]byte { + var data [][]byte + for i := 0; i < 5000; i++ { + data = append(data, randBytes(50)) + } + return data +} + +func createKey(i int) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(math.Sin(float64(i))*100000)) + return b +} + +func runDeterministicOperations(b *testing.B, s store, values [][]byte, c counts) { + counts := []int{c.has, c.get, c.set, c.delete} + sort.Ints(counts) + step := counts[len(counts)-1] + b.ResetTimer() + + for i := 0; i < b.N; i++ { + idx := i * step + + b.StopTimer() + if idx >= len(values) { + for j := len(values); j < (idx + step); j++ { + values = append(values, randBytes(50)) + } + } + + b.StartTimer() + for j := 0; j < c.set; j++ { + key := createKey(idx + j) + s.Set(key, values[idx+j]) + } + for j := 0; j < c.has; j++ { + key := createKey(idx + j) + s.Has(key) + } + for j := 0; j < c.get; j++ { + key := createKey(idx + j) + s.Get(key) + } + for j := 0; j < c.delete; j++ { + key := createKey(idx + j) + s.Delete(key) + } + s.Commit() + } +} + +func RunRvert(b *testing.B, s store, db interface{}, uncommittedValues [][]byte) { + for i := 0; i < b.N; i++ { + // Key, value pairs changed but not committed + for i, v := range uncommittedValues { + s.Set(createKey(i), v) + } + + b.ResetTimer() + switch t := s.(type) { + case *storev1.Store: + _, err := newStore(1, db, nil, cacheSize) // This shall revert to the last commitID + require.NoError(b, err) + case *storeV2: + require.NoError(b, t.Close()) + _, err := newStore(2, db, nil, 0) // This shall revert to the last commitID + require.NoError(b, err) + default: + panic("not supported store type") + } + } +} + +func newDB(version int, dbName string, dbType tmdb.BackendType, dir string) (db interface{}, err error) { + d := filepath.Join(dir, dbName, dbName+".db") + err = os.MkdirAll(d, os.ModePerm) + if err != nil { + panic(err) + } + + if version == 1 { + db, err = tmdb.NewDB(dbName, dbType, d) + if err != nil { + return nil, err + } + return db, err + } + + if version == 2 { + switch dbType { + case tmdb.RocksDBBackend: + db, err = rocksdb.NewDB(d) + if err != nil { + return nil, err + } + return db, nil + case tmdb.BadgerDBBackend: + db, err = badgerdb.NewDB(d) + if err != nil { + return nil, err + } + return db, nil + default: + return nil, fmt.Errorf("not supported backend for store v2") + } + } + + return nil, fmt.Errorf("not supported version") +} + +func newStore(version int, dbBackend interface{}, cID *types.CommitID, cacheSize int) (store, error) { + if version == 1 { + db, ok := dbBackend.(tmdb.DB) + if !ok { + return nil, fmt.Errorf("unsupported db type") + } + if cID == nil { + cID = &types.CommitID{Version: 0, Hash: nil} + } + s, err := storev1.LoadStore(db, *cID, false, cacheSize) + if err != nil { + return nil, err + } + return s, nil + } + + if version == 2 { + db, ok := dbBackend.(db.DBConnection) + if !ok { + return nil, fmt.Errorf("unsupported db type") + } + simpleStoreConfig, err := simpleStoreConfig() + if err != nil { + return nil, err + } + root, err := storev2.NewStore(db, simpleStoreConfig) + if err != nil { + return nil, err + } + store := root.GetKVStore(storev2types.NewKVStoreKey("store1")) + s := &storeV2{root, store} + return s, nil + } + + return nil, fmt.Errorf("unsupported version") +} + +func prepareStore(b *testing.B, version int, dbType tmdb.BackendType, committedValues [][]byte) (store, interface{}) { + dir := fmt.Sprintf("testdbs/v%d", version) + dbName := fmt.Sprintf("reverttest-%s", dbType) + db, err := newDB(version, dbName, dbType, dir) + require.NoError(b, err) + s, err := newStore(version, db, nil, cacheSize) + require.NoError(b, err) + for i, v := range committedValues { + s.Set(createKey(i), v) + } + _ = s.Commit() + + return s, db +} + +func runSuite(b *testing.B, version int, dbBackendTypes []tmdb.BackendType, dir string) { + // run randomized operations subbenchmarks for various scenarios + sampledPercentages := generateGradedPercentages() + benchmarks := generateBenchmarks(dbBackendTypes, sampledPercentages, nil) + + values := prepareValues() + for _, bm := range benchmarks { + db, err := newDB(version, bm.name, bm.dbType, dir) + require.NoError(b, err) + s, err := newStore(version, db, nil, cacheSize) + require.NoError(b, err) + // add existing data + for i, v := range values { + s.Set(createKey(i), v) + } + b.Run(bm.name, func(sub *testing.B) { + runRandomizedOperations(sub, s, 1000, bm.percentages) + }) + } + + // run deterministic operations subbenchmarks for various scenarios + c := counts{has: 200, get: 5500, set: 4000, delete: 300} + sampledCounts := []counts{c} + benchmarks = generateBenchmarks(dbBackendTypes, nil, sampledCounts) + for _, bm := range benchmarks { + db, err := newDB(version, bm.name, bm.dbType, dir) + require.NoError(b, err) + s, err := newStore(version, db, nil, cacheSize) + require.NoError(b, err) + b.Run(bm.name, func(sub *testing.B) { + runDeterministicOperations(sub, s, values, bm.counts) + }) + } + + // test performance when the store reverting to the last committed version + committedValues := prepareValues() + uncommittedValues := prepareValues() + for _, dbType := range dbBackendTypes { + s, db := prepareStore(b, version, dbType, committedValues) + b.Run(fmt.Sprintf("v%d-%s-revert", version, dbType), func(sub *testing.B) { + RunRvert(sub, s, db, uncommittedValues) + }) + } +} + +func BenchmarkLoadStoreV1(b *testing.B) { + dbBackendTypes := []tmdb.BackendType{tmdb.BadgerDBBackend} + runSuite(b, 1, dbBackendTypes, b.TempDir()) +} + +func BenchmarkLoadStoreV2(b *testing.B) { + dbBackendTypes := []tmdb.BackendType{tmdb.BadgerDBBackend} + runSuite(b, 2, dbBackendTypes, b.TempDir()) +}