diff --git a/CHANGELOG.md b/CHANGELOG.md index 4faa2f480..881799984 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,12 @@ ### Breaking Changes +- [#646](https://github.com/cosmos/iavl/pull/646) Remove the `orphans` from the storage - [#622](https://github.com/cosmos/iavl/pull/622) `export/newExporter()` and `ImmutableTree.Export()` returns error for nil arguements -## Unreleased +### API Changes +- [#646](https://github.com/cosmos/iavl/pull/646) Remove the `DeleteVersion`, `DeleteVersions`, `DeleteVersionsRange` and introduce a new endpoint of `DeleteVersionsTo` instead - [#586](https://github.com/cosmos/iavl/pull/586) Remove the `RangeProof` and refactor the ics23_proof to use the internal methods. - [#640](https://github.com/cosmos/iavl/pull/640) commit `NodeDB` batch in `LoadVersionForOverwriting`. - [#636](https://github.com/cosmos/iavl/pull/636) Speed up rollback method: `LoadVersionForOverwriting`. diff --git a/benchmarks/bench_test.go b/benchmarks/bench_test.go index a16155655..b6792dcbf 100644 --- a/benchmarks/bench_test.go +++ b/benchmarks/bench_test.go @@ -51,7 +51,7 @@ func commitTree(b *testing.B, t *iavl.MutableTree) { } if version > historySize { - err = t.DeleteVersion(version - historySize) + err = t.DeleteVersionsTo(version - historySize) if err != nil { b.Errorf("Can't delete: %v", err) } diff --git a/export_test.go b/export_test.go index 1294f71b1..0e1adf72b 100644 --- a/export_test.go +++ b/export_test.go @@ -296,13 +296,14 @@ func TestExporter_DeleteVersionErrors(t *testing.T) { require.NoError(t, err) defer exporter.Close() - err = tree.DeleteVersion(2) - require.Error(t, err) - err = tree.DeleteVersion(1) + err = tree.DeleteVersionsTo(1) require.NoError(t, err) + err = tree.DeleteVersionsTo(2) + require.Error(t, err) + exporter.Close() - err = tree.DeleteVersion(2) + err = tree.DeleteVersionsTo(2) require.NoError(t, err) } diff --git a/iterator.go b/iterator.go index 391f54e4c..7ec06b180 100644 --- a/iterator.go +++ b/iterator.go @@ -322,16 +322,17 @@ func (iter *NodeIterator) Next(isSkipped bool) { return } - leftNode, err := iter.ndb.GetNode(node.leftHash) + rightNode, err := iter.ndb.GetNode(node.rightHash) if err != nil { iter.err = err return } - iter.nodesToVisit = append(iter.nodesToVisit, leftNode) - rightNode, err := iter.ndb.GetNode(node.rightHash) + iter.nodesToVisit = append(iter.nodesToVisit, rightNode) + + leftNode, err := iter.ndb.GetNode(node.leftHash) if err != nil { iter.err = err return } - iter.nodesToVisit = append(iter.nodesToVisit, rightNode) + iter.nodesToVisit = append(iter.nodesToVisit, leftNode) } diff --git a/iterator_test.go b/iterator_test.go index da4cde833..a376542ac 100644 --- a/iterator_test.go +++ b/iterator_test.go @@ -180,7 +180,7 @@ func TestIterator_WithDelete_Full_Ascending_Success(t *testing.T) { _, _, err = tree.SaveVersion() require.NoError(t, err) - err = tree.DeleteVersion(1) + err = tree.DeleteVersionsTo(1) require.NoError(t, err) latestVersion, err := tree.ndb.getLatestVersion() @@ -330,3 +330,45 @@ func setupUnsavedFastIterator(t *testing.T, config *iteratorTestConfig) (dbm.Ite itr := NewUnsavedFastIterator(config.startIterate, config.endIterate, config.ascending, tree.ndb, tree.unsavedFastNodeAdditions, tree.unsavedFastNodeRemovals) return itr, mirror } + +func TestNodeIterator_Success(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) + + // check if the iterating count is same with the entire node count of the tree + itr, err := NewNodeIterator(tree.root.hash, tree.ndb) + require.NoError(t, err) + nodeCount := 0 + for ; itr.Valid(); itr.Next(false) { + nodeCount++ + } + require.Equal(t, int64(nodeCount), tree.Size()*2-1) + + // check if the skipped node count is right + itr, err = NewNodeIterator(tree.root.hash, tree.ndb) + require.NoError(t, err) + updateCount := 0 + skipCount := 0 + for itr.Valid() { + node := itr.GetNode() + updateCount++ + if node.version < tree.Version() { + skipCount += int(node.size*2 - 2) // the size of the subtree without the root + } + itr.Next(node.version < tree.Version()) + } + require.Equal(t, nodeCount, updateCount+skipCount) +} + +func TestNodeIterator_WithEmptyRoot(t *testing.T) { + itr, err := NewNodeIterator([]byte{}, newNodeDB(dbm.NewMemDB(), 0, nil)) + require.NoError(t, err) + require.False(t, itr.Valid()) +} diff --git a/mutable_tree.go b/mutable_tree.go index ef1d732a7..272acba7f 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -21,6 +21,9 @@ var commitGap uint64 = 5000000 // ErrVersionDoesNotExist is returned if a requested version does not exist. var ErrVersionDoesNotExist = errors.New("version does not exist") +// ErrKeyDoesNotExist is returned if a key does not exist. +var ErrKeyDoesNotExist = errors.New("key does not exist") + // MutableTree is a persistent tree which keeps track of versions. It is not safe for concurrent // use, and should be guarded by a Mutex or RWLock as appropriate. An immutable tree at a given // version can be returned via GetImmutable, which is safe for concurrent access. @@ -32,9 +35,6 @@ var ErrVersionDoesNotExist = errors.New("version does not exist") type MutableTree struct { *ImmutableTree // The current, working tree. lastSaved *ImmutableTree // The most recently saved tree. - orphans map[string]int64 // Nodes removed by changes to working tree. - versions map[int64]bool // The previous, saved versions of the tree. - allRootLoaded bool // Whether all roots are loaded or not(by LazyLoadVersion) unsavedFastNodeAdditions map[string]*fastnode.Node // FastNodes that have not yet been saved to disk unsavedFastNodeRemovals map[string]interface{} // FastNodes that have not yet been removed from disk ndb *nodeDB @@ -56,9 +56,6 @@ func NewMutableTreeWithOpts(db dbm.DB, cacheSize int, opts *Options, skipFastSto return &MutableTree{ ImmutableTree: head, lastSaved: head.clone(), - orphans: map[string]int64{}, - versions: map[int64]bool{}, - allRootLoaded: false, unsavedFastNodeAdditions: make(map[string]*fastnode.Node), unsavedFastNodeRemovals: make(map[string]interface{}), ndb: ndb, @@ -74,34 +71,32 @@ func (tree *MutableTree) IsEmpty() bool { // VersionExists returns whether or not a version exists. func (tree *MutableTree) VersionExists(version int64) bool { - tree.mtx.Lock() - defer tree.mtx.Unlock() - - if tree.allRootLoaded { - return tree.versions[version] + firstVersion, err := tree.ndb.getFirstVersion() + if err != nil { + return false } - - has, ok := tree.versions[version] - if ok { - return has + latestVersion, err := tree.ndb.getLatestVersion() + if err != nil { + return false } - has, _ = tree.ndb.HasRoot(version) - tree.versions[version] = has - return has + return firstVersion <= version && version <= latestVersion } // AvailableVersions returns all available versions in ascending order func (tree *MutableTree) AvailableVersions() []int { - tree.mtx.Lock() - defer tree.mtx.Unlock() + firstVersion, err := tree.ndb.getFirstVersion() + if err != nil { + return nil + } + latestVersion, err := tree.ndb.getLatestVersion() + if err != nil { + return nil + } - res := make([]int, 0, len(tree.versions)) - for i, v := range tree.versions { - if v { - res = append(res, int(i)) - } + res := make([]int, 0) + for version := firstVersion; version <= latestVersion; version++ { + res = append(res, int(version)) } - sort.Ints(res) return res } @@ -121,26 +116,15 @@ func (tree *MutableTree) String() (string, error) { return tree.ndb.String() } -// Set/Remove will orphan at most tree.Height nodes, -// balancing the tree after a Set/Remove will orphan at most 3 nodes. -func (tree *MutableTree) prepareOrphansSlice() []*Node { - return make([]*Node, 0, tree.Height()+3) -} - // Set sets a key in the working tree. Nil values are invalid. The given // key/value byte slices must not be modified after this call, since they point // to slices stored within IAVL. It returns true when an existing value was // updated, while false means it was a new key. func (tree *MutableTree) Set(key, value []byte) (updated bool, err error) { - var orphaned []*Node - orphaned, updated, err = tree.set(key, value) + updated, err = tree.set(key, value) if err != nil { return false, err } - err = tree.addOrphans(orphaned) - if err != nil { - return updated, err - } return updated, nil } @@ -222,9 +206,9 @@ func (tree *MutableTree) Iterator(start, end []byte, ascending bool) (dbm.Iterat return tree.ImmutableTree.Iterator(start, end, ascending) } -func (tree *MutableTree) set(key []byte, value []byte) (orphans []*Node, updated bool, err error) { +func (tree *MutableTree) set(key []byte, value []byte) (updated bool, err error) { if value == nil { - return nil, updated, fmt.Errorf("attempt to store nil value at key '%s'", key) + return updated, fmt.Errorf("attempt to store nil value at key '%s'", key) } if tree.ImmutableTree.root == nil { @@ -232,15 +216,14 @@ func (tree *MutableTree) set(key []byte, value []byte) (orphans []*Node, updated tree.addUnsavedAddition(key, fastnode.NewNode(key, value, tree.version+1)) } tree.ImmutableTree.root = NewNode(key, value, tree.version+1) - return nil, updated, nil + return updated, nil } - orphans = tree.prepareOrphansSlice() - tree.ImmutableTree.root, updated, err = tree.recursiveSet(tree.ImmutableTree.root, key, value, &orphans) - return orphans, updated, err + tree.ImmutableTree.root, updated, err = tree.recursiveSet(tree.ImmutableTree.root, key, value) + return updated, err } -func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orphans *[]*Node) ( +func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) ( newSelf *Node, updated bool, err error, ) { version := tree.version + 1 @@ -270,11 +253,9 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph version: version, }, false, nil default: - *orphans = append(*orphans, node) return NewNode(key, value, version), true, nil } } else { - *orphans = append(*orphans, node) node, err = node.clone(version) if err != nil { return nil, false, err @@ -285,7 +266,7 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph if err != nil { return nil, false, err } - node.leftNode, updated, err = tree.recursiveSet(leftNode, key, value, orphans) + node.leftNode, updated, err = tree.recursiveSet(leftNode, key, value) if err != nil { return nil, updated, err } @@ -295,7 +276,7 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph if err != nil { return nil, false, err } - node.rightNode, updated, err = tree.recursiveSet(rightNode, key, value, orphans) + node.rightNode, updated, err = tree.recursiveSet(rightNode, key, value) if err != nil { return nil, updated, err } @@ -310,7 +291,7 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph return nil, false, err } - newNode, err := tree.balance(node, orphans) + newNode, err := tree.balance(node) if err != nil { return nil, false, err } @@ -321,31 +302,23 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte, orph // Remove removes a key from the working tree. The given key byte slice should not be modified // after this call, since it may point to data stored inside IAVL. func (tree *MutableTree) Remove(key []byte) ([]byte, bool, error) { - val, orphaned, removed, err := tree.remove(key) + val, removed, err := tree.remove(key) if err != nil { return nil, false, err } - err = tree.addOrphans(orphaned) - if err != nil { - return val, removed, err - } return val, removed, nil } // remove tries to remove a key from the tree and if removed, returns its // value, nodes orphaned and 'true'. -func (tree *MutableTree) remove(key []byte) (value []byte, orphaned []*Node, removed bool, err error) { +func (tree *MutableTree) remove(key []byte) (value []byte, removed bool, err error) { if tree.root == nil { - return nil, nil, false, nil + return nil, false, nil } - orphaned = tree.prepareOrphansSlice() - newRootHash, newRoot, _, value, err := tree.recursiveRemove(tree.root, key, &orphaned) - if err != nil { - return nil, nil, false, err - } - if len(orphaned) == 0 { - return nil, nil, false, nil + newRootHash, newRoot, _, value, err := tree.recursiveRemove(tree.root, key) + if err != nil && err != ErrKeyDoesNotExist { + return nil, false, err } if !tree.skipFastStorageUpgrade { @@ -355,12 +328,12 @@ func (tree *MutableTree) remove(key []byte) (value []byte, orphaned []*Node, rem if newRoot == nil && newRootHash != nil { tree.root, err = tree.ndb.GetNode(newRootHash) if err != nil { - return nil, nil, false, err + return nil, false, err } } else { tree.root = newRoot } - return value, orphaned, true, nil + return value, true, nil } // removes the node corresponding to the passed key and balances the tree. @@ -370,15 +343,14 @@ func (tree *MutableTree) remove(key []byte) (value []byte, orphaned []*Node, rem // - new leftmost leaf key for tree after successfully removing 'key' if changed. // - the removed value // - the orphaned nodes. -func (tree *MutableTree) recursiveRemove(node *Node, key []byte, orphans *[]*Node) (newHash []byte, newSelf *Node, newKey []byte, newValue []byte, err error) { +func (tree *MutableTree) recursiveRemove(node *Node, key []byte) (newHash []byte, newSelf *Node, newKey []byte, newValue []byte, err error) { version := tree.version + 1 if node.isLeaf() { if bytes.Equal(key, node.key) { - *orphans = append(*orphans, node) return nil, nil, nil, node.value, nil } - return node.hash, node, nil, nil, nil + return node.hash, node, nil, nil, ErrKeyDoesNotExist } // node.key < key; we go to the left to find the key: @@ -387,15 +359,14 @@ func (tree *MutableTree) recursiveRemove(node *Node, key []byte, orphans *[]*Nod if err != nil { return nil, nil, nil, nil, err } - newLeftHash, newLeftNode, newKey, value, err := tree.recursiveRemove(leftNode, key, orphans) + newLeftHash, newLeftNode, newKey, value, err := tree.recursiveRemove(leftNode, key) if err != nil { + if err == ErrKeyDoesNotExist { + return node.hash, node, nil, value, err + } return nil, nil, nil, nil, err } - if len(*orphans) == 0 { - return node.hash, node, nil, value, nil - } - *orphans = append(*orphans, node) if newLeftHash == nil && newLeftNode == nil { // left node held value, was removed return node.rightHash, node.rightNode, node.key, value, nil } @@ -410,7 +381,7 @@ func (tree *MutableTree) recursiveRemove(node *Node, key []byte, orphans *[]*Nod if err != nil { return nil, nil, nil, nil, err } - newNode, err = tree.balance(newNode, orphans) + newNode, err = tree.balance(newNode) if err != nil { return nil, nil, nil, nil, err } @@ -422,14 +393,14 @@ func (tree *MutableTree) recursiveRemove(node *Node, key []byte, orphans *[]*Nod if err != nil { return nil, nil, nil, nil, err } - newRightHash, newRightNode, newKey, value, err := tree.recursiveRemove(rightNode, key, orphans) + newRightHash, newRightNode, newKey, value, err := tree.recursiveRemove(rightNode, key) if err != nil { + if err == ErrKeyDoesNotExist { + return node.hash, node, nil, value, err + } return nil, nil, nil, nil, err } - if len(*orphans) == 0 { - return node.hash, node, nil, value, nil - } - *orphans = append(*orphans, node) + if newRightHash == nil && newRightNode == nil { // right node held value, was removed return node.leftHash, node.leftNode, nil, value, nil } @@ -448,7 +419,7 @@ func (tree *MutableTree) recursiveRemove(node *Node, key []byte, orphans *[]*Nod return nil, nil, nil, nil, err } - newNode, err = tree.balance(newNode, orphans) + newNode, err = tree.balance(newNode) if err != nil { return nil, nil, nil, nil, err } @@ -461,12 +432,8 @@ func (tree *MutableTree) Load() (int64, error) { return tree.LoadVersion(int64(0)) } -// LazyLoadVersion attempts to lazy load only the specified target version -// without loading previous roots/versions. If the targetVersion is non-positive, the latest version -// will be loaded by default. If the latest version is non-positive, this method -// performs a no-op. Otherwise, if the root does not exist, an error will be -// returned. -func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { +// Returns the version number of the specific version found +func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { firstVersion, err := tree.ndb.getFirstVersion() if err != nil { return 0, err @@ -486,8 +453,7 @@ func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { return latestVersion, fmt.Errorf("wanted to load target %d but only found up to %d", targetVersion, latestVersion) } - // no versions have been saved if the latest version is non-positive - if latestVersion <= 0 { + if firstVersion == 0 { if targetVersion <= 0 { if !tree.skipFastStorageUpgrade { tree.mtx.Lock() @@ -500,117 +466,45 @@ func (tree *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { return 0, fmt.Errorf("no versions found while trying to load %v", targetVersion) } - // default to the latest version if the targeted version is non-positive if targetVersion <= 0 { targetVersion = latestVersion } - rootHash, err := tree.ndb.getRoot(targetVersion) - if err != nil { - return 0, err - } - if rootHash == nil { - return latestVersion, ErrVersionDoesNotExist - } - - tree.mtx.Lock() - defer tree.mtx.Unlock() - - tree.versions[targetVersion] = true - - iTree := &ImmutableTree{ - ndb: tree.ndb, - version: targetVersion, - skipFastStorageUpgrade: tree.skipFastStorageUpgrade, - } - if len(rootHash) > 0 { - // If rootHash is empty then root of tree should be nil - // This makes `LazyLoadVersion` to do the same thing as `LoadVersion` - iTree.root, err = tree.ndb.GetNode(rootHash) - if err != nil { - return 0, err - } + if firstVersion > targetVersion { + return latestVersion, fmt.Errorf("wanted to load target %v but only found from %v", + targetVersion, firstVersion) } - tree.orphans = map[string]int64{} - tree.ImmutableTree = iTree - tree.lastSaved = iTree.clone() - - if !tree.skipFastStorageUpgrade { - // Attempt to upgrade - if _, err := tree.enableFastStorageAndCommitIfNotEnabled(); err != nil { - return 0, err - } + if latestVersion < targetVersion { + return latestVersion, fmt.Errorf("wanted to load target %v but only found up to %v", + targetVersion, latestVersion) } - return targetVersion, nil -} - -// Returns the version number of the latest version found -func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { - roots, err := tree.ndb.getRoots() + targetRoot, err := tree.ndb.getRoot(targetVersion) if err != nil { return 0, err } - if len(roots) == 0 { - if targetVersion <= 0 { - if !tree.skipFastStorageUpgrade { - tree.mtx.Lock() - defer tree.mtx.Unlock() - _, err := tree.enableFastStorageAndCommitIfNotEnabled() - return 0, err - } - return 0, nil - } - return 0, fmt.Errorf("no versions found while trying to load %v", targetVersion) - } - - firstVersion := int64(0) - latestVersion := int64(0) - - tree.mtx.Lock() - defer tree.mtx.Unlock() - - var latestRoot []byte - for version, r := range roots { - tree.versions[version] = true - if version > latestVersion && (targetVersion == 0 || version <= targetVersion) { - latestVersion = version - latestRoot = r - } - if firstVersion == 0 || version < firstVersion { - firstVersion = version - } - } - - if !(targetVersion == 0 || latestVersion == targetVersion) { - return latestVersion, fmt.Errorf("wanted to load target %v but only found up to %v", - targetVersion, latestVersion) - } - - if firstVersion > 0 && firstVersion < int64(tree.ndb.opts.InitialVersion) { + if firstVersion < int64(tree.ndb.opts.InitialVersion) { return latestVersion, fmt.Errorf("initial version set to %v, but found earlier version %v", tree.ndb.opts.InitialVersion, firstVersion) } t := &ImmutableTree{ ndb: tree.ndb, - version: latestVersion, + version: targetVersion, skipFastStorageUpgrade: tree.skipFastStorageUpgrade, } - if len(latestRoot) != 0 { - t.root, err = tree.ndb.GetNode(latestRoot) + if len(targetRoot) != 0 { + t.root, err = tree.ndb.GetNode(targetRoot) if err != nil { return 0, err } } - tree.orphans = map[string]int64{} tree.ImmutableTree = t tree.lastSaved = t.clone() - tree.allRootLoaded = true if !tree.skipFastStorageUpgrade { // Attempt to upgrade @@ -624,61 +518,30 @@ func (tree *MutableTree) LoadVersion(targetVersion int64) (int64, error) { // loadVersionForOverwriting attempts to load a tree at a previously committed // version, or the latest version below it. Any versions greater than targetVersion will be deleted. -func (tree *MutableTree) loadVersionForOverwriting(targetVersion int64, lazy bool) (int64, error) { - var ( - latestVersion int64 - err error - ) - if lazy { - latestVersion, err = tree.LazyLoadVersion(targetVersion) - } else { - latestVersion, err = tree.LoadVersion(targetVersion) - } - if err != nil { - return latestVersion, err +func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) error { + if _, err := tree.LoadVersion(targetVersion); err != nil { + return err } - if err = tree.ndb.DeleteVersionsFrom(targetVersion + 1); err != nil { - return latestVersion, err + if err := tree.ndb.DeleteVersionsFrom(targetVersion + 1); err != nil { + return err } // Commit the tree rollback first // The fast storage rebuild don't have to be atomic with this, // because it's idempotent and will do again when `LoadVersion`. if err := tree.ndb.Commit(); err != nil { - return latestVersion, err + return err } - tree.mtx.Lock() - defer tree.mtx.Unlock() - - tree.ndb.resetLatestVersion(latestVersion) - if !tree.skipFastStorageUpgrade { // it'll repopulates the fast node index because of version mismatch. if _, err := tree.enableFastStorageAndCommitIfNotEnabled(); err != nil { - return latestVersion, err - } - } - - for v := range tree.versions { - if v > targetVersion { - delete(tree.versions, v) + return err } } - return latestVersion, nil -} - -// LoadVersionForOverwriting attempts to load a tree at a previously committed -// version, or the latest version below it. Any versions greater than targetVersion will be deleted. -func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, error) { - return tree.loadVersionForOverwriting(targetVersion, false) -} - -// LazyLoadVersionForOverwriting is the lazy version of `LoadVersionForOverwriting`. -func (tree *MutableTree) LazyLoadVersionForOverwriting(targetVersion int64) (int64, error) { - return tree.loadVersionForOverwriting(targetVersion, true) + return nil } // Returns true if the tree may be auto-upgraded, false otherwise @@ -770,25 +633,22 @@ func (tree *MutableTree) enableFastStorageAndCommit() error { // GetImmutable loads an ImmutableTree at a given version for querying. The returned tree is // safe for concurrent access, provided the version is not deleted, e.g. via `DeleteVersion()`. func (tree *MutableTree) GetImmutable(version int64) (*ImmutableTree, error) { - rootHash, err := tree.ndb.getRoot(version) + firstVersion, err := tree.ndb.getFirstVersion() if err != nil { return nil, err } - if rootHash == nil { + latestVersion, err := tree.ndb.getLatestVersion() + if err != nil { + return nil, err + } + if firstVersion > version || version > latestVersion { return nil, ErrVersionDoesNotExist } - tree.mtx.Lock() - defer tree.mtx.Unlock() - if len(rootHash) == 0 { - tree.versions[version] = true - return &ImmutableTree{ - ndb: tree.ndb, - version: version, - skipFastStorageUpgrade: tree.skipFastStorageUpgrade, - }, nil + rootHash, err := tree.ndb.getRoot(version) + if err != nil { + return nil, err } - tree.versions[version] = true root, err := tree.ndb.GetNode(rootHash) if err != nil { @@ -814,7 +674,6 @@ func (tree *MutableTree) Rollback() { skipFastStorageUpgrade: tree.skipFastStorageUpgrade, } } - tree.orphans = map[string]int64{} if !tree.skipFastStorageUpgrade { tree.unsavedFastNodeAdditions = map[string]*fastnode.Node{} tree.unsavedFastNodeRemovals = map[string]interface{}{} @@ -886,7 +745,6 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { tree.version = version tree.ImmutableTree = tree.ImmutableTree.clone() tree.lastSaved = tree.ImmutableTree.clone() - tree.orphans = map[string]int64{} return existingHash, version, nil } @@ -897,9 +755,6 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { // There can still be orphans, for example if the root is the node being // removed. logger.Debug("SAVE EMPTY TREE %v\n", version) - if err := tree.ndb.SaveOrphans(version, tree.orphans); err != nil { - return nil, 0, err - } if err := tree.ndb.SaveEmptyRoot(version); err != nil { return nil, 0, err } @@ -908,9 +763,6 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { if _, err := tree.ndb.SaveBranch(tree.root); err != nil { return nil, 0, err } - if err := tree.ndb.SaveOrphans(version, tree.orphans); err != nil { - return nil, 0, err - } if err := tree.ndb.SaveRoot(tree.root, version); err != nil { return nil, 0, err } @@ -925,16 +777,10 @@ func (tree *MutableTree) SaveVersion() ([]byte, int64, error) { if err := tree.ndb.Commit(); err != nil { return nil, version, err } - - tree.mtx.Lock() - defer tree.mtx.Unlock() tree.version = version - tree.versions[version] = true - // set new working tree tree.ImmutableTree = tree.ImmutableTree.clone() tree.lastSaved = tree.ImmutableTree.clone() - tree.orphans = map[string]int64{} if !tree.skipFastStorageUpgrade { tree.unsavedFastNodeAdditions = make(map[string]*fastnode.Node) tree.unsavedFastNodeRemovals = make(map[string]interface{}) @@ -1010,23 +856,6 @@ func (tree *MutableTree) saveFastNodeRemovals() error { return nil } -func (tree *MutableTree) deleteVersion(version int64) error { - if version <= 0 { - return errors.New("version must be greater than 0") - } - if version == tree.version { - return fmt.Errorf("cannot delete latest saved version (%d)", version) - } - if !tree.VersionExists(version) { - return ErrVersionDoesNotExist - } - if err := tree.ndb.DeleteVersion(version, true); err != nil { - return err - } - - return nil -} - // SetInitialVersion sets the initial version of the tree, replacing Options.InitialVersion. // It is only used during the initial SaveVersion() call for a tree with no other versions, // and is otherwise ignored. @@ -1034,65 +863,11 @@ func (tree *MutableTree) SetInitialVersion(version uint64) { tree.ndb.opts.InitialVersion = version } -// DeleteVersions deletes a series of versions from the MutableTree. -// Deprecated: please use DeleteVersionsRange instead. -func (tree *MutableTree) DeleteVersions(versions ...int64) error { - logger.Debug("DELETING VERSIONS: %v\n", versions) - - if len(versions) == 0 { - return nil - } - - sort.Slice(versions, func(i, j int) bool { - return versions[i] < versions[j] - }) - - // Find ordered data and delete by interval - intervals := map[int64]int64{} - var fromVersion int64 - for _, version := range versions { - if version-fromVersion != intervals[fromVersion] { - fromVersion = version - } - intervals[fromVersion]++ - } - - for fromVersion, sortedBatchSize := range intervals { - if err := tree.DeleteVersionsRange(fromVersion, fromVersion+sortedBatchSize); err != nil { - return err - } - } - - return nil -} - -// DeleteVersionsRange removes versions from an interval from the MutableTree (not inclusive). +// DeleteVersionsTo removes versions upto the given version from the MutableTree. // An error is returned if any single version has active readers. // All writes happen in a single batch with a single commit. -func (tree *MutableTree) DeleteVersionsRange(fromVersion, toVersion int64) error { - if err := tree.ndb.DeleteVersionsRange(fromVersion, toVersion); err != nil { - return err - } - - if err := tree.ndb.Commit(); err != nil { - return err - } - - tree.mtx.Lock() - defer tree.mtx.Unlock() - for version := fromVersion; version < toVersion; version++ { - delete(tree.versions, version) - } - - return nil -} - -// DeleteVersion deletes a tree version from disk. The version can then no -// longer be accessed. -func (tree *MutableTree) DeleteVersion(version int64) error { - logger.Debug("DELETE VERSION: %d\n", version) - - if err := tree.deleteVersion(version); err != nil { +func (tree *MutableTree) DeleteVersionsTo(toVersion int64) error { + if err := tree.ndb.DeleteVersionsTo(toVersion); err != nil { return err } @@ -1100,30 +875,27 @@ func (tree *MutableTree) DeleteVersion(version int64) error { return err } - tree.mtx.Lock() - defer tree.mtx.Unlock() - delete(tree.versions, version) return nil } // Rotate right and return the new node and orphan. -func (tree *MutableTree) rotateRight(node *Node) (*Node, *Node, error) { +func (tree *MutableTree) rotateRight(node *Node) (*Node, error) { version := tree.version + 1 var err error // TODO: optimize balance & rotate. node, err = node.clone(version) if err != nil { - return nil, nil, err + return nil, err } - orphaned, err := node.getLeftNode(tree.ImmutableTree) + leftNode, err := node.getLeftNode(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - newNode, err := orphaned.clone(version) + newNode, err := leftNode.clone(version) if err != nil { - return nil, nil, err + return nil, err } newNoderHash, newNoderCached := newNode.rightHash, newNode.rightNode @@ -1132,35 +904,35 @@ func (tree *MutableTree) rotateRight(node *Node) (*Node, *Node, error) { err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } err = newNode.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - return newNode, orphaned, nil + return newNode, nil } // Rotate left and return the new node and orphan. -func (tree *MutableTree) rotateLeft(node *Node) (*Node, *Node, error) { +func (tree *MutableTree) rotateLeft(node *Node) (*Node, error) { version := tree.version + 1 var err error // TODO: optimize balance & rotate. node, err = node.clone(version) if err != nil { - return nil, nil, err + return nil, err } - orphaned, err := node.getRightNode(tree.ImmutableTree) + rightNode, err := node.getRightNode(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - newNode, err := orphaned.clone(version) + newNode, err := rightNode.clone(version) if err != nil { - return nil, nil, err + return nil, err } newNodelHash, newNodelCached := newNode.leftHash, newNode.leftNode @@ -1169,20 +941,20 @@ func (tree *MutableTree) rotateLeft(node *Node) (*Node, *Node, error) { err = node.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } err = newNode.calcHeightAndSize(tree.ImmutableTree) if err != nil { - return nil, nil, err + return nil, err } - return newNode, orphaned, nil + return newNode, nil } // NOTE: assumes that node can be modified // TODO: optimize balance & rotate -func (tree *MutableTree) balance(node *Node, orphans *[]*Node) (newSelf *Node, err error) { +func (tree *MutableTree) balance(node *Node) (newSelf *Node, err error) { if node.persisted { return nil, fmt.Errorf("unexpected balance() call on persisted node") } @@ -1204,32 +976,16 @@ func (tree *MutableTree) balance(node *Node, orphans *[]*Node) (newSelf *Node, e if lftBalance >= 0 { // Left Left Case - newNode, orphaned, err := tree.rotateRight(node) - if err != nil { - return nil, err - } - *orphans = append(*orphans, orphaned) - return newNode, nil + return tree.rotateRight(node) } // Left Right Case - var leftOrphaned *Node - - left, err := node.getLeftNode(tree.ImmutableTree) - if err != nil { - return nil, err - } node.leftHash = nil - node.leftNode, leftOrphaned, err = tree.rotateLeft(left) + node.leftNode, err = tree.rotateLeft(leftNode) if err != nil { return nil, err } - newNode, rightOrphaned, err := tree.rotateRight(node) - if err != nil { - return nil, err - } - *orphans = append(*orphans, left, leftOrphaned, rightOrphaned) - return newNode, nil + return tree.rotateRight(node) } if balance < -1 { rightNode, err := node.getRightNode(tree.ImmutableTree) @@ -1243,47 +999,16 @@ func (tree *MutableTree) balance(node *Node, orphans *[]*Node) (newSelf *Node, e } if rightBalance <= 0 { // Right Right Case - newNode, orphaned, err := tree.rotateLeft(node) - if err != nil { - return nil, err - } - *orphans = append(*orphans, orphaned) - return newNode, nil + return tree.rotateLeft(node) } // Right Left Case - var rightOrphaned *Node - - right, err := node.getRightNode(tree.ImmutableTree) - if err != nil { - return nil, err - } node.rightHash = nil - node.rightNode, rightOrphaned, err = tree.rotateRight(right) - if err != nil { - return nil, err - } - newNode, leftOrphaned, err := tree.rotateLeft(node) + node.rightNode, err = tree.rotateRight(rightNode) if err != nil { return nil, err } - - *orphans = append(*orphans, right, leftOrphaned, rightOrphaned) - return newNode, nil + return tree.rotateLeft(node) } // Nothing changed return node, nil } - -func (tree *MutableTree) addOrphans(orphans []*Node) error { - for _, node := range orphans { - if !node.persisted { - // We don't need to orphan nodes that were never persisted. - continue - } - if len(node.hash) == 0 { - return fmt.Errorf("expected to find node hash, but was empty") - } - tree.orphans[ibytes.UnsafeBytesToStr(node.hash)] = node.version - } - return nil -} diff --git a/mutable_tree_test.go b/mutable_tree_test.go index 13af8c885..85dc901ec 100644 --- a/mutable_tree_test.go +++ b/mutable_tree_test.go @@ -115,14 +115,14 @@ func TestNewIteratorConcurrency(t *testing.T) { func TestDelete(t *testing.T) { tree := setupMutableTree(t, false) - _, _, err := tree.set([]byte("k1"), []byte("Fred")) + _, err := tree.set([]byte("k1"), []byte("Fred")) require.NoError(t, err) hash, version, err := tree.SaveVersion() require.NoError(t, err) _, _, err = tree.SaveVersion() require.NoError(t, err) - require.NoError(t, tree.DeleteVersion(version)) + require.NoError(t, tree.DeleteVersionsTo(version)) proof, err := tree.GetVersionedProof([]byte("k1"), version) require.EqualError(t, err, ErrVersionDoesNotExist.Error()) @@ -131,9 +131,8 @@ func TestDelete(t *testing.T) { key := tree.ndb.rootKey(version) err = tree.ndb.db.Set(key, hash) require.NoError(t, err) - tree.versions[version] = true - proof, err = tree.GetVersionedProof([]byte("k1"), version) + proof, err = tree.GetVersionedProof([]byte("k1"), version+1) require.Nil(t, err) require.Equal(t, 0, bytes.Compare([]byte("Fred"), proof.GetExist().Value)) } @@ -183,14 +182,14 @@ func TestTraverse(t *testing.T) { tree := setupMutableTree(t, false) for i := 0; i < 6; i++ { - _, _, err := tree.set([]byte(fmt.Sprintf("k%d", i)), []byte(fmt.Sprintf("v%d", i))) + _, err := tree.set([]byte(fmt.Sprintf("k%d", i)), []byte(fmt.Sprintf("v%d", i))) require.NoError(t, err) } require.Equal(t, 11, tree.nodeSize(), "Size of tree unexpected") } -func TestMutableTree_DeleteVersions(t *testing.T) { +func TestMutableTree_DeleteVersionsTo(t *testing.T) { tree := setupMutableTree(t, false) type entry struct { @@ -220,22 +219,22 @@ func TestMutableTree_DeleteVersions(t *testing.T) { } // delete even versions - versionsToDelete := []int64{2, 4, 6, 8} - require.NoError(t, tree.DeleteVersions(versionsToDelete...)) + versionToDelete := int64(8) + require.NoError(t, tree.DeleteVersionsTo(versionToDelete)) // ensure even versions have been deleted - for _, v := range versionsToDelete { - require.False(t, tree.versions[v]) + for v := int64(1); v <= versionToDelete; v++ { + // require.False(t, tree.versions[v]) - _, err := tree.LazyLoadVersion(v) + _, err := tree.LoadVersion(v) require.Error(t, err) } // ensure odd number versions exist and we can query for all set entries - for _, v := range []int64{1, 3, 5, 7, 9, 10} { - require.True(t, tree.versions[v]) + for _, v := range []int64{9, 10} { + // require.True(t, tree.versions[v]) - _, err := tree.LazyLoadVersion(v) + _, err := tree.LoadVersion(v) require.NoError(t, err) for _, e := range versionEntries[v] { @@ -261,106 +260,6 @@ func TestMutableTree_LoadVersion_Empty(t *testing.T) { require.Error(t, err) } -func TestMutableTree_LazyLoadVersion_Empty(t *testing.T) { - memDB := db.NewMemDB() - tree, err := NewMutableTree(memDB, 0, false) - require.NoError(t, err) - - version, err := tree.LazyLoadVersion(0) - require.NoError(t, err) - assert.EqualValues(t, 0, version) - - version, err = tree.LazyLoadVersion(-1) - require.NoError(t, err) - assert.EqualValues(t, 0, version) - - _, err = tree.LazyLoadVersion(3) - require.Error(t, err) -} - -func TestMutableTree_DeleteVersionsRange(t *testing.T) { - require := require.New(t) - - mdb := db.NewMemDB() - tree, err := NewMutableTree(mdb, 0, false) - require.NoError(err) - const maxLength = 100 - const fromLength = 10 - - versions := make([]int64, 0, maxLength) - for count := 1; count <= maxLength; count++ { - versions = append(versions, int64(count)) - countStr := strconv.Itoa(count) - // Set kv pair and save version - _, err = tree.Set([]byte("aaa"), []byte("bbb")) - require.NoError(err, "Set should not fail") - _, err = tree.Set([]byte("key"+countStr), []byte("value"+countStr)) - require.NoError(err, "Set should not fail") - _, _, err = tree.SaveVersion() - require.NoError(err, "SaveVersion should not fail") - } - - tree, err = NewMutableTree(mdb, 0, false) - require.NoError(err) - targetVersion, err := tree.LoadVersion(int64(maxLength)) - require.NoError(err) - require.Equal(targetVersion, int64(maxLength), "targetVersion shouldn't larger than the actual tree latest version") - - err = tree.DeleteVersionsRange(fromLength, int64(maxLength/2)) - require.NoError(err, "DeleteVersionsTo should not fail") - - for _, version := range versions[:fromLength-1] { - require.True(tree.versions[version], "versions %d no more than 10 should exist", version) - - v, err := tree.LazyLoadVersion(version) - require.NoError(err, version) - require.Equal(v, version) - - value, err := tree.Get([]byte("aaa")) - require.NoError(err) - require.Equal(string(value), "bbb") - - for _, count := range versions[:version] { - countStr := strconv.Itoa(int(count)) - value, err := tree.Get([]byte("key" + countStr)) - require.NoError(err) - require.Equal(string(value), "value"+countStr) - } - } - - for _, version := range versions[fromLength : int64(maxLength/2)-1] { - require.False(tree.versions[version], "versions %d more 10 and no more than 50 should have been deleted", version) - - _, err := tree.LazyLoadVersion(version) - require.Error(err) - } - - for _, version := range versions[int64(maxLength/2)-1:] { - require.True(tree.versions[version], "versions %d more than 50 should exist", version) - - v, err := tree.LazyLoadVersion(version) - require.NoError(err) - require.Equal(v, version) - - value, err := tree.Get([]byte("aaa")) - require.NoError(err) - require.Equal(string(value), "bbb") - - for _, count := range versions[:fromLength] { - countStr := strconv.Itoa(int(count)) - value, err := tree.Get([]byte("key" + countStr)) - require.NoError(err) - require.Equal(string(value), "value"+countStr) - } - for _, count := range versions[int64(maxLength/2)-1 : version] { - countStr := strconv.Itoa(int(count)) - value, err := tree.Get([]byte("key" + countStr)) - require.NoError(err) - require.Equal(string(value), "value"+countStr) - } - } -} - func TestMutableTree_InitialVersion(t *testing.T) { memDB := db.NewMemDB() tree, err := NewMutableTreeWithOpts(memDB, 0, &Options{InitialVersion: 9}, false) @@ -385,20 +284,12 @@ func TestMutableTree_InitialVersion(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, 10, version) - // Check `LazyLoadVersion` behaviors the same as `LoadVersion` - version, err = tree.LazyLoadVersion(0) - require.NoError(t, err) - assert.EqualValues(t, 10, version) - // Reloading the tree with an initial version beyond the lowest should error tree, err = NewMutableTreeWithOpts(memDB, 0, &Options{InitialVersion: 10}, false) require.NoError(t, err) _, err = tree.Load() require.Error(t, err) - _, err = tree.LazyLoadVersion(0) - require.Error(t, err) - // Reloading the tree with a lower initial version is fine, and new versions can be produced tree, err = NewMutableTreeWithOpts(memDB, 0, &Options{InitialVersion: 3}, false) require.NoError(t, err) @@ -406,10 +297,6 @@ func TestMutableTree_InitialVersion(t *testing.T) { require.NoError(t, err) assert.EqualValues(t, 10, version) - version, err = tree.LazyLoadVersion(0) - require.NoError(t, err) - assert.EqualValues(t, 10, version) - _, err = tree.Set([]byte("c"), []byte{0x03}) require.NoError(t, err) _, version, err = tree.SaveVersion() @@ -487,8 +374,8 @@ func checkGetVersioned(t *testing.T, tree *MutableTree, version int64, key, valu func TestMutableTree_GetVersioned(t *testing.T) { tree := prepareTree(t) - ver, err := tree.LazyLoadVersion(1) - require.True(t, ver == 1) + ver, err := tree.LoadVersion(1) + require.True(t, ver == 2) require.NoError(t, err) // check key of unloaded version checkGetVersioned(t, tree, 1, []byte{1}, []byte("a")) @@ -496,7 +383,7 @@ func TestMutableTree_GetVersioned(t *testing.T) { checkGetVersioned(t, tree, 3, []byte{1}, nil) tree = prepareTree(t) - ver, err = tree.LazyLoadVersion(2) + ver, err = tree.LoadVersion(2) require.True(t, ver == 2) require.NoError(t, err) checkGetVersioned(t, tree, 1, []byte{1}, []byte("a")) @@ -506,18 +393,18 @@ func TestMutableTree_GetVersioned(t *testing.T) { func TestMutableTree_DeleteVersion(t *testing.T) { tree := prepareTree(t) - ver, err := tree.LazyLoadVersion(2) + ver, err := tree.LoadVersion(2) require.True(t, ver == 2) require.NoError(t, err) - require.NoError(t, tree.DeleteVersion(1)) + require.NoError(t, tree.DeleteVersionsTo(1)) require.False(t, tree.VersionExists(1)) require.True(t, tree.VersionExists(2)) require.False(t, tree.VersionExists(3)) // cannot delete latest version - require.Error(t, tree.DeleteVersion(2)) + require.Error(t, tree.DeleteVersionsTo(2)) } func TestMutableTree_LazyLoadVersionWithEmptyTree(t *testing.T) { @@ -529,7 +416,7 @@ func TestMutableTree_LazyLoadVersionWithEmptyTree(t *testing.T) { newTree1, err := NewMutableTree(mdb, 1000, false) require.NoError(t, err) - v2, err := newTree1.LazyLoadVersion(1) + v2, err := newTree1.LoadVersion(1) require.NoError(t, err) require.True(t, v1 == v2) @@ -1229,7 +1116,7 @@ func TestUpgradeStorageToFast_Integration_Upgraded_GetFast_Success(t *testing.T) require.NoError(t, err) // LazyLoadVersion - should auto enable fast storage - version, err := sut.LazyLoadVersion(1) + version, err := sut.LoadVersion(1) require.NoError(t, err) isFastCacheEnabled, err = tree.IsFastCacheEnabled() @@ -1416,7 +1303,7 @@ func TestNoFastStorageUpgrade_Integration_SaveVersion_Load_Get_Success(t *testin require.NoError(t, err) // LazyLoadVersion - should not auto enable fast storage - version, err := sut.LazyLoadVersion(1) + version, err := sut.LoadVersion(1) require.NoError(t, err) require.Equal(t, int64(1), version) @@ -1443,7 +1330,7 @@ func TestNoFastStorageUpgrade_Integration_SaveVersion_Load_Get_Success(t *testin require.False(t, isFastCacheEnabled) // LoadVersionForOverwriting - should not auto enable fast storage - version, err = sut.LoadVersionForOverwriting(1) + err = sut.LoadVersionForOverwriting(1) require.NoError(t, err) require.Equal(t, int64(1), version) diff --git a/nodedb.go b/nodedb.go index e49ddb78b..f05594743 100644 --- a/nodedb.go +++ b/nodedb.go @@ -43,16 +43,6 @@ var ( // possible with the other keys, and makes them easier to traverse. They are indexed by the node hash. nodeKeyFormat = keyformat.NewKeyFormat('n', hashSize) // n - // Orphans are keyed in the database by their expected lifetime. - // The first number represents the *last* version at which the orphan needs - // to exist, while the second number represents the *earliest* version at - // which it is expected to exist - which starts out by being the version - // of the node being orphaned. - // To clarify: - // When I write to key {X} with value V and old value O, we orphan O with =time of write - // and = version O was created at. - orphanKeyFormat = keyformat.NewKeyFormat('o', int64Size, int64Size, hashSize) // o - // Key Format for making reads and iterates go through a data-locality preserving db. // The value at an entry will list what version it was written to. // Then to query values, you first query state via this fast method. @@ -78,6 +68,7 @@ type nodeDB struct { opts Options // Options to customize for pruning/writing versionReaders map[int64]uint32 // Number of active version readers storageVersion string // Storage version + firstVersion int64 // First version of nodeDB. latestVersion int64 // Latest version of nodeDB. nodeCache cache.Cache // Cache for nodes in the regular tree that consists of key-value pairs at any version. fastNodeCache cache.Cache // Cache for nodes in the fast index that represents only key-value pairs at the latest version. @@ -99,6 +90,7 @@ func newNodeDB(db dbm.DB, cacheSize int, opts *Options) *nodeDB { db: db, batch: db.NewBatch(), opts: *opts, + firstVersion: 0, latestVersion: 0, // initially invalid nodeCache: cache.New(cacheSize), fastNodeCache: cache.New(fastNodeCacheSize), @@ -402,22 +394,17 @@ func (ndb *nodeDB) resetBatch() error { return nil } -// DeleteVersion deletes a tree version from disk. -// calls deleteOrphans(version), deleteRoot(version, checkLatestVersion) -func (ndb *nodeDB) DeleteVersion(version int64, checkLatestVersion bool) error { - ndb.mtx.Lock() - defer ndb.mtx.Unlock() - - if ndb.versionReaders[version] > 0 { - return fmt.Errorf("unable to delete version %v, it has %v active readers", version, ndb.versionReaders[version]) - } - - err := ndb.deleteOrphans(version) +// deleteVersion deletes a tree version from disk. +// deletes orphans, calls deleteRoot(version) +func (ndb *nodeDB) deleteVersion(version int64) error { + err := ndb.traverseOrphans(version, func(orphan *Node) error { + return ndb.batch.Delete(ndb.nodeKey(orphan.hash)) + }) if err != nil { return err } - err = ndb.deleteRoot(version, checkLatestVersion) + err = ndb.deleteRoot(version) if err != nil { return err } @@ -425,67 +412,41 @@ func (ndb *nodeDB) DeleteVersion(version int64, checkLatestVersion bool) error { } // DeleteVersionsFrom permanently deletes all tree versions from the given version upwards. -func (ndb *nodeDB) DeleteVersionsFrom(version int64) error { +func (ndb *nodeDB) DeleteVersionsFrom(fromVersion int64) error { latest, err := ndb.getLatestVersion() if err != nil { return err } - if latest < version { + if latest < fromVersion { return nil } - root, err := ndb.getRoot(latest) - if err != nil { - return err - } - if root == nil { - return fmt.Errorf("root for version %v not found", latest) - } ndb.mtx.Lock() - defer ndb.mtx.Unlock() - for v, r := range ndb.versionReaders { - if v >= version && r != 0 { + if v >= fromVersion && r != 0 { return fmt.Errorf("unable to delete version %v with %v active readers", v, r) } } + ndb.mtx.Unlock() - // First, delete all active nodes in the current (latest) version whose node version is after - // the given version. - err = ndb.deleteNodesFrom(version, root) - if err != nil { - return err - } - - // 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) - err = ndb.traverseRange(orphanKeyFormat.Key(version-1), orphanKeyFormat.Key(maxVersion), 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 { - return err - } - if err = ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { - return err - } - ndb.nodeCache.Remove(hash) - } else if toVersion >= version-1 { - if err = ndb.batch.Delete(key); err != nil { - return err - } + // First, delete all active nodes whose node version is after the given version. + for version := fromVersion; version <= latest; version++ { + root, err := ndb.getRoot(version) + if err != nil { + return err + } + if root == nil { + return fmt.Errorf("root for version %v not found", version) } - return nil - }) - if err != nil { - return err + err = ndb.deleteNodesFrom(version, root) + if err != nil { + return err + } } // Delete the version root entries - err = ndb.traverseRange(rootKeyFormat.Key(version), rootKeyFormat.Key(maxVersion), func(k, v []byte) error { + err = ndb.traverseRange(rootKeyFormat.Key(fromVersion), rootKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) error { if err = ndb.batch.Delete(k); err != nil { return err } @@ -498,77 +459,40 @@ func (ndb *nodeDB) DeleteVersionsFrom(version int64) error { // NOTICE: we don't touch fast node indexes here, because it'll be rebuilt later because of version mismatch. + ndb.resetLatestVersion(fromVersion - 1) + return nil } -// DeleteVersionsRange deletes versions from an interval (not inclusive). -func (ndb *nodeDB) DeleteVersionsRange(fromVersion, toVersion int64) error { - if fromVersion >= toVersion { - return errors.New("toVersion must be greater than fromVersion") - } - if toVersion == 0 { - return errors.New("toVersion must be greater than 0") +// DeleteVersionsTo deletes the oldest versions up to the given version from disk. +func (ndb *nodeDB) DeleteVersionsTo(toVersion int64) error { + first, err := ndb.getFirstVersion() + if err != nil { + return err } - ndb.mtx.Lock() - defer ndb.mtx.Unlock() - latest, err := ndb.getLatestVersion() if err != nil { return err } - if latest < toVersion { - return fmt.Errorf("cannot delete latest saved version (%d)", latest) - } - predecessor, err := ndb.getPreviousVersion(fromVersion) - if err != nil { - return err + if toVersion < first || latest <= toVersion { + return fmt.Errorf("the version should be in the range of [%d, %d)", first, latest) } for v, r := range ndb.versionReaders { - if v < toVersion && v > predecessor && r != 0 { + if v >= first && v <= toVersion && r != 0 { return fmt.Errorf("unable to delete version %v with %v active readers", v, r) } } - // 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++ { - 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 { - return err - } - if from > predecessor { - if err := ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { - return err - } - ndb.nodeCache.Remove(hash) - } else { - if err := ndb.saveOrphan(hash, from, predecessor); err != nil { - return err - } - } - return nil - }) - if err != nil { + for version := first; version <= toVersion; version++ { + if err := ndb.deleteVersion(version); err != nil { return err } + ndb.resetFirstVersion(version + 1) } - // Delete the version root entries - err = ndb.traverseRange(rootKeyFormat.Key(fromVersion), rootKeyFormat.Key(toVersion), func(k, v []byte) error { - if err := ndb.batch.Delete(k); err != nil { - return err - } - return nil - }) - - if err != nil { - return err - } return nil } @@ -584,119 +508,19 @@ func (ndb *nodeDB) DeleteFastNode(key []byte) error { // deleteNodesFrom deletes the given node and any descendants that have versions after the given // (inclusive). It is mainly used via LoadVersionForOverwriting, to delete the current version. -func (ndb *nodeDB) deleteNodesFrom(version int64, hash []byte) error { - if len(hash) == 0 { - return nil - } - - node, err := ndb.unsafeGetNode(hash) - if err != nil { - return err - } - - if node.version < version { - // We can skip the whole sub-tree since children.version <= parent.version. - return nil - } - - if node.leftHash != nil { - if err := ndb.deleteNodesFrom(version, node.leftHash); err != nil { - return err - } - } - if node.rightHash != nil { - if err := ndb.deleteNodesFrom(version, node.rightHash); err != nil { - return err - } - } - - if node.version >= version { - if err := ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { - return err - } - - ndb.nodeCache.Remove(hash) - } - - return nil -} - -// Saves orphaned nodes to disk under a special prefix. -// version: the new version being saved. -// orphans: the orphan nodes created since version-1 -func (ndb *nodeDB) SaveOrphans(version int64, orphans map[string]int64) error { - ndb.mtx.Lock() - defer ndb.mtx.Unlock() - - toVersion, err := ndb.getPreviousVersion(version) - if err != nil { - return err - } - - for hash, fromVersion := range orphans { - logger.Debug("SAVEORPHAN %v-%v %X\n", fromVersion, toVersion, hash) - err := ndb.saveOrphan([]byte(hash), fromVersion, toVersion) - if err != nil { - return err +func (ndb *nodeDB) deleteNodesFrom(version int64, root []byte) error { + return ndb.traverseTree(root, func(node *Node) (bool, error) { + // We can skip the whole sub-tree since `children.version <= parent.version`. + if node.version < version { + return true, nil } - } - return nil -} - -// Saves a single orphan to disk. -func (ndb *nodeDB) saveOrphan(hash []byte, fromVersion, toVersion int64) error { - if fromVersion > toVersion { - return fmt.Errorf("orphan expires before it comes alive. %d > %d", fromVersion, toVersion) - } - key := ndb.orphanKey(fromVersion, toVersion, hash) - if err := ndb.batch.Set(key, hash); err != nil { - return err - } - return nil -} -// deleteOrphans deletes orphaned nodes from disk, and the associated orphan -// entries. -func (ndb *nodeDB) deleteOrphans(version int64) error { - // Will be zero if there is no previous version. - predecessor, err := ndb.getPreviousVersion(version) - if err != nil { - return err - } - - // Traverse orphans with a lifetime ending at the version specified. - // TODO optimize. - return ndb.traverseOrphansVersion(version, func(key, hash []byte) error { - var fromVersion, toVersion int64 - - // See comment on `orphanKeyFmt`. Note that here, `version` and - // `toVersion` are always equal. - orphanKeyFormat.Scan(key, &toVersion, &fromVersion) - - // Delete orphan key and reverse-lookup key. - if err := ndb.batch.Delete(key); err != nil { - return err + if err := ndb.batch.Delete(ndb.nodeKey(node.hash)); err != nil { + return false, err } - // If there is no predecessor, or the predecessor is earlier than the - // beginning of the lifetime (ie: negative lifetime), or the lifetime - // spans a single version and that version is the one being deleted, we - // can delete the orphan. Otherwise, we shorten its lifetime, by - // moving its endpoint to the previous version. - if predecessor < fromVersion || fromVersion == toVersion { - logger.Debug("DELETE predecessor:%v fromVersion:%v toVersion:%v %X\n", predecessor, fromVersion, toVersion, hash) - if err := ndb.batch.Delete(ndb.nodeKey(hash)); err != nil { - return err - } - ndb.nodeCache.Remove(hash) - } else { - logger.Debug("MOVE predecessor:%v fromVersion:%v toVersion:%v %X\n", predecessor, fromVersion, toVersion, hash) - err := ndb.saveOrphan(hash, fromVersion, predecessor) - if err != nil { - return err - } - } - return nil + ndb.nodeCache.Remove(node.hash) + return false, nil }) } @@ -708,14 +532,25 @@ func (ndb *nodeDB) fastNodeKey(key []byte) []byte { return fastKeyFormat.KeyBytes(key) } -func (ndb *nodeDB) orphanKey(fromVersion, toVersion int64, hash []byte) []byte { - return orphanKeyFormat.Key(toVersion, fromVersion, hash) -} - func (ndb *nodeDB) rootKey(version int64) []byte { return rootKeyFormat.Key(version) } +func (ndb *nodeDB) getFirstVersion() (int64, error) { + if ndb.firstVersion == 0 { + var err error + ndb.firstVersion, err = ndb.getNextVersion(0) + if err != nil { + return 0, err + } + } + return ndb.firstVersion, nil +} + +func (ndb *nodeDB) resetFirstVersion(version int64) { + ndb.firstVersion = version +} + func (ndb *nodeDB) getLatestVersion() (int64, error) { if ndb.latestVersion == 0 { var err error @@ -727,31 +562,25 @@ func (ndb *nodeDB) getLatestVersion() (int64, error) { return ndb.latestVersion, nil } -func (ndb *nodeDB) updateLatestVersion(version int64) { - if ndb.latestVersion < version { - ndb.latestVersion = version - } -} - func (ndb *nodeDB) resetLatestVersion(version int64) { ndb.latestVersion = version } -func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { - itr, err := ndb.db.ReverseIterator( - rootKeyFormat.Key(1), - rootKeyFormat.Key(version), +func (ndb *nodeDB) getNextVersion(version int64) (int64, error) { + itr, err := ndb.db.Iterator( + rootKeyFormat.Key(version+1), + rootKeyFormat.Key(int64(math.MaxInt64)), ) if err != nil { return 0, err } defer itr.Close() - pversion := int64(-1) + nversion := int64(0) for ; itr.Valid(); itr.Next() { k := itr.Key() - rootKeyFormat.Scan(k, &pversion) - return pversion, nil + rootKeyFormat.Scan(k, &nversion) + return nversion, nil } if err := itr.Error(); err != nil { @@ -761,53 +590,44 @@ func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { return 0, nil } -// getFirstVersion returns the first version in the iavl tree, returns 0 if it's empty. -func (ndb *nodeDB) getFirstVersion() (int64, error) { - itr, err := dbm.IteratePrefix(ndb.db, rootKeyFormat.Key()) +func (ndb *nodeDB) getPreviousVersion(version int64) (int64, error) { + itr, err := ndb.db.ReverseIterator( + rootKeyFormat.Key(1), + rootKeyFormat.Key(version), + ) if err != nil { return 0, err } defer itr.Close() - if itr.Valid() { - var version int64 - rootKeyFormat.Scan(itr.Key(), &version) - return version, nil + + pversion := int64(-1) + for ; itr.Valid(); itr.Next() { + k := itr.Key() + rootKeyFormat.Scan(k, &pversion) + return pversion, nil + } + + if err := itr.Error(); err != nil { + return 0, err } + return 0, nil } // deleteRoot deletes the root entry from disk, but not the node it points to. -func (ndb *nodeDB) deleteRoot(version int64, checkLatestVersion bool) error { - latestVersion, err := ndb.getLatestVersion() - if err != nil { - return err - } - - if checkLatestVersion && version == latestVersion { - return errors.New("tried to delete latest version") - } +func (ndb *nodeDB) deleteRoot(version int64) error { if err := ndb.batch.Delete(ndb.rootKey(version)); err != nil { 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) -} - // 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) } -// 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 and return error if any, nil otherwise func (ndb *nodeDB) traverse(fn func(key, value []byte) error) error { @@ -852,6 +672,36 @@ func (ndb *nodeDB) traversePrefix(prefix []byte, fn func(k, v []byte) error) err return nil } +// Traverse the subtree with a given node as the root. +func (ndb *nodeDB) traverseTree(hash []byte, fn func(node *Node) (bool, error)) error { + if len(hash) == 0 { + return nil + } + + node, err := ndb.GetNode(hash) + if err != nil { + return err + } + + stop, err := fn(node) + if err != nil || stop { + return err + } + + if node.leftHash != nil { + if err := ndb.traverseTree(node.leftHash, fn); err != nil { + return err + } + } + if node.rightHash != nil { + if err := ndb.traverseTree(node.rightHash, fn); err != nil { + return err + } + } + + return 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 @@ -905,17 +755,6 @@ func (ndb *nodeDB) getRoot(version int64) ([]byte, error) { return ndb.db.Get(ndb.rootKey(version)) } -func (ndb *nodeDB) getRoots() (roots map[int64][]byte, err error) { - roots = make(map[int64][]byte) - err = ndb.traversePrefix(rootKeyFormat.Key(), func(k, v []byte) error { - var version int64 - rootKeyFormat.Scan(k, &version) - roots[version] = v - return nil - }) - return roots, err -} - // SaveRoot creates an entry on disk for the given root, so that it can be // loaded later. func (ndb *nodeDB) SaveRoot(root *Node, version int64) error { @@ -947,7 +786,7 @@ func (ndb *nodeDB) saveRoot(hash []byte, version int64) error { return err } - ndb.updateLatestVersion(version) + ndb.resetLatestVersion(version) return nil } @@ -966,6 +805,55 @@ func (ndb *nodeDB) decrVersionReaders(version int64) { } } +// traverseOrphans traverses orphans which removed by the updates of the version (n+1). +func (ndb *nodeDB) traverseOrphans(version int64, fn func(*Node) error) error { + cRoot, err := ndb.getRoot(version + 1) + if err != nil { + return err + } + + curIter, err := NewNodeIterator(cRoot, ndb) + if err != nil { + return err + } + + pRoot, err := ndb.getRoot(version) + if err != nil { + return err + } + prevIter, err := NewNodeIterator(pRoot, ndb) + if err != nil { + return err + } + + var orgNode *Node + for prevIter.Valid() { + for orgNode == nil && curIter.Valid() { + node := curIter.GetNode() + if node.version <= version { + curIter.Next(true) + orgNode = node + } else { + curIter.Next(false) + } + } + pNode := prevIter.GetNode() + + if orgNode != nil && bytes.Equal(pNode.hash, orgNode.hash) { + prevIter.Next(true) + orgNode = nil + } else { + err = fn(pNode) + if err != nil { + return err + } + prevIter.Next(false) + } + } + + return nil +} + // Utility and test functions func (ndb *nodeDB) leafNodes() ([]*Node, error) { @@ -1001,12 +889,14 @@ func (ndb *nodeDB) nodes() ([]*Node, error) { func (ndb *nodeDB) orphans() ([][]byte, error) { orphans := [][]byte{} - err := ndb.traverseOrphans(func(k, v []byte) error { - orphans = append(orphans, v) - return nil - }) - if err != nil { - return nil, err + for version := ndb.firstVersion; version < ndb.latestVersion; version++ { + err := ndb.traverseOrphans(version, func(orphan *Node) error { + orphans = append(orphans, orphan.hash) + return nil + }) + if err != nil { + return nil, err + } } return orphans, nil @@ -1101,17 +991,6 @@ func (ndb *nodeDB) String() (string, error) { buf.WriteByte('\n') - err = ndb.traverseOrphans(func(key, value []byte) error { - fmt.Fprintf(buf, "%s: %x\n", key, value) - return nil - }) - - if err != nil { - return "", err - } - - buf.WriteByte('\n') - err = ndb.traverseNodes(func(hash []byte, node *Node) error { switch { case len(hash) == 0: diff --git a/nodedb_test.go b/nodedb_test.go index be77aede6..ad96854a5 100644 --- a/nodedb_test.go +++ b/nodedb_test.go @@ -22,14 +22,6 @@ func BenchmarkNodeKey(b *testing.B) { } } -func BenchmarkOrphanKey(b *testing.B) { - ndb := &nodeDB{} - hashes := makeHashes(b, 2432325) - for i := 0; i < b.N; i++ { - ndb.orphanKey(1234, 1239, hashes[i]) - } -} - func BenchmarkTreeString(b *testing.B) { tree := makeAndPopulateMutableTree(b) b.ReportAllocs() diff --git a/tree_fuzz_test.go b/tree_fuzz_test.go index 6f760290b..ce71892ca 100644 --- a/tree_fuzz_test.go +++ b/tree_fuzz_test.go @@ -67,7 +67,7 @@ func (i instruction) Execute(tree *MutableTree) { case "SAVE": tree.SaveVersion() case "DELETE": - tree.DeleteVersion(i.version) + tree.DeleteVersionsTo(i.version) default: panic("Unrecognized op: " + i.op) } diff --git a/tree_random_test.go b/tree_random_test.go index 1f740b920..bc29b2be7 100644 --- a/tree_random_test.go +++ b/tree_random_test.go @@ -51,16 +51,13 @@ func testRandomOperations(t *testing.T, randSeed int64) { keySize = 16 // before base64-encoding valueSize = 16 // before base64-encoding - versions = 32 // number of final versions to generate - reloadChance = 0.1 // chance of tree reload after save - deleteChance = 0.2 // chance of random version deletion after save - deleteRangeChance = 0.3 // chance of deleting a version range (DeleteVersionsRange) - deleteMultiChance = 0.3 // chance of deleting multiple versions (DeleteVersions) - deleteMax = 5 // max number of versions to delete - revertChance = 0.05 // chance to revert tree to random version with LoadVersionForOverwriting - syncChance = 0.2 // chance of enabling sync writes on tree load - cacheChance = 0.4 // chance of enabling caching - cacheSizeMax = 256 // maximum size of cache (will be random from 1) + versions = 32 // number of final versions to generate + reloadChance = 0.1 // chance of tree reload after save + deleteChance = 0.2 // chance of random version deletion after save + revertChance = 0.05 // chance to revert tree to random version with LoadVersionForOverwriting + syncChance = 0.2 // chance of enabling sync writes on tree load + cacheChance = 0.4 // chance of enabling caching + cacheSizeMax = 256 // maximum size of cache (will be random from 1) versionOps = 64 // number of operations (create/update/delete) per version updateRatio = 0.4 // ratio of updates out of all operations @@ -163,54 +160,15 @@ func testRandomOperations(t *testing.T, randSeed int64) { // Delete random versions if requested, but never the latest version. if r.Float64() < deleteChance { versions := getMirrorVersions(diskMirrors, memMirrors) - switch { - case len(versions) < 2: - - case r.Float64() < deleteRangeChance: - indexFrom := r.Intn(len(versions) - 1) - from := versions[indexFrom] - batch := r.Intn(deleteMax) - if batch > len(versions[indexFrom:])-2 { - batch = len(versions[indexFrom:]) - 2 - } - to := versions[indexFrom+batch] + 1 - t.Logf("Deleting versions %v-%v", from, to-1) - err = tree.DeleteVersionsRange(int64(from), int64(to)) + if len(versions) > 1 { + to := versions[r.Intn(len(versions)-1)] + t.Logf("Deleting versions to %v", to) + err = tree.DeleteVersionsTo(int64(to)) require.NoError(t, err) - for version := from; version < to; version++ { + for version := versions[0]; version <= to; version++ { delete(diskMirrors, int64(version)) delete(memMirrors, int64(version)) } - - // adjust probability to take into account probability of range delete not happening - case r.Float64() < deleteMultiChance/(1.0-deleteRangeChance): - deleteVersions := []int64{} - desc := "" - batchSize := 1 + r.Intn(deleteMax) - if batchSize > len(versions)-1 { - batchSize = len(versions) - 1 - } - for _, i := range r.Perm(len(versions) - 1)[:batchSize] { - deleteVersions = append(deleteVersions, int64(versions[i])) - delete(diskMirrors, int64(versions[i])) - delete(memMirrors, int64(versions[i])) - if len(desc) > 0 { - desc += "," - } - desc += fmt.Sprintf("%v", versions[i]) - } - t.Logf("Deleting versions %v", desc) - err = tree.DeleteVersions(deleteVersions...) - require.NoError(t, err) - - default: - i := r.Intn(len(versions) - 1) - deleteVersion := int64(versions[i]) - t.Logf("Deleting version %v", deleteVersion) - err = tree.DeleteVersion(deleteVersion) - require.NoError(t, err) - delete(diskMirrors, deleteVersion) - delete(memMirrors, deleteVersion) } } @@ -230,7 +188,7 @@ func testRandomOperations(t *testing.T, randSeed int64) { if len(versions) > 1 { version = int64(versions[r.Intn(len(versions)-1)]) t.Logf("Reverting to version %v", version) - _, err = tree.LoadVersionForOverwriting(version) + err = tree.LoadVersionForOverwriting(version) require.NoError(t, err, "Failed to revert to version %v", version) if m, ok := diskMirrors[version]; ok { mirror = copyMirror(m) @@ -265,43 +223,14 @@ func testRandomOperations(t *testing.T, randSeed int64) { } } - // Once we're done, delete all prior versions in random order, make sure all orphans have been - // removed, and check that the latest versions matches the mirror. + // Once we're done, delete all prior versions. remaining := tree.AvailableVersions() remaining = remaining[:len(remaining)-1] - switch { - case len(remaining) == 0: - - case r.Float64() < deleteRangeChance: - t.Logf("Deleting versions %v-%v", remaining[0], remaining[len(remaining)-1]) - err = tree.DeleteVersionsRange(int64(remaining[0]), int64(remaining[len(remaining)-1]+1)) - require.NoError(t, err) - - // adjust probability to take into account probability of range delete not happening - case r.Float64() < deleteMultiChance/(1.0-deleteRangeChance): - deleteVersions := []int64{} - desc := "" - for _, i := range r.Perm(len(remaining)) { - deleteVersions = append(deleteVersions, int64(remaining[i])) - if len(desc) > 0 { - desc += "," - } - desc += fmt.Sprintf("%v", remaining[i]) - } - t.Logf("Deleting versions %v", desc) - err = tree.DeleteVersions(deleteVersions...) + if len(remaining) > 0 { + t.Logf("Deleting versions to %v", remaining[len(remaining)-1]) + err = tree.DeleteVersionsTo(int64(remaining[len(remaining)-1])) require.NoError(t, err) - - default: - for len(remaining) > 0 { - i := r.Intn(len(remaining)) - deleteVersion := int64(remaining[i]) - remaining = append(remaining[:i], remaining[i+1:]...) - t.Logf("Deleting version %v", deleteVersion) - err = tree.DeleteVersion(deleteVersion) - require.NoError(t, err) - } } require.EqualValues(t, []int{int(version)}, tree.AvailableVersions()) @@ -326,7 +255,7 @@ func testRandomOperations(t *testing.T, randSeed int64) { } _, _, err = tree.SaveVersion() require.NoError(t, err) - err = tree.DeleteVersion(prevVersion) + err = tree.DeleteVersionsTo(prevVersion) require.NoError(t, err) assertEmptyDatabase(t, tree) t.Logf("Final version %v deleted, no stray database entries", prevVersion) @@ -367,13 +296,9 @@ 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 - err := tree.ndb.traverseOrphans(func(k, v []byte) error { - count++ - return nil - }) + orphans, err := tree.ndb.orphans() require.Nil(t, err) - require.EqualValues(t, expected, count, "Expected %v orphans, got %v", expected, count) + require.EqualValues(t, expected, len(orphans), "Expected %v orphans, got %v", expected, len(orphans)) } // Checks that a version is the maximum mirrored version. diff --git a/tree_test.go b/tree_test.go index 5d801a277..d58547387 100644 --- a/tree_test.go +++ b/tree_test.go @@ -67,9 +67,9 @@ func TestVersionedRandomTree(t *testing.T) { } tree.SaveVersion() } - roots, err := tree.ndb.getRoots() - require.NoError(err) - require.Equal(versions, len(roots), "wrong number of roots") + // roots, err := tree.ndb.getRoots() + // require.NoError(err) + // require.Equal(versions, len(roots), "wrong number of roots") leafNodes, err := tree.ndb.leafNodes() require.Nil(err) @@ -88,10 +88,10 @@ func TestVersionedRandomTree(t *testing.T) { assert.Equal(t, versions, available[len(available)-1]) for i := 1; i < versions; i++ { - tree.DeleteVersion(int64(i)) + tree.DeleteVersionsTo(int64(i)) } - require.Len(tree.versions, 1, "tree must have one version left") + // require.Len(tree.versions, 1, "tree must have one version left") tr, err := tree.GetImmutable(int64(versions)) require.NoError(err, "GetImmutable should not error for version %d", versions) require.Equal(tr.root, tree.root) @@ -206,7 +206,7 @@ func TestVersionedRandomTreeSmallKeys(t *testing.T) { singleVersionTree.SaveVersion() for i := 1; i < versions; i++ { - tree.DeleteVersion(int64(i)) + tree.DeleteVersionsTo(int64(i)) } // After cleaning up all previous versions, we should have as many nodes @@ -256,7 +256,7 @@ func TestVersionedRandomTreeSmallKeysRandomDeletes(t *testing.T) { singleVersionTree.SaveVersion() for _, i := range iavlrand.RandPerm(versions - 1) { - tree.DeleteVersion(int64(i + 1)) + tree.DeleteVersionsTo(int64(i + 1)) } // After cleaning up all previous versions, we should have as many nodes @@ -297,9 +297,9 @@ func TestVersionedTreeSpecial1(t *testing.T) { tree.Set([]byte("T"), []byte("MhkWjkVy")) tree.SaveVersion() - tree.DeleteVersion(1) - tree.DeleteVersion(2) - tree.DeleteVersion(3) + tree.DeleteVersionsTo(1) + tree.DeleteVersionsTo(2) + tree.DeleteVersionsTo(3) nodes, err := tree.ndb.nodes() require.Nil(t, err) @@ -319,7 +319,7 @@ func TestVersionedRandomTreeSpecial2(t *testing.T) { tree.Set([]byte("7OSHNE7k"), []byte("ff181M2d")) tree.SaveVersion() - tree.DeleteVersion(1) + tree.DeleteVersionsTo(1) nodes, err := tree.ndb.nodes() require.NoError(err) @@ -359,8 +359,7 @@ func TestVersionedEmptyTree(t *testing.T) { require.True(tree.VersionExists(1)) require.True(tree.VersionExists(3)) - require.NoError(tree.DeleteVersion(1)) - require.NoError(tree.DeleteVersion(3)) + require.NoError(tree.DeleteVersionsTo(3)) require.False(tree.VersionExists(1)) require.False(tree.VersionExists(3)) @@ -369,19 +368,16 @@ func TestVersionedEmptyTree(t *testing.T) { require.EqualValues(5, tree.root.version) // Now reload the tree. - tree, err = NewMutableTree(d, 0, false) require.NoError(err) tree.Load() require.False(tree.VersionExists(1)) - require.True(tree.VersionExists(2)) + require.False(tree.VersionExists(2)) require.False(tree.VersionExists(3)) - t2, err := tree.GetImmutable(2) - require.NoError(err, "GetImmutable should not fail for version 2") - - require.Empty(t2.root) + _, err = tree.GetImmutable(2) + require.Error(err, "GetImmutable should fail for version 2") } func TestVersionedTree(t *testing.T) { @@ -445,7 +441,7 @@ func TestVersionedTree(t *testing.T) { _, err = tree.Load() require.NoError(err) - require.Len(tree.versions, 2, "wrong number of versions") + // require.Len(tree.versions, 2, "wrong number of versions") require.EqualValues(v2, tree.Version()) // -----1----- @@ -561,8 +557,7 @@ func TestVersionedTree(t *testing.T) { require.Equal("val1", string(val)) // Delete a version. After this the keys in that version should not be found. - - tree.DeleteVersion(2) + tree.DeleteVersionsTo(2) // -----1----- // key1 = val0 @@ -596,15 +591,15 @@ func TestVersionedTree(t *testing.T) { require.NoError(err) require.Equal("val1", string(val)) - // Version 1 should still be available. + // Version 1 should not be available. val, err = tree.GetVersioned([]byte("key1"), 1) require.NoError(err) - require.Equal("val0", string(val)) + require.Nil(val) val, err = tree.GetVersioned([]byte("key2"), 1) require.NoError(err) - require.Equal("val0", string(val)) + require.Nil(val) } func TestVersionedTreeVersionDeletingEfficiency(t *testing.T) { @@ -641,13 +636,7 @@ func TestVersionedTreeVersionDeletingEfficiency(t *testing.T) { require.Nil(t, err) require.Len(t, leafNodes, 8) - tree.DeleteVersion(2) - - leafNodes, err = tree.ndb.leafNodes() - require.Nil(t, err) - require.Len(t, leafNodes, 6) - - tree.DeleteVersion(1) + tree.DeleteVersionsTo(2) leafNodes, err = tree.ndb.leafNodes() require.Nil(t, err) @@ -682,7 +671,7 @@ func TestVersionedTreeOrphanDeleting(t *testing.T) { tree.Set([]byte("key2"), []byte("val2")) tree.SaveVersion() - tree.DeleteVersion(2) + tree.DeleteVersionsTo(2) val, err := tree.Get([]byte("key0")) require.NoError(t, err) @@ -700,7 +689,7 @@ func TestVersionedTreeOrphanDeleting(t *testing.T) { require.NoError(t, err) require.Equal(t, val, []byte("val1")) - tree.DeleteVersion(1) + tree.DeleteVersionsTo(1) leafNodes, err := tree.ndb.leafNodes() require.Nil(t, err) @@ -726,11 +715,11 @@ func TestVersionedTreeSpecialCase(t *testing.T) { tree.Set([]byte("key2"), []byte("val2")) tree.SaveVersion() - tree.DeleteVersion(2) + tree.DeleteVersionsTo(2) val, err := tree.GetVersioned([]byte("key2"), 1) require.NoError(err) - require.Equal("val0", string(val)) + require.Nil(val) } func TestVersionedTreeSpecialCase2(t *testing.T) { @@ -756,11 +745,11 @@ func TestVersionedTreeSpecialCase2(t *testing.T) { _, err = tree.Load() require.NoError(err) - require.NoError(tree.DeleteVersion(2)) + require.NoError(tree.DeleteVersionsTo(2)) val, err := tree.GetVersioned([]byte("key2"), 1) require.NoError(err) - require.Equal("val0", string(val)) + require.Nil(val) } func TestVersionedTreeSpecialCase3(t *testing.T) { @@ -784,10 +773,10 @@ func TestVersionedTreeSpecialCase3(t *testing.T) { tree.Set([]byte("k"), []byte("CpEnpzKJ")) tree.SaveVersion() - tree.DeleteVersion(1) - tree.DeleteVersion(2) - tree.DeleteVersion(3) - tree.DeleteVersion(4) + tree.DeleteVersionsTo(1) + tree.DeleteVersionsTo(2) + tree.DeleteVersionsTo(3) + tree.DeleteVersionsTo(4) nodes, err := tree.ndb.nodes() require.NoError(err) @@ -837,12 +826,12 @@ func TestVersionedTreeSaveAndLoad(t *testing.T) { ntree.Set([]byte("T"), []byte("MhkWjkVy")) ntree.SaveVersion() - ntree.DeleteVersion(6) - ntree.DeleteVersion(5) - ntree.DeleteVersion(1) - ntree.DeleteVersion(2) - ntree.DeleteVersion(4) - ntree.DeleteVersion(3) + ntree.DeleteVersionsTo(6) + ntree.DeleteVersionsTo(5) + ntree.DeleteVersionsTo(1) + ntree.DeleteVersionsTo(2) + ntree.DeleteVersionsTo(4) + ntree.DeleteVersionsTo(3) require.False(ntree.IsEmpty()) require.Equal(int64(4), ntree.Size()) @@ -857,8 +846,8 @@ func TestVersionedTreeErrors(t *testing.T) { require.NoError(err) // Can't delete non-existent versions. - require.Error(tree.DeleteVersion(1)) - require.Error(tree.DeleteVersion(99)) + require.Error(tree.DeleteVersionsTo(1)) + require.Error(tree.DeleteVersionsTo(99)) tree.Set([]byte("key"), []byte("val")) @@ -867,7 +856,7 @@ func TestVersionedTreeErrors(t *testing.T) { require.NoError(err) // Can't delete current version. - require.Error(tree.DeleteVersion(1)) + require.Error(tree.DeleteVersionsTo(1)) // Trying to get a key from a version which doesn't exist. val, err := tree.GetVersioned([]byte("key"), 404) @@ -880,68 +869,6 @@ func TestVersionedTreeErrors(t *testing.T) { require.Error(err) } -func TestVersionedCheckpoints(t *testing.T) { - require := require.New(t) - d, closeDB := getTestDB() - defer closeDB() - - tree, err := NewMutableTree(d, 100, false) - require.NoError(err) - versions := 50 - keysPerVersion := 10 - versionsPerCheckpoint := 5 - keys := map[int64]([][]byte){} - - for i := 1; i <= versions; i++ { - for j := 0; j < keysPerVersion; j++ { - k := []byte(iavlrand.RandStr(1)) - v := []byte(iavlrand.RandStr(8)) - keys[int64(i)] = append(keys[int64(i)], k) - tree.Set(k, v) - } - _, _, err = tree.SaveVersion() - require.NoError(err, "failed to save version") - } - - for i := 1; i <= versions; i++ { - if i%versionsPerCheckpoint != 0 { - err = tree.DeleteVersion(int64(i)) - require.NoError(err, "failed to delete") - } - } - - // Make sure all keys exist at least once. - for _, ks := range keys { - for _, k := range ks { - val, err := tree.Get(k) - require.NoError(err) - require.NotEmpty(val) - } - } - - // Make sure all keys from deleted versions aren't present. - for i := 1; i <= versions; i++ { - if i%versionsPerCheckpoint != 0 { - for _, k := range keys[int64(i)] { - val, err := tree.GetVersioned(k, int64(i)) - require.NoError(err) - require.Nil(val) - } - } - } - - // Make sure all keys exist at all checkpoints. - for i := 1; i <= versions; i++ { - for _, k := range keys[int64(i)] { - if i%versionsPerCheckpoint == 0 { - val, err := tree.GetVersioned(k, int64(i)) - require.NoError(err) - require.NotEmpty(val) - } - } - } -} - func TestVersionedCheckpointsSpecialCase(t *testing.T) { require := require.New(t) tree, err := getTestTree(0) @@ -962,10 +889,10 @@ func TestVersionedCheckpointsSpecialCase(t *testing.T) { // When version 1 is deleted, the orphans should move to the next // checkpoint, which is version 10. - tree.DeleteVersion(1) + tree.DeleteVersionsTo(1) val, err := tree.GetVersioned(key, 2) - require.NoError(err) + require.Nil(err) require.NotEmpty(val) require.Equal([]byte("val1"), val) } @@ -987,8 +914,8 @@ func TestVersionedCheckpointsSpecialCase2(t *testing.T) { tree.Set([]byte("X"), []byte("New")) tree.SaveVersion() - tree.DeleteVersion(1) - tree.DeleteVersion(2) + tree.DeleteVersionsTo(1) + tree.DeleteVersionsTo(2) } func TestVersionedCheckpointsSpecialCase3(t *testing.T) { @@ -1007,7 +934,7 @@ func TestVersionedCheckpointsSpecialCase3(t *testing.T) { tree.Set([]byte("B"), []byte("rj97IKZh")) tree.SaveVersion() - tree.DeleteVersion(2) + tree.DeleteVersionsTo(2) tree.GetVersioned([]byte("m"), 1) } @@ -1037,8 +964,8 @@ func TestVersionedCheckpointsSpecialCase4(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, val) - tree.DeleteVersion(1) - tree.DeleteVersion(2) + tree.DeleteVersionsTo(1) + tree.DeleteVersionsTo(2) val, err = tree.GetVersioned([]byte("A"), 2) require.NoError(t, err) @@ -1062,7 +989,7 @@ func TestVersionedCheckpointsSpecialCase5(t *testing.T) { tree.Set([]byte("R"), []byte("vQDaoz6Z")) tree.SaveVersion() - tree.DeleteVersion(1) + tree.DeleteVersionsTo(1) tree.GetVersioned([]byte("R"), 2) } @@ -1088,8 +1015,8 @@ func TestVersionedCheckpointsSpecialCase6(t *testing.T) { tree.Set([]byte("5"), []byte("wZuLGDkZ")) tree.SaveVersion() - tree.DeleteVersion(1) - tree.DeleteVersion(2) + tree.DeleteVersionsTo(1) + tree.DeleteVersionsTo(2) tree.GetVersioned([]byte("Y"), 1) tree.GetVersioned([]byte("7"), 1) @@ -1129,7 +1056,7 @@ func TestVersionedCheckpointsSpecialCase7(t *testing.T) { tree.Set([]byte("A"), []byte("tWQgbFCY")) tree.SaveVersion() - tree.DeleteVersion(4) + tree.DeleteVersionsTo(4) tree.GetVersioned([]byte("A"), 3) } @@ -1168,7 +1095,7 @@ func TestVersionedTreeEfficiency(t *testing.T) { nodes, err := tree.ndb.nodes() require.NoError(err) sizeBefore := len(nodes) - tree.DeleteVersion(int64(i)) + tree.DeleteVersionsTo(int64(i)) nodes, err = tree.ndb.nodes() require.NoError(err) sizeAfter := len(nodes) @@ -1283,20 +1210,10 @@ func TestOrphans(t *testing.T) { require.NoError(err, "SaveVersion should not error") } - idx := iavlrand.RandPerm(NUMVERSIONS - 2) - for _, v := range idx { - err = tree.DeleteVersion(int64(v + 1)) + for v := 1; v < NUMVERSIONS; v++ { + err = tree.DeleteVersionsTo(int64(v)) require.NoError(err, "DeleteVersion should not error") } - - 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) { @@ -1395,12 +1312,12 @@ func TestRollback(t *testing.T) { require.Equal([]byte("v"), val) } -func TestLazyLoadVersion(t *testing.T) { +func TestLoadVersion(t *testing.T) { tree, err := getTestTree(0) require.NoError(t, err) maxVersions := 10 - version, err := tree.LazyLoadVersion(0) + version, err := tree.LoadVersion(0) require.NoError(t, err, "unexpected error") require.Equal(t, version, int64(0), "expected latest version to be zero") @@ -1412,7 +1329,7 @@ func TestLazyLoadVersion(t *testing.T) { } // require the ability to lazy load the latest version - version, err = tree.LazyLoadVersion(int64(maxVersions)) + version, err = tree.LoadVersion(int64(maxVersions)) require.NoError(t, err, "unexpected error when lazy loading version") require.Equal(t, version, int64(maxVersions)) @@ -1421,16 +1338,16 @@ func TestLazyLoadVersion(t *testing.T) { require.Equal(t, value, []byte(fmt.Sprintf("value_%d", maxVersions)), "unexpected value") // require the ability to lazy load an older version - version, err = tree.LazyLoadVersion(int64(maxVersions - 1)) + version, err = tree.LoadVersion(int64(maxVersions - 1)) require.NoError(t, err, "unexpected error when lazy loading version") - require.Equal(t, version, int64(maxVersions-1)) + require.Equal(t, version, int64(maxVersions)) value, err = tree.Get([]byte(fmt.Sprintf("key_%d", maxVersions-1))) require.NoError(t, err) require.Equal(t, value, []byte(fmt.Sprintf("value_%d", maxVersions-1)), "unexpected value") // require the inability to lazy load a non-valid version - version, err = tree.LazyLoadVersion(int64(maxVersions + 1)) + version, err = tree.LoadVersion(int64(maxVersions + 1)) require.Error(t, err, "expected error when lazy loading version") require.Equal(t, version, int64(maxVersions)) } @@ -1522,12 +1439,11 @@ func TestLoadVersionForOverwriting(t *testing.T) { tree, err = NewMutableTree(mdb, 0, false) require.NoError(err) - targetVersion, _ := tree.LoadVersionForOverwriting(int64(maxLength * 2)) - require.Equal(targetVersion, int64(maxLength), "targetVersion shouldn't larger than the actual tree latest version") + require.Error(tree.LoadVersionForOverwriting(int64(maxLength * 2))) tree, err = NewMutableTree(mdb, 0, false) require.NoError(err) - _, err = tree.LoadVersionForOverwriting(int64(maxLength / 2)) + err = tree.LoadVersionForOverwriting(int64(maxLength / 2)) require.NoError(err, "LoadVersion should not fail") for version := 1; version <= maxLength/2; version++ { @@ -1573,101 +1489,6 @@ func TestLoadVersionForOverwriting(t *testing.T) { require.NoError(err, "SaveVersion should not fail.") } -func TestDeleteVersionsCompare(t *testing.T) { - require := require.New(t) - - var databaseSizeDeleteVersionsRange, databaseSizeDeleteVersion, databaseSizeDeleteVersions string - - const maxLength = 100 - const fromLength = 5 - { - mdb := db.NewMemDB() - tree, err := NewMutableTree(mdb, 0, false) - require.NoError(err) - - versions := make([]int64, 0, maxLength) - for count := 1; count <= maxLength; count++ { - versions = append(versions, int64(count)) - countStr := strconv.Itoa(count) - // Set kv pair and save version - tree.Set([]byte("aaa"), []byte("bbb")) - tree.Set([]byte("key"+countStr), []byte("value"+countStr)) - _, _, err = tree.SaveVersion() - require.NoError(err, "SaveVersion should not fail") - } - - tree, err = NewMutableTree(mdb, 0, false) - require.NoError(err) - targetVersion, err := tree.LoadVersion(int64(maxLength)) - require.NoError(err) - require.Equal(targetVersion, int64(maxLength), "targetVersion shouldn't larger than the actual tree latest version") - - err = tree.DeleteVersionsRange(versions[fromLength], versions[int64(maxLength/2)]) - require.NoError(err, "DeleteVersionsRange should not fail") - - databaseSizeDeleteVersionsRange = mdb.Stats()["database.size"] - } - { - mdb := db.NewMemDB() - tree, err := NewMutableTree(mdb, 0, false) - require.NoError(err) - - versions := make([]int64, 0, maxLength) - for count := 1; count <= maxLength; count++ { - versions = append(versions, int64(count)) - countStr := strconv.Itoa(count) - // Set kv pair and save version - tree.Set([]byte("aaa"), []byte("bbb")) - tree.Set([]byte("key"+countStr), []byte("value"+countStr)) - _, _, err = tree.SaveVersion() - require.NoError(err, "SaveVersion should not fail") - } - - tree, err = NewMutableTree(mdb, 0, false) - require.NoError(err) - targetVersion, err := tree.LoadVersion(int64(maxLength)) - require.NoError(err) - require.Equal(targetVersion, int64(maxLength), "targetVersion shouldn't larger than the actual tree latest version") - - for _, version := range versions[fromLength:int64(maxLength/2)] { - err = tree.DeleteVersion(version) - require.NoError(err, "DeleteVersion should not fail for %v", version) - } - - databaseSizeDeleteVersion = mdb.Stats()["database.size"] - } - { - mdb := db.NewMemDB() - tree, err := NewMutableTree(mdb, 0, false) - require.NoError(err) - - versions := make([]int64, 0, maxLength) - for count := 1; count <= maxLength; count++ { - versions = append(versions, int64(count)) - countStr := strconv.Itoa(count) - // Set kv pair and save version - tree.Set([]byte("aaa"), []byte("bbb")) - tree.Set([]byte("key"+countStr), []byte("value"+countStr)) - _, _, err = tree.SaveVersion() - require.NoError(err, "SaveVersion should not fail") - } - - tree, err = NewMutableTree(mdb, 0, false) - require.NoError(err) - targetVersion, err := tree.LoadVersion(int64(maxLength)) - require.NoError(err) - require.Equal(targetVersion, int64(maxLength), "targetVersion shouldn't larger than the actual tree latest version") - - err = tree.DeleteVersions(versions[fromLength:int64(maxLength/2)]...) - require.NoError(err, "DeleteVersions should not fail") - - databaseSizeDeleteVersions = mdb.Stats()["database.size"] - } - - require.Equal(databaseSizeDeleteVersion, databaseSizeDeleteVersionsRange) - require.Equal(databaseSizeDeleteVersion, databaseSizeDeleteVersions) -} - // BENCHMARKS func BenchmarkTreeLoadAndDelete(b *testing.B) { @@ -1708,7 +1529,7 @@ func BenchmarkTreeLoadAndDelete(b *testing.B) { // efficient deletes, we are golden. for v := 0; v < numVersions/10; v++ { version := (iavlrand.RandInt() % numVersions) + 1 - tree.DeleteVersion(int64(version)) + tree.DeleteVersionsTo(int64(version)) } } }) @@ -1748,7 +1569,7 @@ func TestLoadVersionForOverwritingCase2(t *testing.T) { } } - _, err = tree.LoadVersionForOverwriting(1) + err = tree.LoadVersionForOverwriting(1) require.NoError(err, "LoadVersionForOverwriting should not fail") for i := byte(0); i < 20; i++ { @@ -1767,7 +1588,7 @@ func TestLoadVersionForOverwritingCase2(t *testing.T) { _, _, err = tree.SaveVersion() require.NoError(err, "SaveVersion should not fail") - err = tree.DeleteVersion(1) + err = tree.DeleteVersionsTo(1) require.NoError(err, "DeleteVersion should not fail") tree.Set([]byte{0x1}, []byte{0x3}) @@ -1810,7 +1631,7 @@ func TestLoadVersionForOverwritingCase3(t *testing.T) { _, _, err = tree.SaveVersion() require.NoError(err) - _, err = tree.LoadVersionForOverwriting(1) + err = tree.LoadVersionForOverwriting(1) require.NoError(err) for _, n := range removedNodes { has, err := tree.ndb.Has(n.hash)