From 9cd6b78b55d84f16381e8818e898059565ba5936 Mon Sep 17 00:00:00 2001 From: Roman Akhtariev <34196718+akhtariev@users.noreply.github.com> Date: Wed, 19 Jan 2022 17:16:47 -0800 Subject: [PATCH] Fast Node Iteration (#7) * propagate errors from nodedb and avoid panicking * begin implementing fast node iteration * resolve rebase issue in random tests * fix iteration to deserialize fast node for extracting the correct value * finalzie iteration and let all unit tests pass * add benchmarks * merge GetVersioned and GetVersionedFast * remove fast node deletion from DeleteVersion and DeleteVersionRange and benchmark * fix and unit test iteration on mutable and immutable trees * implement tests for iterator and iterate, begin testing fast iterator * fix and unit test fast iterator --- benchmarks/bench_test.go | 73 +++++-- fast_iterator.go | 132 +++++++++++++ fast_node.go | 15 +- immutable_tree.go | 37 +++- iterator.go | 32 ++- iterator_test.go | 262 ++++++++++++++++++++++++ mutable_tree.go | 144 +++++++++++--- mutable_tree_test.go | 54 ++++- new-iteration-before-iterator.txt | 53 +++++ new-no-fast-node-removal.txt | 65 ++++++ new.txt | 65 ++++++ node.go | 2 +- nodedb.go | 319 +++++++++++++++++++----------- old-iteration.txt | 53 +++++ old.txt | 65 ++++++ repair.go | 7 +- testutils_test.go | 192 +++++++++++++++++- tree_fuzz_test.go | 4 +- tree_random_test.go | 16 +- tree_test.go | 241 +++++++++++++++------- 20 files changed, 1561 insertions(+), 270 deletions(-) create mode 100644 fast_iterator.go create mode 100644 iterator_test.go create mode 100644 new-iteration-before-iterator.txt create mode 100644 new-no-fast-node-removal.txt create mode 100644 new.txt create mode 100644 old-iteration.txt create mode 100644 old.txt diff --git a/benchmarks/bench_test.go b/benchmarks/bench_test.go index 81d07fe4c..e245f2ca0 100644 --- a/benchmarks/bench_test.go +++ b/benchmarks/bench_test.go @@ -72,6 +72,24 @@ func runKnownQueries(b *testing.B, t *iavl.MutableTree, keys [][]byte) { } } +func runIteration(b *testing.B, t *iavl.MutableTree) { + itr := iavl.NewIterator(nil, nil, false, t.ImmutableTree) + defer itr.Close() + + for i := 0; i < b.N && itr.Valid(); i++ { + itr.Next() + } +} + +func runFastIteration(b *testing.B, t *iavl.MutableTree) { + itr := t.ImmutableTree.Iterator(nil, nil, false) + defer itr.Close() + + for i := 0; i < b.N && itr.Valid(); i++ { + itr.Next() + } +} + // func runInsert(b *testing.B, t *iavl.MutableTree, keyLen, dataLen, blockSize int) *iavl.MutableTree { // for i := 1; i <= b.N; i++ { // t.Set(randBytes(keyLen), randBytes(dataLen)) @@ -146,6 +164,26 @@ func runBlock(b *testing.B, t *iavl.MutableTree, keyLen, dataLen, blockSize int, return lastCommit } + +func BenchmarkIteration(b *testing.B) { + fmt.Printf("%s\n", iavl.GetVersionInfo()) + benchmarks := []struct { + length int + }{ + {4}, {16}, {32}, {100}, {1000}, + } + for _, bench := range benchmarks { + bench := bench + name := fmt.Sprintf("random-%d", bench.length) + b.Run(name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + randBytes(bench.length) + } + runtime.GC() + }) + } +} + func BenchmarkRandomBytes(b *testing.B) { fmt.Printf("%s\n", iavl.GetVersionInfo()) benchmarks := []struct { @@ -270,28 +308,37 @@ func runSuite(b *testing.B, d db.DB, initSize, blockSize, keyLen, dataLen int) { runtime.GC() init := memUseMB() - t, keys := prepareTree(b, d, initSize, keyLen, dataLen) + t, _ := prepareTree(b, d, initSize, keyLen, dataLen) used := memUseMB() - init fmt.Printf("Init Tree took %0.2f MB\n", used) b.ResetTimer() - b.Run("query-no-in-tree-guarantee", func(sub *testing.B) { - sub.ReportAllocs() - runQueries(sub, t, keyLen) - }) - b.Run("query-hits", func(sub *testing.B) { - sub.ReportAllocs() - runKnownQueries(sub, t, keys) - }) - b.Run("update", func(sub *testing.B) { + // b.Run("query-no-in-tree-guarantee", func(sub *testing.B) { + // sub.ReportAllocs() + // runQueries(sub, t, keyLen) + // }) + // b.Run("query-hits", func(sub *testing.B) { + // sub.ReportAllocs() + // runKnownQueries(sub, t, keys) + // }) + b.Run("iteration", func(sub *testing.B) { sub.ReportAllocs() - t = runUpdate(sub, t, dataLen, blockSize, keys) + runIteration(sub, t) }) - b.Run("block", func(sub *testing.B) { + + b.Run("fast-iteration", func(sub *testing.B) { sub.ReportAllocs() - t = runBlock(sub, t, keyLen, dataLen, blockSize, keys) + runFastIteration(sub, t) }) + // b.Run("update", func(sub *testing.B) { + // sub.ReportAllocs() + // t = runUpdate(sub, t, dataLen, blockSize, keys) + // }) + // b.Run("block", func(sub *testing.B) { + // sub.ReportAllocs() + // t = runBlock(sub, t, keyLen, dataLen, blockSize, keys) + // }) // both of these edit size of the tree too much // need to run with their own tree diff --git a/fast_iterator.go b/fast_iterator.go new file mode 100644 index 000000000..366068538 --- /dev/null +++ b/fast_iterator.go @@ -0,0 +1,132 @@ +package iavl + +import ( + "errors" + + dbm "github.com/tendermint/tm-db" +) + +// Iterator is a dbm.Iterator for ImmutableTree +type FastIterator struct { + start, end []byte + + valid bool + + ascending bool + + err error + + ndb *nodeDB + + nextFastNode *FastNode + + fastIterator dbm.Iterator +} + +var _ dbm.Iterator = &FastIterator{} + +func NewFastIterator(start, end []byte, ascending bool, ndb *nodeDB) *FastIterator { + iter := &FastIterator{ + start: start, + end: end, + err: nil, + ascending: ascending, + ndb: ndb, + nextFastNode: nil, + fastIterator: nil, + } + iter.Next() + return iter +} + +// Domain implements dbm.Iterator. +func (iter *FastIterator) Domain() ([]byte, []byte) { + if iter.fastIterator == nil { + return iter.start, iter.end + } + + start, end := iter.fastIterator.Domain() + + if start != nil { + start = start[1:] + if len(start) == 0 { + start = nil + } + } + + if end != nil { + end = end[1:] + if len(end) == 0 { + end = nil + } + } + + return start, end +} + +// Valid implements dbm.Iterator. +func (iter *FastIterator) Valid() bool { + if iter.fastIterator == nil || !iter.fastIterator.Valid() { + return false + } + + return iter.valid +} + +// Key implements dbm.Iterator +func (iter *FastIterator) Key() []byte { + if iter.valid { + return iter.nextFastNode.key + } + return nil +} + +// Value implements dbm.Iterator +func (iter *FastIterator) Value() []byte { + if iter.valid { + return iter.nextFastNode.value + } + return nil +} + +// Next implements dbm.Iterator +func (iter *FastIterator) Next() { + if iter.ndb == nil { + iter.err = errors.New("nodeDB is nil") + iter.valid = false + return + } + + if iter.fastIterator == nil { + iter.fastIterator, iter.err = iter.ndb.getFastIterator(iter.start, iter.end, iter.ascending) + iter.valid = true + } else { + iter.fastIterator.Next() + } + + if iter.err == nil { + iter.err = iter.fastIterator.Error() + } + + iter.valid = iter.valid && iter.fastIterator.Valid() + if iter.valid { + iter.nextFastNode, iter.err = DeserializeFastNode(iter.fastIterator.Key()[1:], iter.fastIterator.Value()) + iter.valid = iter.err == nil + } +} + +// Close implements dbm.Iterator +func (iter *FastIterator) Close() error { + iter.fastIterator = nil + iter.valid = false + + if iter.fastIterator != nil { + iter.err = iter.fastIterator.Close() + } + return iter.err +} + +// Error implements dbm.Iterator +func (iter *FastIterator) Error() error { + return iter.err +} diff --git a/fast_node.go b/fast_node.go index 9c7573037..5fa1a2667 100644 --- a/fast_node.go +++ b/fast_node.go @@ -25,13 +25,7 @@ func NewFastNode(key []byte, value []byte, version int64) *FastNode { } // DeserializeFastNode constructs an *FastNode from an encoded byte slice. -func DeserializeFastNode(buf []byte) (*FastNode, error) { - key, n, cause := decodeBytes(buf) - if cause != nil { - return nil, errors.Wrap(cause, "decoding fastnode.key") - } - buf = buf[n:] - +func DeserializeFastNode(key []byte, buf []byte) (*FastNode, error) { ver, n, cause := decodeVarint(buf) if cause != nil { return nil, errors.Wrap(cause, "decoding fastnode.version") @@ -54,7 +48,6 @@ func DeserializeFastNode(buf []byte) (*FastNode, error) { func (node *FastNode) encodedSize() int { n := 1 + - encodeBytesSize(node.key) + encodeVarintSize(node.versionLastUpdatedAt) + encodeBytesSize(node.value) return n @@ -65,11 +58,7 @@ func (node *FastNode) writeBytes(w io.Writer) error { if node == nil { return errors.New("cannot write nil node") } - cause := encodeBytes(w, node.key) - if cause != nil { - return errors.Wrap(cause, "writing key") - } - cause = encodeVarint(w, node.versionLastUpdatedAt) + cause := encodeVarint(w, node.versionLastUpdatedAt) if cause != nil { return errors.Wrap(cause, "writing version last updated at") } diff --git a/immutable_tree.go b/immutable_tree.go index e980277cd..07c507fb3 100644 --- a/immutable_tree.go +++ b/immutable_tree.go @@ -109,6 +109,11 @@ func (t *ImmutableTree) Version() int64 { return t.version } +// IsLatestVersion returns true if curren tree is of the latest version, false otherwise. +func (t *ImmutableTree) IsLatestVersion() bool { + return t.version == t.ndb.getLatestVersion() +} + // Height returns the height of the tree. func (t *ImmutableTree) Height() int8 { if t.root == nil { @@ -211,12 +216,32 @@ func (t *ImmutableTree) GetByIndex(index int64) (key []byte, value []byte) { return t.root.getByIndex(t, index) } -// Iterate iterates over all keys of the tree, in order. The keys and values must not be modified, +// Iterate iterates over all keys of the tree. The keys and values must not be modified, // since they may point to data stored within IAVL. func (t *ImmutableTree) Iterate(fn func(key []byte, value []byte) bool) (stopped bool) { if t.root == nil { return false } + + if t.version == t.ndb.getLatestVersion() { + stopped, err := t.ndb.traverseFastNodesWithStop(func(keyWithPrefix, v []byte) (bool, error) { + key := keyWithPrefix[1:] + fastNode, err := DeserializeFastNode(key, v) + + if err != nil { + return false, err + } + + return fn(fastNode.key, fastNode.value), nil + }) + + if err != nil { + panic(err) + } + + return stopped + } + return t.root.traverse(t, true, func(node *Node) bool { if node.height == 0 { return fn(node.key, node.value) @@ -225,6 +250,16 @@ func (t *ImmutableTree) Iterate(fn func(key []byte, value []byte) bool) (stopped }) } +// Iterator returns an iterator over the immutable tree. +func (t *ImmutableTree) Iterator(start, end []byte, ascending bool) dbm.Iterator { + isFastTraversal := t.IsLatestVersion() + if isFastTraversal { + return NewFastIterator(start, end, ascending, t.ndb) + } else { + return NewIterator(start, end, ascending, t) + } +} + // IterateRange makes a callback for all nodes with key between start and end non-inclusive. // If either are nil, then it is open on that side (nil, nil is the same as Iterate). The keys and // values must not be modified, since they may point to data stored within IAVL. diff --git a/iterator.go b/iterator.go index bd69fcd42..a62f7d485 100644 --- a/iterator.go +++ b/iterator.go @@ -5,6 +5,7 @@ package iavl import ( "bytes" + "errors" dbm "github.com/tendermint/tm-db" ) @@ -157,23 +158,32 @@ type Iterator struct { valid bool + err error + t *traversal } -func (t *ImmutableTree) Iterator(start, end []byte, ascending bool) *Iterator { +var _ dbm.Iterator = &Iterator{} + +// Returns a new iterator over the immutable tree. If the tree is nil, the iterator will be invalid. +func NewIterator(start, end []byte, ascending bool, tree *ImmutableTree) dbm.Iterator { iter := &Iterator{ start: start, end: end, - valid: true, - t: t.root.newTraversal(t, start, end, ascending, false, false), + valid: tree != nil, + t: nil, } - iter.Next() + if iter.valid { + iter.t = tree.root.newTraversal(tree, start, end, ascending, false, false) + iter.Next() + } else { + iter.err = errors.New("Iterator must be created with an immutable tree but it was mil") + } + return iter } -var _ dbm.Iterator = &Iterator{} - // Domain implements dbm.Iterator. func (iter *Iterator) Domain() ([]byte, []byte) { return iter.start, iter.end @@ -219,10 +229,18 @@ func (iter *Iterator) Next() { func (iter *Iterator) Close() error { iter.t = nil iter.valid = false - return nil + return iter.err } // Error implements dbm.Iterator func (iter *Iterator) Error() error { + if iter.err != nil { + return iter.err + } return nil } + +// IsFast returnts true if iterator uses fast strategy +func (iter *Iterator) IsFast() bool { + return false +} diff --git a/iterator_test.go b/iterator_test.go new file mode 100644 index 000000000..2fdadeb60 --- /dev/null +++ b/iterator_test.go @@ -0,0 +1,262 @@ +package iavl + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/require" + dbm "github.com/tendermint/tm-db" +) + +func TestIterator_NewIterator_NilTree_Failure(t *testing.T) { + var start, end []byte = []byte{'a'}, []byte{'c'} + ascending := true + + performTest := func(t *testing.T, itr dbm.Iterator) { + require.NotNil(t, itr) + require.False(t, itr.Valid()) + actualsStart, actualEnd := itr.Domain() + require.Equal(t, start, actualsStart) + require.Equal(t, end, actualEnd) + require.Error(t, itr.Error()) + } + + t.Run("Iterator", func(t *testing.T) { + itr := NewIterator(start, end, ascending, nil) + performTest(t, itr) + }) + + t.Run("Fast Iterator", func(t *testing.T) { + itr := NewFastIterator(start, end, ascending, nil) + performTest(t, itr) + }) +} + +func TestIterator_Empty_Invalid(t *testing.T) { + config := &iteratorTestConfig{ + startByteToSet: 'a', + endByteToSet: 'z', + startIterate: []byte("a"), + endIterate: []byte("a"), + ascending: true, + } + + performTest := func(t *testing.T, itr dbm.Iterator, mirror [][]string) { + require.Equal(t, 0, len(mirror)) + require.False(t, itr.Valid()) + } + + t.Run("Iterator", func(t *testing.T) { + itr, mirror := setupIteratorAndMirror(t, config) + performTest(t, itr, mirror) + }) + + t.Run("Fast Iterator", func(t *testing.T) { + itr, mirror := setupFastIteratorAndMirror(t, config) + performTest(t, itr, mirror) + }) +} + +func TestIterator_Basic_Ranged_Ascending_Success(t *testing.T) { + config := &iteratorTestConfig{ + startByteToSet: 'a', + endByteToSet: 'z', + startIterate: []byte("e"), + endIterate: []byte("w"), + ascending: true, + } + + performTest := func(t *testing.T, itr dbm.Iterator, mirror [][]string) { + actualStart, actualEnd := itr.Domain() + require.Equal(t, config.startIterate, actualStart) + require.Equal(t, config.endIterate, actualEnd) + + require.NoError(t, itr.Error()) + + assertIterator(t, itr, mirror, config.ascending) + } + + t.Run("Iterator", func(t *testing.T) { + itr, mirror := setupIteratorAndMirror(t, config) + require.True(t, itr.Valid()) + performTest(t, itr, mirror) + }) + + t.Run("Fast Iterator", func(t *testing.T) { + itr, mirror := setupFastIteratorAndMirror(t, config) + require.True(t, itr.Valid()) + performTest(t, itr, mirror) + }) +} + +func TestIterator_Basic_Ranged_Descending_Success(t *testing.T) { + config := &iteratorTestConfig{ + startByteToSet: 'a', + endByteToSet: 'z', + startIterate: []byte("e"), + endIterate: []byte("w"), + ascending: false, + } + + performTest := func(t *testing.T, itr dbm.Iterator, mirror [][]string) { + actualStart, actualEnd := itr.Domain() + require.Equal(t, config.startIterate, actualStart) + require.Equal(t, config.endIterate, actualEnd) + + require.NoError(t, itr.Error()) + + assertIterator(t, itr, mirror, config.ascending) + } + + t.Run("Iterator", func(t *testing.T) { + itr, mirror := setupIteratorAndMirror(t, config) + require.True(t, itr.Valid()) + performTest(t, itr, mirror) + }) + + t.Run("Fast Iterator", func(t *testing.T) { + itr, mirror := setupFastIteratorAndMirror(t, config) + require.True(t, itr.Valid()) + performTest(t, itr, mirror) + }) +} + +func TestIterator_Basic_Full_Ascending_Success(t *testing.T) { + config := &iteratorTestConfig{ + startByteToSet: 'a', + endByteToSet: 'z', + startIterate: nil, + endIterate: nil, + ascending: true, + } + + performTest := func(t *testing.T, itr dbm.Iterator, mirror [][]string) { + + require.Equal(t, 25, len(mirror)) + + actualStart, actualEnd := itr.Domain() + require.Equal(t, config.startIterate, actualStart) + require.Equal(t, config.endIterate, actualEnd) + + require.NoError(t, itr.Error()) + + assertIterator(t, itr, mirror, config.ascending) + } + + t.Run("Iterator", func(t *testing.T) { + itr, mirror := setupIteratorAndMirror(t, config) + require.True(t, itr.Valid()) + performTest(t, itr, mirror) + }) + + t.Run("Fast Iterator", func(t *testing.T) { + itr, mirror := setupFastIteratorAndMirror(t, config) + require.True(t, itr.Valid()) + performTest(t, itr, mirror) + }) +} + +func TestIterator_Basic_Full_Descending_Success(t *testing.T) { + config := &iteratorTestConfig{ + startByteToSet: 'a', + endByteToSet: 'z', + startIterate: nil, + endIterate: nil, + ascending: false, + } + + performTest := func(t *testing.T, itr dbm.Iterator, mirror [][]string) { + require.Equal(t, 25, len(mirror)) + + actualStart, actualEnd := itr.Domain() + require.Equal(t, config.startIterate, actualStart) + require.Equal(t, config.endIterate, actualEnd) + + require.NoError(t, itr.Error()) + + assertIterator(t, itr, mirror, config.ascending) + } + + t.Run("Iterator", func(t *testing.T) { + itr, mirror := setupIteratorAndMirror(t, config) + require.True(t, itr.Valid()) + performTest(t, itr, mirror) + }) + + t.Run("Fast Iterator", func(t *testing.T) { + itr, mirror := setupFastIteratorAndMirror(t, config) + require.True(t, itr.Valid()) + performTest(t, itr, mirror) + }) +} + +func TestIterator_WithDelete_Full_Ascending_Success(t *testing.T) { + config := &iteratorTestConfig{ + startByteToSet: 'a', + endByteToSet: 'z', + startIterate: nil, + endIterate: nil, + ascending: false, + } + + tree, mirror := getRandomizedTreeAndMirror(t) + + _, _, err := tree.SaveVersion() + require.NoError(t, err) + + randomizeTreeAndMirror(t, tree, mirror) + + _, _, err = tree.SaveVersion() + require.NoError(t, err) + + err = tree.DeleteVersion(1) + require.NoError(t, err) + + immutableTree, err := tree.GetImmutable(tree.ndb.getLatestVersion()) + require.NoError(t, err) + + // sort mirror for assertion + sortedMirror := make([][]string, 0, len(mirror)) + for k, v := range mirror { + sortedMirror = append(sortedMirror, []string{k, v}) + } + + sort.Slice(sortedMirror, func(i, j int) bool { + return sortedMirror[i][0] < sortedMirror[j][0] + }) + + t.Run("Iterator", func(t *testing.T) { + itr := NewIterator(config.startIterate, config.endIterate, config.ascending, immutableTree) + require.True(t, itr.Valid()) + assertIterator(t, itr, sortedMirror, config.ascending) + }) + + t.Run("Fast Iterator", func(t *testing.T) { + itr := NewFastIterator(config.startIterate, config.endIterate, config.ascending, immutableTree.ndb) + require.True(t, itr.Valid()) + assertIterator(t, itr, sortedMirror, config.ascending) + }) +} + +func setupIteratorAndMirror(t *testing.T, config *iteratorTestConfig) (dbm.Iterator, [][]string) { + tree, err := NewMutableTree(dbm.NewMemDB(), 0) + require.NoError(t, err) + + mirror := setupMirrorForIterator(t, config, tree) + + immutableTree, err := tree.GetImmutable(tree.ndb.getLatestVersion()) + require.NoError(t, err) + + itr := NewIterator(config.startIterate, config.endIterate, config.ascending, immutableTree) + return itr, mirror +} + +func setupFastIteratorAndMirror(t *testing.T, config *iteratorTestConfig) (dbm.Iterator, [][]string) { + tree, err := NewMutableTree(dbm.NewMemDB(), 0) + require.NoError(t, err) + + mirror := setupMirrorForIterator(t,config, tree) + + itr := NewFastIterator(config.startIterate, config.endIterate, config.ascending, tree.ndb) + return itr, mirror +} diff --git a/mutable_tree.go b/mutable_tree.go index 0948ce643..6521534c0 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -109,7 +109,7 @@ func (tree *MutableTree) WorkingHash() []byte { } // String returns a string representation of the tree. -func (tree *MutableTree) String() string { +func (tree *MutableTree) String() (string, error) { return tree.ndb.String() } @@ -156,6 +156,111 @@ func (tree *MutableTree) Import(version int64) (*Importer, error) { return newImporter(tree, version) } +// Iterate iterates over all keys of the tree. The keys and values must not be modified, +// since they may point to data stored within IAVL. +func (t *MutableTree) Iterate(fn func(key []byte, value []byte) bool) (stopped bool) { + if t.root == nil { + return false + } + + if t.version == t.ndb.getLatestVersion() { + // We need to ensure that we iterate over saved and unsaved state in order. + // The strategy is to sort unsaved nodes, the fast node on disk are already sorted. + // Then, we keep a pointer to both the unsaved and saved nodes, and iterate over them in sorted order efficiently. + + unsavedFastNodesToSort := make([]string, 0, len(t.unsavedFastNodeAdditions)) + + for _, fastNode := range t.unsavedFastNodeAdditions { + unsavedFastNodesToSort = append(unsavedFastNodesToSort, string(fastNode.key)) + } + + sort.Strings(unsavedFastNodesToSort) + + itr, err := t.ndb.getFastIterator(nil, nil, true) + if err != nil { + panic(err) + } + + nextUnsavedIdx := 0 + + for itr.Valid() && nextUnsavedIdx < len(unsavedFastNodesToSort) { + diskKeyStr := string(itr.Key()[1:]) + + if t.unsavedFastNodeRemovals[string(diskKeyStr)] != nil { + // If next fast node from disk is to be removed, skip it. + itr.Next() + continue + } + + nextUnsavedKey := unsavedFastNodesToSort[nextUnsavedIdx] + nextUnsavedNode := t.unsavedFastNodeAdditions[nextUnsavedKey] // O(1) + + + + if diskKeyStr >= nextUnsavedKey { + // Unsaved node is next + + if diskKeyStr == nextUnsavedKey { + // Unsaved update prevails over saved copy so we skip the copy from disk + itr.Next() + } + + if fn(nextUnsavedNode.key, nextUnsavedNode.value) { + return true + } + + nextUnsavedIdx++ + } else { + // Disk node is next + + fastNode, err := DeserializeFastNode([]byte(diskKeyStr), itr.Value()) + + if err != nil { + panic(err) + } + + if fn(fastNode.key, fastNode.value) { + return true + } + + itr.Next() + } + } + + // if only nodes on disk are left, we can just iterate + for itr.Valid() { + fastNode, err := DeserializeFastNode(itr.Key()[1:], itr.Value()) + if err != nil { + panic(err) + } + + if fn(fastNode.key, fastNode.value) { + return true + } + itr.Next() + } + + // if only unsaved nodes are left, we can just iterate + for ; nextUnsavedIdx < len(unsavedFastNodesToSort); nextUnsavedIdx++ { + nextUnsavedKey := unsavedFastNodesToSort[nextUnsavedIdx] + nextUnsavedNode := t.unsavedFastNodeAdditions[nextUnsavedKey] + + if fn(nextUnsavedNode.key, nextUnsavedNode.value) { + return true + } + } + + return false + } + + return t.ImmutableTree.Iterate(fn) +} + +// Iterator is not supported and is therefore invalid for MutableTree. Get an ImmutableTree instead for a valid iterator. +func (t *MutableTree) Iterator(start, end []byte, ascending bool) dbm.Iterator { + return NewIterator(start, end, ascending, nil) // this is an invalid iterator +} + func (tree *MutableTree) set(key []byte, value []byte) (orphans []*Node, updated bool) { if value == nil { panic(fmt.Sprintf("Attempt to store nil value at key '%s'", key)) @@ -524,34 +629,25 @@ func (tree *MutableTree) Rollback() { // GetVersioned gets the value and index at the specified key and version. The returned value must not be // modified, since it may point to data stored within IAVL. -func (tree *MutableTree) GetVersioned(key []byte, version int64) ( - index int64, value []byte, -) { +func (tree *MutableTree) GetVersioned(key []byte, version int64) []byte { if tree.VersionExists(version) { + fastNode, _ := tree.ndb.GetFastNode(key) + if fastNode == nil && version == tree.ndb.latestVersion { + return nil + } + + if fastNode != nil && fastNode.versionLastUpdatedAt <= version { + return fastNode.value + } + t, err := tree.GetImmutable(version) if err != nil { - return -1, nil + return nil } - return t.Get(key) - } - return -1, nil -} - -// GetVersionedFast gets the value at the specified key and version. The returned value must not be -// modified, since it may point to data stored within IAVL. GetVersionedFast utilizes a more performant -// strategy for retrieving the value than GetVersioned but falls back to regular strategy if fails. -func (tree *MutableTree) GetVersionedFast(key []byte, version int64) []byte { - fastNode, _ := tree.ndb.GetFastNode(key) - if fastNode == nil && version == tree.ndb.latestVersion || version > tree.version { - return nil - } - - if fastNode != nil && fastNode.versionLastUpdatedAt <= version { - return fastNode.value + _, value := t.Get(key) + return value } - - _, value := tree.GetVersioned(key, version) - return value + return nil } // GetUnsavedFastNodeAdditions returns unsaved FastNodes to add diff --git a/mutable_tree_test.go b/mutable_tree_test.go index 92ac2ba41..e22291e2e 100644 --- a/mutable_tree_test.go +++ b/mutable_tree_test.go @@ -318,9 +318,8 @@ func TestMutableTree_VersionExists(t *testing.T) { require.False(t, tree.VersionExists(3)) } -func checkGetVersioned(t *testing.T, tree *MutableTree, version, index int64, key, value []byte) { - idx, val := tree.GetVersioned(key, version) - require.True(t, idx == index) +func checkGetVersioned(t *testing.T, tree *MutableTree, version int64, key, value []byte) { + val := tree.GetVersioned(key, version) require.True(t, bytes.Equal(val, value)) } @@ -330,17 +329,17 @@ func TestMutableTree_GetVersioned(t *testing.T) { require.True(t, ver == 1) require.NoError(t, err) // check key of unloaded version - checkGetVersioned(t, tree, 1, 1, []byte{1}, []byte("a")) - checkGetVersioned(t, tree, 2, 1, []byte{1}, []byte("b")) - checkGetVersioned(t, tree, 3, -1, []byte{1}, nil) + checkGetVersioned(t, tree, 1, []byte{1}, []byte("a")) + checkGetVersioned(t, tree, 2, []byte{1}, []byte("b")) + checkGetVersioned(t, tree, 3, []byte{1}, nil) tree = prepareTree(t) ver, err = tree.LazyLoadVersion(2) require.True(t, ver == 2) require.NoError(t, err) - checkGetVersioned(t, tree, 1, 1, []byte{1}, []byte("a")) - checkGetVersioned(t, tree, 2, 1, []byte{1}, []byte("b")) - checkGetVersioned(t, tree, 3, -1, []byte{1}, nil) + checkGetVersioned(t, tree, 1, []byte{1}, []byte("a")) + checkGetVersioned(t, tree, 2, []byte{1}, []byte("b")) + checkGetVersioned(t, tree, 3, []byte{1}, nil) } func TestMutableTree_DeleteVersion(t *testing.T) { @@ -627,3 +626,40 @@ func TestMutableTree_FastNodeIntegration(t *testing.T) { require.Equal(t, []byte(testVal2), fastValue) require.Equal(t, []byte(testVal2), regularValue) } + +func TestIterate_MutableTree_Unsaved(t *testing.T) { + tree, mirror := getRandomizedTreeAndMirror(t) + assertMutableMirrorIterate(t, tree, mirror) +} + +func TestIterate_MutableTree_Saved(t *testing.T) { + tree, mirror := getRandomizedTreeAndMirror(t) + + _, _, err := tree.SaveVersion() + require.NoError(t, err) + + assertMutableMirrorIterate(t, tree, mirror) +} + +func TestIterate_MutableTree_Unsaved_NextVersion(t *testing.T) { + tree, mirror := getRandomizedTreeAndMirror(t) + + _, _, err := tree.SaveVersion() + require.NoError(t, err) + + assertMutableMirrorIterate(t, tree, mirror) + + randomizeTreeAndMirror(t, tree, mirror) + + assertMutableMirrorIterate(t, tree, mirror) +} + +func TestIterator_MutableTree_Invalid(t *testing.T) { + tree, err := getTestTree(0) + require.NoError(t, err) + + itr := tree.Iterator([]byte("a"), []byte("b"), true) + + require.NotNil(t, itr) + require.False(t, itr.Valid()) +} diff --git a/new-iteration-before-iterator.txt b/new-iteration-before-iterator.txt new file mode 100644 index 000000000..70b2c6c0a --- /dev/null +++ b/new-iteration-before-iterator.txt @@ -0,0 +1,53 @@ +cd benchmarks && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-60-gbe48191 -X github.com/cosmos/iavl.Commit=be48191cee2ea69e2d0c552428b8cc858a039d23 -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=RandomBytes . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-60-gbe48191 -X github.com/cosmos/iavl.Commit=be48191cee2ea69e2d0c552428b8cc858a039d23 -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=Small . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-60-gbe48191 -X github.com/cosmos/iavl.Commit=be48191cee2ea69e2d0c552428b8cc858a039d23 -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=Medium . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-60-gbe48191 -X github.com/cosmos/iavl.Commit=be48191cee2ea69e2d0c552428b8cc858a039d23 -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=BenchmarkMemKeySizes . +iavl: 0.17.2-60-gbe48191 +git commit: be48191cee2ea69e2d0c552428b8cc858a039d23 +git branch: roman/fast-node-iteration +go version go1.17.6 linux/amd64 + +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkRandomBytes/random-4-8 24876063 49.10 ns/op +BenchmarkRandomBytes/random-16-8 15777246 87.06 ns/op +BenchmarkRandomBytes/random-32-8 11110020 101.7 ns/op +BenchmarkRandomBytes/random-100-8 5163843 273.9 ns/op +BenchmarkRandomBytes/random-1000-8 690846 1505 ns/op +PASS +ok github.com/cosmos/iavl/benchmarks 8.712s +iavl: 0.17.2-60-gbe48191 +git commit: be48191cee2ea69e2d0c552428b8cc858a039d23 +git branch: roman/fast-node-iteration +go version go1.17.6 linux/amd64 + +Init Tree took 1.10 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkSmall/memdb-1000-100-4-10/iteration-8 1000000000 0.002510 ns/op 0 B/op 0 allocs/op +Init Tree took 0.72 MB +BenchmarkSmall/goleveldb-1000-100-4-10/iteration-8 1000000000 0.004526 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 0.204s +iavl: 0.17.2-60-gbe48191 +git commit: be48191cee2ea69e2d0c552428b8cc858a039d23 +git branch: roman/fast-node-iteration +go version go1.17.6 linux/amd64 + +Init Tree took 114.27 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkMedium/memdb-100000-100-16-40/iteration-8 1000000000 0.7426 ns/op 0 B/op 0 allocs/op +Init Tree took 66.92 MB +BenchmarkMedium/goleveldb-100000-100-16-40/iteration-8 90834 17064 ns/op 2004 B/op 34 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 25.720s +PASS +ok github.com/cosmos/iavl/benchmarks 0.015s diff --git a/new-no-fast-node-removal.txt b/new-no-fast-node-removal.txt new file mode 100644 index 000000000..d3bb2c519 --- /dev/null +++ b/new-no-fast-node-removal.txt @@ -0,0 +1,65 @@ +cd benchmarks && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-59-gac8956d -X github.com/cosmos/iavl.Commit=ac8956d6b7a756d253756056db9bd22df706e0de -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=RandomBytes . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-59-gac8956d -X github.com/cosmos/iavl.Commit=ac8956d6b7a756d253756056db9bd22df706e0de -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=Small . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-59-gac8956d -X github.com/cosmos/iavl.Commit=ac8956d6b7a756d253756056db9bd22df706e0de -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=Medium . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-59-gac8956d -X github.com/cosmos/iavl.Commit=ac8956d6b7a756d253756056db9bd22df706e0de -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=BenchmarkMemKeySizes . +iavl: 0.17.2-59-gac8956d +git commit: ac8956d6b7a756d253756056db9bd22df706e0de +git branch: roman/fast-node-iteration +go version go1.17.6 linux/amd64 + +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkRandomBytes/random-4-8 20889218 48.64 ns/op +BenchmarkRandomBytes/random-16-8 16159047 71.05 ns/op +BenchmarkRandomBytes/random-32-8 12536655 96.02 ns/op +BenchmarkRandomBytes/random-100-8 6227706 190.1 ns/op +BenchmarkRandomBytes/random-1000-8 780081 1445 ns/op +PASS +ok github.com/cosmos/iavl/benchmarks 6.165s +iavl: 0.17.2-59-gac8956d +git commit: ac8956d6b7a756d253756056db9bd22df706e0de +git branch: roman/fast-node-iteration +go version go1.17.6 linux/amd64 + +Init Tree took 1.10 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkSmall/memdb-1000-100-4-10/query-no-in-tree-guarantee-8 1912308 636.2 ns/op 88 B/op 4 allocs/op +BenchmarkSmall/memdb-1000-100-4-10/query-hits-8 9573507 114.0 ns/op 0 B/op 0 allocs/op +BenchmarkSmall/memdb-1000-100-4-10/update-8 25666 47808 ns/op 12065 B/op 177 allocs/op +BenchmarkSmall/memdb-1000-100-4-10/block-8 164 10243846 ns/op 2247888 B/op 34921 allocs/op +Init Tree took 0.72 MB +BenchmarkSmall/goleveldb-1000-100-4-10/query-no-in-tree-guarantee-8 906481 1244 ns/op 144 B/op 7 allocs/op +BenchmarkSmall/goleveldb-1000-100-4-10/query-hits-8 8456372 124.3 ns/op 0 B/op 0 allocs/op +BenchmarkSmall/goleveldb-1000-100-4-10/update-8 17923 75469 ns/op 21829 B/op 176 allocs/op +BenchmarkSmall/goleveldb-1000-100-4-10/block-8 100 12146897 ns/op 3129029 B/op 30991 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 13.042s +iavl: 0.17.2-59-gac8956d +git commit: ac8956d6b7a756d253756056db9bd22df706e0de +git branch: roman/fast-node-iteration +go version go1.17.6 linux/amd64 + +Init Tree took 114.28 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkMedium/memdb-100000-100-16-40/query-no-in-tree-guarantee-8 650342 1974 ns/op 112 B/op 4 allocs/op +BenchmarkMedium/memdb-100000-100-16-40/query-hits-8 797115 2269 ns/op 25 B/op 0 allocs/op +BenchmarkMedium/memdb-100000-100-16-40/update-8 5407 254901 ns/op 26996 B/op 337 allocs/op +BenchmarkMedium/memdb-100000-100-16-40/block-8 45 26687069 ns/op 2972550 B/op 36911 allocs/op +Init Tree took 66.84 MB +BenchmarkMedium/goleveldb-100000-100-16-40/query-no-in-tree-guarantee-8 208466 5578 ns/op 814 B/op 16 allocs/op +BenchmarkMedium/goleveldb-100000-100-16-40/query-hits-8 738081 1407 ns/op 69 B/op 1 allocs/op +BenchmarkMedium/goleveldb-100000-100-16-40/update-8 7971 317110 ns/op 44099 B/op 436 allocs/op +BenchmarkMedium/goleveldb-100000-100-16-40/block-8 20 53302265 ns/op 6795066 B/op 70841 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 26.772s +PASS +ok github.com/cosmos/iavl/benchmarks 0.014s diff --git a/new.txt b/new.txt new file mode 100644 index 000000000..d4bfa89e7 --- /dev/null +++ b/new.txt @@ -0,0 +1,65 @@ +cd benchmarks && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-57-g4c94f48 -X github.com/cosmos/iavl.Commit=4c94f489e26eceff57184b82689b315504788b72 -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=RandomBytes . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-57-g4c94f48 -X github.com/cosmos/iavl.Commit=4c94f489e26eceff57184b82689b315504788b72 -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=Small . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-57-g4c94f48 -X github.com/cosmos/iavl.Commit=4c94f489e26eceff57184b82689b315504788b72 -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=Medium . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-57-g4c94f48 -X github.com/cosmos/iavl.Commit=4c94f489e26eceff57184b82689b315504788b72 -X github.com/cosmos/iavl.Branch=roman/fast-node-iteration" -bench=BenchmarkMemKeySizes . +iavl: 0.17.2-57-g4c94f48 +git commit: 4c94f489e26eceff57184b82689b315504788b72 +git branch: roman/fast-node-iteration +go version go1.17.6 linux/amd64 + +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkRandomBytes/random-4-8 23002562 47.68 ns/op +BenchmarkRandomBytes/random-16-8 15802635 77.07 ns/op +BenchmarkRandomBytes/random-32-8 11370483 124.9 ns/op +BenchmarkRandomBytes/random-100-8 5276336 224.3 ns/op +BenchmarkRandomBytes/random-1000-8 738265 1548 ns/op +PASS +ok github.com/cosmos/iavl/benchmarks 6.588s +iavl: 0.17.2-57-g4c94f48 +git commit: 4c94f489e26eceff57184b82689b315504788b72 +git branch: roman/fast-node-iteration +go version go1.17.6 linux/amd64 + +Init Tree took 1.10 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkSmall/memdb-1000-100-4-10/query-no-in-tree-guarantee-8 1668390 681.2 ns/op 88 B/op 4 allocs/op +BenchmarkSmall/memdb-1000-100-4-10/query-hits-8 8594239 132.6 ns/op 0 B/op 0 allocs/op +BenchmarkSmall/memdb-1000-100-4-10/update-8 22060 57592 ns/op 13166 B/op 203 allocs/op +BenchmarkSmall/memdb-1000-100-4-10/block-8 100 12803428 ns/op 2974467 B/op 47489 allocs/op +Init Tree took 0.72 MB +BenchmarkSmall/goleveldb-1000-100-4-10/query-no-in-tree-guarantee-8 942721 1296 ns/op 144 B/op 7 allocs/op +BenchmarkSmall/goleveldb-1000-100-4-10/query-hits-8 8306544 125.2 ns/op 0 B/op 0 allocs/op +BenchmarkSmall/goleveldb-1000-100-4-10/update-8 14299 101349 ns/op 23083 B/op 222 allocs/op +BenchmarkSmall/goleveldb-1000-100-4-10/block-8 97 23238174 ns/op 4825979 B/op 55900 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 14.187s +iavl: 0.17.2-57-g4c94f48 +git commit: 4c94f489e26eceff57184b82689b315504788b72 +git branch: roman/fast-node-iteration +go version go1.17.6 linux/amd64 + +Init Tree took 114.27 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkMedium/memdb-100000-100-16-40/query-no-in-tree-guarantee-8 625338 1880 ns/op 112 B/op 4 allocs/op +BenchmarkMedium/memdb-100000-100-16-40/query-hits-8 581775 1731 ns/op 55 B/op 1 allocs/op +BenchmarkMedium/memdb-100000-100-16-40/update-8 9762 2099140 ns/op 600259 B/op 7516 allocs/op +BenchmarkMedium/memdb-100000-100-16-40/block-8 4 275097500 ns/op 73751912 B/op 923797 allocs/op +Init Tree took 66.91 MB +BenchmarkMedium/goleveldb-100000-100-16-40/query-no-in-tree-guarantee-8 249350 4766 ns/op 814 B/op 16 allocs/op +BenchmarkMedium/goleveldb-100000-100-16-40/query-hits-8 504381 2098 ns/op 138 B/op 2 allocs/op +BenchmarkMedium/goleveldb-100000-100-16-40/update-8 7176 7638438 ns/op 1500841 B/op 8277 allocs/op +BenchmarkMedium/goleveldb-100000-100-16-40/block-8 1 1024237900 ns/op 204434192 B/op 1129885 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 94.516s +PASS +ok github.com/cosmos/iavl/benchmarks 0.015s diff --git a/node.go b/node.go index da562e18a..ba6de5c93 100644 --- a/node.go +++ b/node.go @@ -457,7 +457,7 @@ func (node *Node) traversePost(t *ImmutableTree, ascending bool, cb func(*Node) func (node *Node) traverseInRange(tree *ImmutableTree, start, end []byte, ascending bool, inclusive bool, post bool, cb func(*Node) bool) bool { stop := false - t := node.newTraversal(tree, start, end, ascending, inclusive, post) + t := node.newTraversal(tree, start, end, ascending, inclusive, post) // TODO: implement fast traversal for node2 := t.next(); node2 != nil; node2 = t.next() { stop = cb(node2) if stop { diff --git a/nodedb.go b/nodedb.go index 9ec3f752f..b7fa3ab8d 100644 --- a/nodedb.go +++ b/nodedb.go @@ -145,12 +145,11 @@ func (ndb *nodeDB) GetFastNode(key []byte) (*FastNode, error) { return nil, nil } - fastNode, err := DeserializeFastNode(buf) + fastNode, err := DeserializeFastNode(key, buf) if err != nil { return nil, fmt.Errorf("error reading FastNode. bytes: %x, error: %w", buf, err) } - fastNode.key = key ndb.cacheFastNode(fastNode) return fastNode, nil } @@ -285,37 +284,16 @@ func (ndb *nodeDB) DeleteVersion(version int64, checkLatestVersion bool) error { return errors.Errorf("unable to delete version %v, it has %v active readers", version, ndb.versionReaders[version]) } - ndb.deleteOrphans(version) - ndb.deleteRoot(version, checkLatestVersion) - ndb.uncacheFastNodesWithVersion(version) - - ndb.traverseFastNodes(func(key, v []byte) { - fastNode, err := DeserializeFastNode(v) - - if err != nil { - panic(err) - } - - if checkLatestVersion && version == ndb.latestVersion { - // Latest version must not be deleted - return - } + err := ndb.deleteOrphans(version) + if err != nil { + return err + } - if fastNode.versionLastUpdatedAt == version { - ndb.uncacheFastNode(key) - if version + 1 <= ndb.latestVersion { - fastNode.versionLastUpdatedAt = version + 1 - if err = ndb.saveFastNodeUnlocked(fastNode); err != nil { - panic(err) - } - } else { - if err = ndb.batch.Delete(key); err != nil { - panic(err) - } - } - } - }) - return nil + err = ndb.deleteRoot(version, checkLatestVersion) + if err != nil { + return err + } + return err } // DeleteVersionsFrom permanently deletes all tree versions from the given version upwards. @@ -348,48 +326,65 @@ func (ndb *nodeDB) DeleteVersionsFrom(version int64) error { // Next, delete orphans: // - Delete orphan entries *and referred nodes* with fromVersion >= version // - Delete orphan entries with toVersion >= version-1 (since orphans at latest are not orphans) - ndb.traverseOrphans(func(key, hash []byte) { + err = ndb.traverseOrphans(func(key, hash []byte) error { var fromVersion, toVersion int64 orphanKeyFormat.Scan(key, &toVersion, &fromVersion) if fromVersion >= version { if err = ndb.batch.Delete(key); err != nil { - panic(err) + return err } if err = ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { - panic(err) + return err } ndb.uncacheNode(hash) } else if toVersion >= version-1 { if err := ndb.batch.Delete(key); err != nil { - panic(err) + return err } } + return nil }) + if err != nil { + return err + } + // Delete the version root entries - ndb.traverseRange(rootKeyFormat.Key(version), rootKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) { + err = ndb.traverseRange(rootKeyFormat.Key(version), rootKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) error { if err := ndb.batch.Delete(k); err != nil { - panic(err) + return err } + return nil }) + if err != nil { + return err + } + // Delete fast node entries - ndb.traverseFastNodes(func(key, v []byte) { - fastNode, err := DeserializeFastNode(v) + err = ndb.traverseFastNodes(func(keyWithPrefix, v []byte) error { + key := keyWithPrefix[1:] + fastNode, err := DeserializeFastNode(key, v) if err != nil { - panic(err) + return err } if version <= fastNode.versionLastUpdatedAt { - if err = ndb.batch.Delete(key); err != nil { - panic(err) + if err = ndb.batch.Delete(keyWithPrefix); err != nil { + debug("%v\n", err) + return err } ndb.uncacheFastNode(key) } + return nil }) + if err != nil { + return err + } + for curVersion := version; curVersion <= ndb.latestVersion; curVersion++ { ndb.uncacheFastNodesWithVersion(curVersion) } @@ -425,11 +420,12 @@ func (ndb *nodeDB) DeleteVersionsRange(fromVersion, toVersion int64) error { // If the predecessor is earlier than the beginning of the lifetime, we can delete the orphan. // Otherwise, we shorten its lifetime, by moving its endpoint to the predecessor version. for version := fromVersion; version < toVersion; version++ { - ndb.traverseOrphansVersion(version, func(key, hash []byte) { + err := ndb.traverseOrphansVersion(version, func(key, hash []byte) error { var from, to int64 orphanKeyFormat.Scan(key, &to, &from) if err := ndb.batch.Delete(key); err != nil { - panic(err) + debug("%v\n", err) + return err } if from > predecessor { if err := ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { @@ -439,41 +435,26 @@ func (ndb *nodeDB) DeleteVersionsRange(fromVersion, toVersion int64) error { } else { ndb.saveOrphan(hash, from, predecessor) } + return nil }) + if err != nil { + return err + } ndb.uncacheFastNodesWithVersion(version) } // Delete the version root entries - ndb.traverseRange(rootKeyFormat.Key(fromVersion), rootKeyFormat.Key(toVersion), func(k, v []byte) { + err := ndb.traverseRange(rootKeyFormat.Key(fromVersion), rootKeyFormat.Key(toVersion), func(k, v []byte) error { if err := ndb.batch.Delete(k); err != nil { - panic(err) - } - }) - - // Delete fast node entries - ndb.traverseFastNodes(func(key, v []byte) { - fastNode, err := DeserializeFastNode(v) - - if err != nil { - panic(err) - } - - if fromVersion <= fastNode.versionLastUpdatedAt && fastNode.versionLastUpdatedAt < toVersion { - ndb.uncacheFastNode(key) - if toVersion <= ndb.latestVersion { - fastNode.versionLastUpdatedAt = toVersion - if err = ndb.saveFastNodeUnlocked(fastNode); err != nil { - panic(err) - } - } else { - if err = ndb.batch.Delete(key); err != nil { - panic(err) - } - } + return err } + return nil }) + if err != nil { + return err + } return nil } @@ -542,13 +523,13 @@ func (ndb *nodeDB) saveOrphan(hash []byte, fromVersion, toVersion int64) { // deleteOrphans deletes orphaned nodes from disk, and the associated orphan // entries. -func (ndb *nodeDB) deleteOrphans(version int64) { +func (ndb *nodeDB) deleteOrphans(version int64) error { // Will be zero if there is no previous version. predecessor := ndb.getPreviousVersion(version) // Traverse orphans with a lifetime ending at the version specified. // TODO optimize. - ndb.traverseOrphansVersion(version, func(key, hash []byte) { + return ndb.traverseOrphansVersion(version, func(key, hash []byte) error { var fromVersion, toVersion int64 // See comment on `orphanKeyFmt`. Note that here, `version` and @@ -557,7 +538,7 @@ func (ndb *nodeDB) deleteOrphans(version int64) { // Delete orphan key and reverse-lookup key. if err := ndb.batch.Delete(key); err != nil { - panic(err) + return err } // If there is no predecessor, or the predecessor is earlier than the @@ -568,13 +549,14 @@ func (ndb *nodeDB) deleteOrphans(version int64) { if predecessor < fromVersion || fromVersion == toVersion { debug("DELETE predecessor:%v fromVersion:%v toVersion:%v %X\n", predecessor, fromVersion, toVersion, hash) if err := ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { - panic(err) + return err } ndb.uncacheNode(hash) } else { debug("MOVE predecessor:%v fromVersion:%v toVersion:%v %X\n", predecessor, fromVersion, toVersion, hash) ndb.saveOrphan(hash, fromVersion, predecessor) } + return nil }) } @@ -636,61 +618,121 @@ func (ndb *nodeDB) getPreviousVersion(version int64) int64 { } // deleteRoot deletes the root entry from disk, but not the node it points to. -func (ndb *nodeDB) deleteRoot(version int64, checkLatestVersion bool) { +func (ndb *nodeDB) deleteRoot(version int64, checkLatestVersion bool) error { if checkLatestVersion && version == ndb.getLatestVersion() { - panic("Tried to delete latest version") + return errors.New("Tried to delete latest version") } if err := ndb.batch.Delete(ndb.rootKey(version)); err != nil { - panic(err) + return err } + return nil +} + +// Traverse orphans and return error if any, nil otherwise +func (ndb *nodeDB) traverseOrphans(fn func(keyWithPrefix, v []byte) error) error { + return ndb.traversePrefix(orphanKeyFormat.Key(), fn) } -func (ndb *nodeDB) traverseOrphans(fn func(k, v []byte)) { - ndb.traversePrefix(orphanKeyFormat.Key(), fn) +// Traverse fast nodes and return error if any, nil otherwise +func (ndb *nodeDB) traverseFastNodes(fn func(k, v []byte) error) error { + return ndb.traversePrefix(fastKeyFormat.Key(), fn) } -func (ndb *nodeDB) traverseFastNodes(fn func(k, v []byte)) { - ndb.traversePrefix(fastKeyFormat.Key(), fn) +// Traverse fast nodes. Returns a flag indicating if traversal was stopped by the callback and error if any, nil otherwise +func (ndb *nodeDB) traverseFastNodesWithStop(fn func(keyWithPrefix, v []byte) (bool, error)) (bool, error) { + return ndb.traversePrefixWithStop(fastKeyFormat.Key(), fn) } -// Traverse orphans ending at a certain version. -func (ndb *nodeDB) traverseOrphansVersion(version int64, fn func(k, v []byte)) { - ndb.traversePrefix(orphanKeyFormat.Key(version), fn) +// Traverse orphans ending at a certain version. return error if any, nil otherwise +func (ndb *nodeDB) traverseOrphansVersion(version int64, fn func(k, v []byte) error) error { + return ndb.traversePrefix(orphanKeyFormat.Key(version), fn) } -// Traverse all keys. -func (ndb *nodeDB) traverse(fn func(key, value []byte)) { - ndb.traverseRange(nil, nil, fn) +// Traverse all keys and return error if any, nil otherwise +func (ndb *nodeDB) traverse(fn func(key, value []byte) error) error { + return ndb.traverseRange(nil, nil, fn) } -// Traverse all keys between a given range (excluding end). -func (ndb *nodeDB) traverseRange(start []byte, end []byte, fn func(k, v []byte)) { +// Traverse all keys between a given range (excluding end) and return error if any, nil otherwise +func (ndb *nodeDB) traverseRange(start []byte, end []byte, fn func(k, v []byte) error) error { itr, err := ndb.db.Iterator(start, end) if err != nil { - panic(err) + return err } defer itr.Close() for ; itr.Valid(); itr.Next() { - fn(itr.Key(), itr.Value()) + if err := fn(itr.Key(), itr.Value()); err != nil { + return err + } } if err := itr.Error(); err != nil { - panic(err) + return err } + + return nil } -// Traverse all keys with a certain prefix. -func (ndb *nodeDB) traversePrefix(prefix []byte, fn func(k, v []byte)) { +// Traverse all keys with a certain prefix. Return error if any, nil otherwise +func (ndb *nodeDB) traversePrefix(prefix []byte, fn func(k, v []byte) error) error { itr, err := dbm.IteratePrefix(ndb.db, prefix) if err != nil { - panic(err) + return err } defer itr.Close() for ; itr.Valid(); itr.Next() { - fn(itr.Key(), itr.Value()) + if err := fn(itr.Key(), itr.Value()); err != nil { + return err + } } + + return nil +} + +// traversePrefixWithStop traverse all keys with a certain prefix. Returns a flag indicating if +// traversal was stopped by the callback and error if any, nil otherwise +func (ndb *nodeDB) traversePrefixWithStop(prefix []byte, fn func(k, v []byte) (bool, error)) (bool, error) { + itr, err := dbm.IteratePrefix(ndb.db, prefix) + if err != nil { + return false, err + } + defer itr.Close() + + for ; itr.Valid(); itr.Next() { + if stopped, err := fn(itr.Key(), itr.Value()); err != nil { + return stopped, err + } else if stopped { + return true, nil + } + } + + return false, nil +} + +// Get iterator for fast prefix and error, if any +func (ndb *nodeDB) getFastIterator(start, end []byte, ascending bool) (dbm.Iterator, error) { + var startFormatted, endFormatted []byte = nil, nil + + if start != nil { + startFormatted = fastKeyFormat.KeyBytes(start) + } else { + startFormatted = fastKeyFormat.Key() + } + + if end != nil { + endFormatted = fastKeyFormat.KeyBytes(end) + } else { + endFormatted = fastKeyFormat.Key() + endFormatted[0]++ + } + + if ascending { + return ndb.db.Iterator(startFormatted, endFormatted) + } + + return ndb.db.ReverseIterator(startFormatted, endFormatted) } func (ndb *nodeDB) uncacheNode(hash []byte) { @@ -774,10 +816,11 @@ func (ndb *nodeDB) getRoot(version int64) ([]byte, error) { func (ndb *nodeDB) getRoots() (map[int64][]byte, error) { roots := map[int64][]byte{} - ndb.traversePrefix(rootKeyFormat.Key(), func(k, v []byte) { + ndb.traversePrefix(rootKeyFormat.Key(), func(k, v []byte) error { var version int64 rootKeyFormat.Scan(k, &version) roots[version] = v + return nil }) return roots, nil } @@ -831,33 +874,51 @@ func (ndb *nodeDB) decrVersionReaders(version int64) { // Utility and test functions -func (ndb *nodeDB) leafNodes() []*Node { +func (ndb *nodeDB) leafNodes() ([]*Node, error) { leaves := []*Node{} - ndb.traverseNodes(func(hash []byte, node *Node) { + err := ndb.traverseNodes(func(hash []byte, node *Node) error { if node.isLeaf() { leaves = append(leaves, node) } + return nil }) - return leaves + + if err != nil { + return nil, err + } + + return leaves, nil } -func (ndb *nodeDB) nodes() []*Node { +func (ndb *nodeDB) nodes() ([]*Node, error) { nodes := []*Node{} - ndb.traverseNodes(func(hash []byte, node *Node) { + err := ndb.traverseNodes(func(hash []byte, node *Node) error { nodes = append(nodes, node) + return nil }) - return nodes + + if err != nil { + return nil, err + } + + return nodes, nil } -func (ndb *nodeDB) orphans() [][]byte { +func (ndb *nodeDB) orphans() ([][]byte, error) { orphans := [][]byte{} - ndb.traverseOrphans(func(k, v []byte) { + err := ndb.traverseOrphans(func(k, v []byte) error { orphans = append(orphans, v) + return nil }) - return orphans + + if err != nil { + return nil, err + } + + return orphans, nil } func (ndb *nodeDB) roots() map[int64][]byte { @@ -870,48 +931,68 @@ func (ndb *nodeDB) roots() map[int64][]byte { // mutations are not always synchronous. func (ndb *nodeDB) size() int { size := 0 - ndb.traverse(func(k, v []byte) { + err := ndb.traverse(func(k, v []byte) error { size++ + return nil }) + + if err != nil { + return -1 + } return size } -func (ndb *nodeDB) traverseNodes(fn func(hash []byte, node *Node)) { +func (ndb *nodeDB) traverseNodes(fn func(hash []byte, node *Node) error) error { nodes := []*Node{} - ndb.traversePrefix(nodeKeyFormat.Key(), func(key, value []byte) { + err := ndb.traversePrefix(nodeKeyFormat.Key(), func(key, value []byte) error { node, err := MakeNode(value) if err != nil { - panic(fmt.Sprintf("Couldn't decode node from database: %v", err)) + return err } nodeKeyFormat.Scan(key, &node.hash) nodes = append(nodes, node) + return nil }) + if err != nil { + return err + } + sort.Slice(nodes, func(i, j int) bool { return bytes.Compare(nodes[i].key, nodes[j].key) < 0 }) for _, n := range nodes { - fn(n.hash, n) + if err := fn(n.hash, n); err != nil { + return err + } } + return nil } -func (ndb *nodeDB) String() string { +func (ndb *nodeDB) String() (string, error) { var str string index := 0 - ndb.traversePrefix(rootKeyFormat.Key(), func(key, value []byte) { + ndb.traversePrefix(rootKeyFormat.Key(), func(key, value []byte) error { str += fmt.Sprintf("%s: %x\n", string(key), value) + return nil }) str += "\n" - ndb.traverseOrphans(func(key, value []byte) { + err := ndb.traverseOrphans(func(key, value []byte) error { str += fmt.Sprintf("%s: %x\n", string(key), value) + return nil }) + + if err != nil { + return "", err + } + str += "\n" - ndb.traverseNodes(func(hash []byte, node *Node) { + err = ndb.traverseNodes(func(hash []byte, node *Node) error { switch { case len(hash) == 0: str += "\n" @@ -925,6 +1006,12 @@ func (ndb *nodeDB) String() string { nodeKeyFormat.Prefix(), hash, node.key, node.value, node.height, node.version) } index++ + return nil }) - return "-" + "\n" + str + "-" + + if err != nil { + return "", err + } + + return "-" + "\n" + str + "-", nil } diff --git a/old-iteration.txt b/old-iteration.txt new file mode 100644 index 000000000..9ea634d43 --- /dev/null +++ b/old-iteration.txt @@ -0,0 +1,53 @@ +cd benchmarks && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-14-g0944259 -X github.com/cosmos/iavl.Commit=094425990290c396365553ff8d1c078b9a8a2607 -X github.com/cosmos/iavl.Branch=dev/iavl_data_locality" -bench=RandomBytes . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-14-g0944259 -X github.com/cosmos/iavl.Commit=094425990290c396365553ff8d1c078b9a8a2607 -X github.com/cosmos/iavl.Branch=dev/iavl_data_locality" -bench=Small . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-14-g0944259 -X github.com/cosmos/iavl.Commit=094425990290c396365553ff8d1c078b9a8a2607 -X github.com/cosmos/iavl.Branch=dev/iavl_data_locality" -bench=Medium . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-14-g0944259 -X github.com/cosmos/iavl.Commit=094425990290c396365553ff8d1c078b9a8a2607 -X github.com/cosmos/iavl.Branch=dev/iavl_data_locality" -bench=BenchmarkMemKeySizes . +iavl: 0.17.2-14-g0944259 +git commit: 094425990290c396365553ff8d1c078b9a8a2607 +git branch: dev/iavl_data_locality +go version go1.17.6 linux/amd64 + +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkRandomBytes/random-4-8 24569575 43.69 ns/op +BenchmarkRandomBytes/random-16-8 16734674 71.58 ns/op +BenchmarkRandomBytes/random-32-8 13346115 93.98 ns/op +BenchmarkRandomBytes/random-100-8 5827652 209.3 ns/op +BenchmarkRandomBytes/random-1000-8 850201 1401 ns/op +PASS +ok github.com/cosmos/iavl/benchmarks 7.406s +iavl: 0.17.2-14-g0944259 +git commit: 094425990290c396365553ff8d1c078b9a8a2607 +git branch: dev/iavl_data_locality +go version go1.17.6 linux/amd64 + +Init Tree took 0.76 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkSmall/memdb-1000-100-4-10/iteration-8 1000000000 0.002265 ns/op 0 B/op 0 allocs/op +Init Tree took 0.47 MB +BenchmarkSmall/goleveldb-1000-100-4-10/iteration-8 1000000000 0.003925 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 0.202s +iavl: 0.17.2-14-g0944259 +git commit: 094425990290c396365553ff8d1c078b9a8a2607 +git branch: dev/iavl_data_locality +go version go1.17.6 linux/amd64 + +Init Tree took 78.75 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkMedium/memdb-100000-100-16-40/iteration-8 2272180 454.8 ns/op 35 B/op 0 allocs/op +Init Tree took 46.72 MB +BenchmarkMedium/goleveldb-100000-100-16-40/iteration-8 94693 29678 ns/op 2627 B/op 45 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 18.289s +PASS +ok github.com/cosmos/iavl/benchmarks 0.013s diff --git a/old.txt b/old.txt new file mode 100644 index 000000000..4e0e977d0 --- /dev/null +++ b/old.txt @@ -0,0 +1,65 @@ +cd benchmarks && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-14-g0944259 -X github.com/cosmos/iavl.Commit=094425990290c396365553ff8d1c078b9a8a2607 -X github.com/cosmos/iavl.Branch=dev/iavl_data_locality" -bench=RandomBytes . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-14-g0944259 -X github.com/cosmos/iavl.Commit=094425990290c396365553ff8d1c078b9a8a2607 -X github.com/cosmos/iavl.Branch=dev/iavl_data_locality" -bench=Small . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-14-g0944259 -X github.com/cosmos/iavl.Commit=094425990290c396365553ff8d1c078b9a8a2607 -X github.com/cosmos/iavl.Branch=dev/iavl_data_locality" -bench=Medium . && \ + go test -ldflags "-X github.com/cosmos/iavl.Version=0.17.2-14-g0944259 -X github.com/cosmos/iavl.Commit=094425990290c396365553ff8d1c078b9a8a2607 -X github.com/cosmos/iavl.Branch=dev/iavl_data_locality" -bench=BenchmarkMemKeySizes . +iavl: 0.17.2-14-g0944259 +git commit: 094425990290c396365553ff8d1c078b9a8a2607 +git branch: dev/iavl_data_locality +go version go1.17.6 linux/amd64 + +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkRandomBytes/random-4-8 24844308 45.68 ns/op +BenchmarkRandomBytes/random-16-8 16860562 70.71 ns/op +BenchmarkRandomBytes/random-32-8 10909080 104.8 ns/op +BenchmarkRandomBytes/random-100-8 6314937 264.1 ns/op +BenchmarkRandomBytes/random-1000-8 741458 1550 ns/op +PASS +ok github.com/cosmos/iavl/benchmarks 6.771s +iavl: 0.17.2-14-g0944259 +git commit: 094425990290c396365553ff8d1c078b9a8a2607 +git branch: dev/iavl_data_locality +go version go1.17.6 linux/amd64 + +Init Tree took 0.76 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkSmall/memdb-1000-100-4-10/query-miss-8 284538 4979 ns/op 506 B/op 12 allocs/op +BenchmarkSmall/memdb-1000-100-4-10/query-hits-8 230426 4835 ns/op 682 B/op 15 allocs/op +BenchmarkSmall/memdb-1000-100-4-10/update-8 26002 47491 ns/op 11639 B/op 167 allocs/op +BenchmarkSmall/memdb-1000-100-4-10/block-8 165 9876546 ns/op 2180318 B/op 33752 allocs/op +Init Tree took 0.47 MB +BenchmarkSmall/goleveldb-1000-100-4-10/query-miss-8 217694 5542 ns/op 725 B/op 20 allocs/op +BenchmarkSmall/goleveldb-1000-100-4-10/query-hits-8 181466 6261 ns/op 945 B/op 24 allocs/op +BenchmarkSmall/goleveldb-1000-100-4-10/update-8 17618 78426 ns/op 20895 B/op 164 allocs/op +BenchmarkSmall/goleveldb-1000-100-4-10/block-8 100 11495975 ns/op 2941281 B/op 29216 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 12.763s +iavl: 0.17.2-14-g0944259 +git commit: 094425990290c396365553ff8d1c078b9a8a2607 +git branch: dev/iavl_data_locality +go version go1.17.6 linux/amd64 + +Init Tree took 78.75 MB +goos: linux +goarch: amd64 +pkg: github.com/cosmos/iavl/benchmarks +cpu: Intel(R) Core(TM) i7-8650U CPU @ 1.90GHz +BenchmarkMedium/memdb-100000-100-16-40/query-miss-8 89678 13027 ns/op 592 B/op 12 allocs/op +BenchmarkMedium/memdb-100000-100-16-40/query-hits-8 85288 13898 ns/op 759 B/op 15 allocs/op +BenchmarkMedium/memdb-100000-100-16-40/update-8 10000 158839 ns/op 26897 B/op 327 allocs/op +BenchmarkMedium/memdb-100000-100-16-40/block-8 67 22582378 ns/op 2896848 B/op 35408 allocs/op +Init Tree took 46.38 MB +BenchmarkMedium/goleveldb-100000-100-16-40/query-miss-8 53392 21976 ns/op 1557 B/op 30 allocs/op +BenchmarkMedium/goleveldb-100000-100-16-40/query-hits-8 40587 29711 ns/op 2103 B/op 39 allocs/op +BenchmarkMedium/goleveldb-100000-100-16-40/update-8 9184 258844 ns/op 40476 B/op 391 allocs/op +BenchmarkMedium/goleveldb-100000-100-16-40/block-8 33 39943139 ns/op 5498317 B/op 57375 allocs/op +PASS +ok github.com/cosmos/iavl/benchmarks 24.381s +PASS +ok github.com/cosmos/iavl/benchmarks 0.016s diff --git a/repair.go b/repair.go index e0c7a052c..e688b9cda 100644 --- a/repair.go +++ b/repair.go @@ -41,20 +41,21 @@ func Repair013Orphans(db dbm.DB) (uint64, error) { ) batch := db.NewBatch() defer batch.Close() - ndb.traverseRange(orphanKeyFormat.Key(version), orphanKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) { + err = ndb.traverseRange(orphanKeyFormat.Key(version), orphanKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) error { // Sanity check so we don't remove stuff we shouldn't var toVersion int64 orphanKeyFormat.Scan(k, &toVersion) if toVersion < version { err = errors.Errorf("Found unexpected orphan with toVersion=%v, lesser than latest version %v", toVersion, version) - return + return err } repaired++ err = batch.Delete(k) if err != nil { - return + return err } + return nil }) if err != nil { return 0, err diff --git a/testutils_test.go b/testutils_test.go index 33a84936e..e2d58e5b7 100644 --- a/testutils_test.go +++ b/testutils_test.go @@ -5,15 +5,22 @@ import ( "bytes" "fmt" "runtime" + "sort" "testing" - mrand "math/rand" + "math/rand" cmn "github.com/cosmos/iavl/common" "github.com/stretchr/testify/require" - db "github.com/tendermint/tm-db" + dbm "github.com/tendermint/tm-db" ) +type iteratorTestConfig struct { + startByteToSet, endByteToSet byte + startIterate, endIterate []byte + ascending bool +} + func randstr(length int) string { return cmn.RandStr(length) } @@ -34,7 +41,7 @@ func b2i(bz []byte) int { // Construct a MutableTree func getTestTree(cacheSize int) (*MutableTree, error) { - return NewMutableTreeWithOpts(db.NewMemDB(), cacheSize, nil) + return NewMutableTreeWithOpts(dbm.NewMemDB(), cacheSize, nil) } // Convenience for a new node @@ -83,7 +90,7 @@ func randBytes(length int) []byte { // math.rand.Read always returns err=nil // we do not need cryptographic randomness for this test: //nolint:gosec - mrand.Read(key) + rand.Read(key) return key } @@ -114,13 +121,186 @@ func expectTraverse(t *testing.T, trav traverser, start, end string, count int) } } +func assertMutableMirrorIterate(t *testing.T, tree *MutableTree, mirror map[string]string) { + sortedMirrorKeys := make([]string, 0, len(mirror)) + for k := range mirror { + sortedMirrorKeys = append(sortedMirrorKeys, k) + } + sort.Strings(sortedMirrorKeys) + + curKeyIdx := 0 + tree.Iterate(func(k, v []byte) bool { + nextMirrorKey := sortedMirrorKeys[curKeyIdx] + nextMirrorValue := mirror[nextMirrorKey] + + require.Equal(t, []byte(nextMirrorKey), k) + require.Equal(t, []byte(nextMirrorValue), v) + + curKeyIdx++ + return false + }) +} + +func assertImmutableMirrorIterate(t *testing.T, tree *ImmutableTree, mirror map[string]string) { + sortedMirrorKeys := getSortedMirrorKeys(mirror) + + curKeyIdx := 0 + tree.Iterate(func(k, v []byte) bool { + nextMirrorKey := sortedMirrorKeys[curKeyIdx] + nextMirrorValue := mirror[nextMirrorKey] + + require.Equal(t, []byte(nextMirrorKey), k) + require.Equal(t, []byte(nextMirrorValue), v) + + curKeyIdx++ + return false + }) +} + +func getSortedMirrorKeys(mirror map[string]string) []string { + sortedMirrorKeys := make([]string, 0, len(mirror)) + for k := range mirror { + sortedMirrorKeys = append(sortedMirrorKeys, k) + } + sort.Strings(sortedMirrorKeys) + return sortedMirrorKeys +} + +func getRandomizedTreeAndMirror(t *testing.T) (*MutableTree, map[string]string) { + const cacheSize = 100 + + tree, err := getTestTree(cacheSize) + require.NoError(t, err) + + mirror := make(map[string]string) + + randomizeTreeAndMirror(t, tree, mirror) + return tree, mirror +} + +func randomizeTreeAndMirror(t *testing.T, tree *MutableTree, mirror map[string]string) { + const keyValLength = 5 + + numberOfSets := 1000 + numberOfUpdates := numberOfSets / 4 + numberOfRemovals := numberOfSets / 4 + + for numberOfSets > numberOfRemovals*3 { + key := randBytes(keyValLength) + value := randBytes(keyValLength) + + isUpdated := tree.Set(key, value) + require.False(t, isUpdated) + mirror[string(key)] = string(value) + + numberOfSets-- + } + + for numberOfSets+numberOfRemovals+numberOfUpdates > 0 { + randOp := rand.Intn(2) + if randOp == 0 && numberOfSets > 0 { + + numberOfSets-- + + key := randBytes(keyValLength) + value := randBytes(keyValLength) + + isUpdated := tree.Set(key, value) + require.False(t, isUpdated) + mirror[string(key)] = string(value) + } else if randOp == 1 && numberOfUpdates > 0 { + + numberOfUpdates-- + + key := getRandomKeyFrom(mirror) + value := randBytes(keyValLength) + + isUpdated := tree.Set([]byte(key), value) + require.True(t, isUpdated) + mirror[string(key)] = string(value) + } else if numberOfRemovals > 0 { + + numberOfRemovals-- + + key := getRandomKeyFrom(mirror) + + val, isRemoved := tree.Remove([]byte(key)) + require.True(t, isRemoved) + require.NotNil(t, val) + delete(mirror, string(key)) + } + } +} + +func getRandomKeyFrom(mirror map[string]string) string { + keys := make([]string, 0, len(mirror)) + for k := range mirror { + keys = append(keys, k) + } + key := keys[rand.Intn(len(keys))] + return key +} + +func setupMirrorForIterator(t *testing.T, config *iteratorTestConfig, tree *MutableTree) [][]string { + mirror := make([][]string, 0) + + startByteToSet := config.startByteToSet + endByteToSet := config.endByteToSet + + if !config.ascending { + startByteToSet, endByteToSet = endByteToSet, startByteToSet + } + + curByte := startByteToSet + for curByte != endByteToSet { + value := randBytes(5) + + if (config.startIterate == nil || curByte >= config.startIterate[0]) && (config.endIterate == nil || curByte < config.endIterate[0]) { + mirror = append(mirror, []string{string(curByte), string(value)}) + } + + isUpdated := tree.Set([]byte{curByte}, value) + require.False(t, isUpdated) + + if config.ascending { + curByte++ + } else { + curByte-- + } + } + _, _, err := tree.SaveVersion() + require.NoError(t, err) + return mirror +} + +func assertIterator(t *testing.T, itr dbm.Iterator, mirror [][]string, ascending bool) { + startIdx, endIdx := 0, len(mirror)-1 + increment := 1 + if !ascending { + startIdx, endIdx = endIdx, startIdx + increment *= -1 + } + + for startIdx < endIdx { + nextExpectedPair := mirror[startIdx] + + require.True(t, itr.Valid()) + require.Equal(t, []byte(nextExpectedPair[0]), itr.Key()) + require.Equal(t, []byte(nextExpectedPair[1]), itr.Value()) + itr.Next() + require.NoError(t, itr.Error()) + + startIdx += increment + } +} + func BenchmarkImmutableAvlTreeMemDB(b *testing.B) { - db, err := db.NewDB("test", db.MemDBBackend, "") + db, err := dbm.NewDB("test", dbm.MemDBBackend, "") require.NoError(b, err) benchmarkImmutableAvlTreeWithDB(b, db) } -func benchmarkImmutableAvlTreeWithDB(b *testing.B, db db.DB) { +func benchmarkImmutableAvlTreeWithDB(b *testing.B, db dbm.DB) { defer db.Close() b.StopTimer() diff --git a/tree_fuzz_test.go b/tree_fuzz_test.go index e4e8814fc..9629d1982 100644 --- a/tree_fuzz_test.go +++ b/tree_fuzz_test.go @@ -118,7 +118,9 @@ func TestMutableTreeFuzz(t *testing.T) { program := genRandomProgram(size) err = program.Execute(tree) if err != nil { - t.Fatalf("Error after %d iterations (size %d): %s\n%s", iterations, size, err.Error(), tree.String()) + str, err := tree.String() + require.Nil(t, err) + t.Fatalf("Error after %d iterations (size %d): %s\n%s", iterations, size, err.Error(), str) } iterations++ } diff --git a/tree_random_test.go b/tree_random_test.go index 4a2da565e..edc78b4c3 100644 --- a/tree_random_test.go +++ b/tree_random_test.go @@ -353,9 +353,11 @@ func assertEmptyDatabase(t *testing.T, tree *MutableTree) { // Checks that the tree has the given number of orphan nodes. func assertOrphans(t *testing.T, tree *MutableTree, expected int) { count := 0 - tree.ndb.traverseOrphans(func(k, v []byte) { + err := tree.ndb.traverseOrphans(func(k, v []byte) error { count++ + return nil }) + require.Nil(t, err) require.EqualValues(t, expected, count, "Expected %v orphans, got %v", expected, count) } @@ -408,7 +410,7 @@ func assertFastNodeCacheIsLive(t *testing.T, tree *MutableTree, mirror map[strin for key, cacheElem := range tree.ndb.fastNodeCache { liveFastNode := mirror[key] - + require.NotNil(t, liveFastNode, "cached fast node must be in live tree") require.Equal(t, liveFastNode, string(cacheElem.Value.(*FastNode).value), "cached fast node's value must be equal to live state value") } @@ -420,19 +422,21 @@ func assertFastNodeDiskIsLive(t *testing.T, tree *MutableTree, mirror map[string // The fast node disk check should only be done to the latest version return } - + count := 0 - tree.ndb.traverseFastNodes(func(k, v []byte) { + err := tree.ndb.traverseFastNodes(func(keyWithPrefix, v []byte) error { + key := keyWithPrefix[1:] count += 1 - fastNode, err := DeserializeFastNode(v) + fastNode, err := DeserializeFastNode(key, v) require.Nil(t, err) mirrorVal := mirror[string(fastNode.key)] require.NotNil(t, mirrorVal) require.Equal(t, []byte(mirrorVal), fastNode.value) + return nil }) - + require.NoError(t, err) require.Equal(t, len(mirror), count) } diff --git a/tree_test.go b/tree_test.go index 06cc942a2..da2f0b64d 100644 --- a/tree_test.go +++ b/tree_test.go @@ -66,11 +66,15 @@ func TestVersionedRandomTree(t *testing.T) { tree.SaveVersion() } require.Equal(versions, len(tree.ndb.roots()), "wrong number of roots") - require.Equal(versions*keysPerVersion, len(tree.ndb.leafNodes()), "wrong number of nodes") + leafNodes, err := tree.ndb.leafNodes() + require.Nil(err) + require.Equal(versions*keysPerVersion, len(leafNodes), "wrong number of nodes") // Before deleting old versions, we should have equal or more nodes in the // db than in the current tree version. - require.True(len(tree.ndb.nodes()) >= tree.nodeSize()) + nodes, err := tree.ndb.nodes() + require.Nil(err) + require.True(len(nodes) >= tree.nodeSize()) // Ensure it returns all versions in sorted order available := tree.AvailableVersions() @@ -94,9 +98,13 @@ func TestVersionedRandomTree(t *testing.T) { // After cleaning up all previous versions, we should have as many nodes // in the db as in the current tree version. - require.Len(tree.ndb.leafNodes(), int(tree.Size())) + leafNodes, err = tree.ndb.leafNodes() + require.Nil(err) + require.Len(leafNodes, int(tree.Size())) - require.Equal(tree.nodeSize(), len(tree.ndb.nodes())) + nodes, err = tree.ndb.nodes() + require.Nil(err) + require.Equal(tree.nodeSize(), len(nodes)) } // nolint: dupl @@ -197,9 +205,15 @@ func TestVersionedRandomTreeSmallKeys(t *testing.T) { // After cleaning up all previous versions, we should have as many nodes // in the db as in the current tree version. The simple tree must be equal // too. - require.Len(tree.ndb.leafNodes(), int(tree.Size())) - require.Len(tree.ndb.nodes(), tree.nodeSize()) - require.Len(tree.ndb.nodes(), singleVersionTree.nodeSize()) + leafNodes, err := tree.ndb.leafNodes() + require.Nil(err) + + nodes, err := tree.ndb.nodes() + require.Nil(err) + + require.Len(leafNodes, int(tree.Size())) + require.Len(nodes, tree.nodeSize()) + require.Len(nodes, singleVersionTree.nodeSize()) // Try getting random keys. for i := 0; i < keysPerVersion; i++ { @@ -240,9 +254,15 @@ func TestVersionedRandomTreeSmallKeysRandomDeletes(t *testing.T) { // After cleaning up all previous versions, we should have as many nodes // in the db as in the current tree version. The simple tree must be equal // too. - require.Len(tree.ndb.leafNodes(), int(tree.Size())) - require.Len(tree.ndb.nodes(), tree.nodeSize()) - require.Len(tree.ndb.nodes(), singleVersionTree.nodeSize()) + leafNodes, err := tree.ndb.leafNodes() + require.Nil(err) + + nodes, err := tree.ndb.nodes() + require.Nil(err) + + require.Len(leafNodes, int(tree.Size())) + require.Len(nodes, tree.nodeSize()) + require.Len(nodes, singleVersionTree.nodeSize()) // Try getting random keys. for i := 0; i < keysPerVersion; i++ { @@ -272,7 +292,9 @@ func TestVersionedTreeSpecial1(t *testing.T) { tree.DeleteVersion(2) tree.DeleteVersion(3) - require.Equal(t, tree.nodeSize(), len(tree.ndb.nodes())) + nodes, err := tree.ndb.nodes() + require.Nil(t, err) + require.Equal(t, tree.nodeSize(), len(nodes)) } func TestVersionedRandomTreeSpecial2(t *testing.T) { @@ -289,7 +311,10 @@ func TestVersionedRandomTreeSpecial2(t *testing.T) { tree.SaveVersion() tree.DeleteVersion(1) - require.Len(tree.ndb.nodes(), tree.nodeSize()) + + nodes, err := tree.ndb.nodes() + require.NoError(err) + require.Len(nodes, tree.nodeSize()) } func TestVersionedEmptyTree(t *testing.T) { @@ -368,7 +393,9 @@ func TestVersionedTree(t *testing.T) { tree.Set([]byte("key2"), []byte("val0")) // Still zero keys, since we haven't written them. - require.Len(tree.ndb.leafNodes(), 0) + nodes, err := tree.ndb.leafNodes() + require.NoError(err) + require.Len(nodes, 0) require.False(tree.IsEmpty()) // Now let's write the keys to storage. @@ -383,7 +410,8 @@ func TestVersionedTree(t *testing.T) { // key2 (root) version=1 // ----------- - nodes1 := tree.ndb.leafNodes() + nodes1, err := tree.ndb.leafNodes() + require.NoError(err) require.Len(nodes1, 2, "db should have a size of 2") // version 1 @@ -391,7 +419,9 @@ func TestVersionedTree(t *testing.T) { tree.Set([]byte("key1"), []byte("val1")) tree.Set([]byte("key2"), []byte("val1")) tree.Set([]byte("key3"), []byte("val1")) - require.Len(tree.ndb.leafNodes(), len(nodes1)) + nodes, err = tree.ndb.leafNodes() + require.NoError(err) + require.Len(nodes, len(nodes1)) hash2, v2, err := tree.SaveVersion() require.NoError(err) @@ -417,9 +447,12 @@ func TestVersionedTree(t *testing.T) { // key3 = val1 // ----------- - nodes2 := tree.ndb.leafNodes() + nodes2, err := tree.ndb.leafNodes() + require.NoError(err) require.Len(nodes2, 5, "db should have grown in size") - require.Len(tree.ndb.orphans(), 3, "db should have three orphans") + orphans, err := tree.ndb.orphans() + require.NoError(err) + require.Len(orphans, 3, "db should have three orphans") // Create three more orphans. tree.Remove([]byte("key1")) // orphans both leaf node and inner node containing "key1" and "key2" @@ -439,9 +472,13 @@ func TestVersionedTree(t *testing.T) { // key2 = val2 // ----------- - nodes3 := tree.ndb.leafNodes() + nodes3, err := tree.ndb.leafNodes() + require.NoError(err) require.Len(nodes3, 6, "wrong number of nodes") - require.Len(tree.ndb.orphans(), 7, "wrong number of orphans") + + orphans, err = tree.ndb.orphans() + require.NoError(err) + require.Len(orphans, 7, "wrong number of orphans") hash4, _, _ := tree.SaveVersion() require.EqualValues(hash3, hash4) @@ -456,48 +493,49 @@ func TestVersionedTree(t *testing.T) { // DB UNCHANGED // ------------ - nodes4 := tree.ndb.leafNodes() + nodes4, err := tree.ndb.leafNodes() + require.NoError(err) require.Len(nodes4, len(nodes3), "db should not have changed in size") tree.Set([]byte("key1"), []byte("val0")) // "key2" - val := tree.GetVersionedFast([]byte("key2"), 0) + val := tree.GetVersioned([]byte("key2"), 0) require.Nil(val) - val = tree.GetVersionedFast([]byte("key2"), 1) + val = tree.GetVersioned([]byte("key2"), 1) require.Equal("val0", string(val)) - val = tree.GetVersionedFast([]byte("key2"), 2) + val = tree.GetVersioned([]byte("key2"), 2) require.Equal("val1", string(val)) val = tree.GetFast([]byte("key2")) require.Equal("val2", string(val)) // "key1" - val = tree.GetVersionedFast([]byte("key1"), 1) + val = tree.GetVersioned([]byte("key1"), 1) require.Equal("val0", string(val)) - val = tree.GetVersionedFast([]byte("key1"), 2) + val = tree.GetVersioned([]byte("key1"), 2) require.Equal("val1", string(val)) - val = tree.GetVersionedFast([]byte("key1"), 3) + val = tree.GetVersioned([]byte("key1"), 3) require.Nil(val) - val = tree.GetVersionedFast([]byte("key1"), 4) + val = tree.GetVersioned([]byte("key1"), 4) require.Nil(val) val = tree.GetFast([]byte("key1")) require.Equal("val0", string(val)) // "key3" - val = tree.GetVersionedFast([]byte("key3"), 0) + val = tree.GetVersioned([]byte("key3"), 0) require.Nil(val) - val = tree.GetVersionedFast([]byte("key3"), 2) + val = tree.GetVersioned([]byte("key3"), 2) require.Equal("val1", string(val)) - val = tree.GetVersionedFast([]byte("key3"), 3) + val = tree.GetVersioned([]byte("key3"), 3) require.Equal("val1", string(val)) // Delete a version. After this the keys in that version should not be found. @@ -513,13 +551,15 @@ func TestVersionedTree(t *testing.T) { // key2 = val2 // ----------- - nodes5 := tree.ndb.leafNodes() + nodes5, err := tree.ndb.leafNodes() + require.NoError(err) + require.True(len(nodes5) < len(nodes4), "db should have shrunk after delete %d !< %d", len(nodes5), len(nodes4)) - val = tree.GetVersionedFast([]byte("key2"), 2) + val = tree.GetVersioned([]byte("key2"), 2) require.Nil(val) - val = tree.GetVersionedFast([]byte("key3"), 2) + val = tree.GetVersioned([]byte("key3"), 2) require.Nil(val) // But they should still exist in the latest version. @@ -532,10 +572,10 @@ func TestVersionedTree(t *testing.T) { // Version 1 should still be available. - val = tree.GetVersionedFast([]byte("key1"), 1) + val = tree.GetVersioned([]byte("key1"), 1) require.Equal("val0", string(val)) - val = tree.GetVersionedFast([]byte("key2"), 1) + val = tree.GetVersioned([]byte("key2"), 1) require.Equal("val0", string(val)) } @@ -551,29 +591,39 @@ func TestVersionedTreeVersionDeletingEfficiency(t *testing.T) { tree.Set([]byte("key2"), []byte("val0")) tree.SaveVersion() - require.Len(t, tree.ndb.leafNodes(), 3) + leafNodes, err := tree.ndb.leafNodes() + require.Nil(t, err) + require.Len(t, leafNodes, 3) tree.Set([]byte("key1"), []byte("val1")) tree.Set([]byte("key2"), []byte("val1")) tree.Set([]byte("key3"), []byte("val1")) tree.SaveVersion() - require.Len(t, tree.ndb.leafNodes(), 6) + leafNodes, err = tree.ndb.leafNodes() + require.Nil(t, err) + require.Len(t, leafNodes, 6) tree.Set([]byte("key0"), []byte("val2")) tree.Remove([]byte("key1")) tree.Set([]byte("key2"), []byte("val2")) tree.SaveVersion() - require.Len(t, tree.ndb.leafNodes(), 8) + leafNodes, err = tree.ndb.leafNodes() + require.Nil(t, err) + require.Len(t, leafNodes, 8) tree.DeleteVersion(2) - require.Len(t, tree.ndb.leafNodes(), 6) + leafNodes, err = tree.ndb.leafNodes() + require.Nil(t, err) + require.Len(t, leafNodes, 6) tree.DeleteVersion(1) - require.Len(t, tree.ndb.leafNodes(), 3) + leafNodes, err = tree.ndb.leafNodes() + require.Nil(t, err) + require.Len(t, leafNodes, 3) tree2, err := getTestTree(0) require.NoError(t, err) @@ -620,7 +670,9 @@ func TestVersionedTreeOrphanDeleting(t *testing.T) { tree.DeleteVersion(1) - require.Len(t, tree.ndb.leafNodes(), 3) + leafNodes, err := tree.ndb.leafNodes() + require.Nil(t, err) + require.Len(t, leafNodes, 3) } func TestVersionedTreeSpecialCase(t *testing.T) { @@ -644,7 +696,7 @@ func TestVersionedTreeSpecialCase(t *testing.T) { tree.DeleteVersion(2) - val := tree.GetVersionedFast([]byte("key2"), 1) + val := tree.GetVersioned([]byte("key2"), 1) require.Equal("val0", string(val)) } @@ -673,7 +725,7 @@ func TestVersionedTreeSpecialCase2(t *testing.T) { require.NoError(tree.DeleteVersion(2)) - val := tree.GetVersionedFast([]byte("key2"), 1) + val := tree.GetVersioned([]byte("key2"), 1) require.Equal("val0", string(val)) } @@ -703,7 +755,9 @@ func TestVersionedTreeSpecialCase3(t *testing.T) { tree.DeleteVersion(3) tree.DeleteVersion(4) - require.Equal(tree.nodeSize(), len(tree.ndb.nodes())) + nodes, err := tree.ndb.nodes() + require.NoError(err) + require.Equal(tree.nodeSize(), len(nodes)) } func TestVersionedTreeSaveAndLoad(t *testing.T) { @@ -756,7 +810,9 @@ func TestVersionedTreeSaveAndLoad(t *testing.T) { require.False(ntree.IsEmpty()) require.Equal(int64(4), ntree.Size()) - require.Len(ntree.ndb.nodes(), ntree.nodeSize()) + nodes, err := tree.ndb.nodes() + require.NoError(err) + require.Len(nodes, ntree.nodeSize()) } func TestVersionedTreeErrors(t *testing.T) { @@ -778,7 +834,7 @@ func TestVersionedTreeErrors(t *testing.T) { require.Error(tree.DeleteVersion(1)) // Trying to get a key from a version which doesn't exist. - val := tree.GetVersionedFast([]byte("key"), 404) + val := tree.GetVersioned([]byte("key"), 404) require.Nil(val) // Same thing with proof. We get an error because a proof couldn't be @@ -831,7 +887,7 @@ func TestVersionedCheckpoints(t *testing.T) { for i := 1; i <= versions; i++ { if i%versionsPerCheckpoint != 0 { for _, k := range keys[int64(i)] { - val := tree.GetVersionedFast(k, int64(i)) + val := tree.GetVersioned(k, int64(i)) require.Nil(val) } } @@ -841,7 +897,7 @@ func TestVersionedCheckpoints(t *testing.T) { for i := 1; i <= versions; i++ { for _, k := range keys[int64(i)] { if i%versionsPerCheckpoint == 0 { - val := tree.GetVersionedFast(k, int64(i)) + val := tree.GetVersioned(k, int64(i)) require.NotEmpty(val) } } @@ -870,7 +926,7 @@ func TestVersionedCheckpointsSpecialCase(t *testing.T) { // checkpoint, which is version 10. tree.DeleteVersion(1) - val := tree.GetVersionedFast(key, 2) + val := tree.GetVersioned(key, 2) require.NotEmpty(val) require.Equal([]byte("val1"), val) } @@ -914,7 +970,7 @@ func TestVersionedCheckpointsSpecialCase3(t *testing.T) { tree.DeleteVersion(2) - tree.GetVersionedFast([]byte("m"), 1) + tree.GetVersioned([]byte("m"), 1) } func TestVersionedCheckpointsSpecialCase4(t *testing.T) { @@ -934,19 +990,19 @@ func TestVersionedCheckpointsSpecialCase4(t *testing.T) { tree.Set([]byte("X"), []byte("New")) tree.SaveVersion() - val := tree.GetVersionedFast([]byte("A"), 2) + val := tree.GetVersioned([]byte("A"), 2) require.Nil(t, val) - val = tree.GetVersionedFast([]byte("A"), 1) + val = tree.GetVersioned([]byte("A"), 1) require.NotEmpty(t, val) tree.DeleteVersion(1) tree.DeleteVersion(2) - val = tree.GetVersionedFast([]byte("A"), 2) + val = tree.GetVersioned([]byte("A"), 2) require.Nil(t, val) - val = tree.GetVersionedFast([]byte("A"), 1) + val = tree.GetVersioned([]byte("A"), 1) require.Nil(t, val) } @@ -965,7 +1021,7 @@ func TestVersionedCheckpointsSpecialCase5(t *testing.T) { tree.DeleteVersion(1) - tree.GetVersionedFast([]byte("R"), 2) + tree.GetVersioned([]byte("R"), 2) } func TestVersionedCheckpointsSpecialCase6(t *testing.T) { @@ -992,13 +1048,13 @@ func TestVersionedCheckpointsSpecialCase6(t *testing.T) { tree.DeleteVersion(1) tree.DeleteVersion(2) - tree.GetVersionedFast([]byte("Y"), 1) - tree.GetVersionedFast([]byte("7"), 1) - tree.GetVersionedFast([]byte("Z"), 1) - tree.GetVersionedFast([]byte("6"), 1) - tree.GetVersionedFast([]byte("s"), 1) - tree.GetVersionedFast([]byte("2"), 1) - tree.GetVersionedFast([]byte("4"), 1) + tree.GetVersioned([]byte("Y"), 1) + tree.GetVersioned([]byte("7"), 1) + tree.GetVersioned([]byte("Z"), 1) + tree.GetVersioned([]byte("6"), 1) + tree.GetVersioned([]byte("s"), 1) + tree.GetVersioned([]byte("2"), 1) + tree.GetVersioned([]byte("4"), 1) } func TestVersionedCheckpointsSpecialCase7(t *testing.T) { @@ -1032,7 +1088,7 @@ func TestVersionedCheckpointsSpecialCase7(t *testing.T) { tree.DeleteVersion(4) - tree.GetVersionedFast([]byte("A"), 3) + tree.GetVersioned([]byte("A"), 3) } func TestVersionedTreeEfficiency(t *testing.T) { @@ -1049,9 +1105,15 @@ func TestVersionedTreeEfficiency(t *testing.T) { // Keys of size one are likely to be overwritten. tree.Set([]byte(cmn.RandStr(1)), []byte(cmn.RandStr(8))) } - sizeBefore := len(tree.ndb.nodes()) + nodes, err := tree.ndb.nodes() + require.NoError(err) + sizeBefore := len(nodes) tree.SaveVersion() - sizeAfter := len(tree.ndb.nodes()) + nodes, err = tree.ndb.nodes() + require.NoError(err) + nodes, err = tree.ndb.nodes() + require.NoError(err) + sizeAfter := len(nodes) change := sizeAfter - sizeBefore keysAddedPerVersion[i] = change keysAdded += change @@ -1060,9 +1122,13 @@ func TestVersionedTreeEfficiency(t *testing.T) { keysDeleted := 0 for i := 1; i < versions; i++ { if tree.VersionExists(int64(i)) { - sizeBefore := len(tree.ndb.nodes()) + nodes, err := tree.ndb.nodes() + require.NoError(err) + sizeBefore := len(nodes) tree.DeleteVersion(int64(i)) - sizeAfter := len(tree.ndb.nodes()) + nodes, err = tree.ndb.nodes() + require.NoError(err) + sizeAfter := len(nodes) change := sizeBefore - sizeAfter keysDeleted += change @@ -1172,12 +1238,14 @@ func TestOrphans(t *testing.T) { require.NoError(err, "DeleteVersion should not error") } - tree.ndb.traverseOrphans(func(k, v []byte) { + err = tree.ndb.traverseOrphans(func(k, v []byte) error { var fromVersion, toVersion int64 orphanKeyFormat.Scan(k, &toVersion, &fromVersion) require.True(fromVersion == int64(1) || toVersion == int64(99), fmt.Sprintf(`Unexpected orphan key exists: %v with fromVersion = %d and toVersion = %d.\n Any orphan remaining in db should have either fromVersion == 1 or toVersion == 99. Since Version 1 and 99 are only versions in db`, k, fromVersion, toVersion)) + return nil }) + require.Nil(err) } func TestVersionedTreeHash(t *testing.T) { @@ -1606,7 +1674,9 @@ func TestLoadVersionForOverwritingCase2(t *testing.T) { removedNodes := []*Node{} - for _, n := range tree.ndb.nodes() { + nodes, err := tree.ndb.nodes() + require.NoError(err) + for _, n := range nodes { if n.version > 1 { removedNodes = append(removedNodes, n) } @@ -1659,7 +1729,9 @@ func TestLoadVersionForOverwritingCase3(t *testing.T) { removedNodes := []*Node{} - for _, n := range tree.ndb.nodes() { + nodes, err := tree.ndb.nodes() + require.NoError(err) + for _, n := range nodes { if n.version > 1 { removedNodes = append(removedNodes, n) } @@ -1684,3 +1756,32 @@ func TestLoadVersionForOverwritingCase3(t *testing.T) { require.Equal([]byte{i}, v) } } + +func TestIterate_ImmutableTree_Version1(t *testing.T) { + tree, mirror := getRandomizedTreeAndMirror(t) + + _, _, err := tree.SaveVersion() + require.NoError(t, err) + + immutableTree, err := tree.GetImmutable(1) + require.NoError(t, err) + + assertImmutableMirrorIterate(t, immutableTree, mirror) +} + +func TestIterate_ImmutableTree_Version2(t *testing.T) { + tree, mirror := getRandomizedTreeAndMirror(t) + + _, _, err := tree.SaveVersion() + require.NoError(t, err) + + randomizeTreeAndMirror(t, tree, mirror) + + _, _, err = tree.SaveVersion() + require.NoError(t, err) + + immutableTree, err := tree.GetImmutable(2) + require.NoError(t, err) + + assertImmutableMirrorIterate(t, immutableTree, mirror) +}