From 85ae1440b567e4179d6379de3510f344663d1175 Mon Sep 17 00:00:00 2001 From: HuangYi Date: Wed, 30 Nov 2022 14:49:35 +0800 Subject: [PATCH] feat: speed up rollback command - fully rebuild fast node indexes after rollback, rather than leave it in dirty state. - skip unnecessary subtree traversal when deleting the latest version. - restrict the iteration range when deleting orphans. --- CHANGELOG.md | 1 + mutable_tree.go | 19 +++++++------------ nodedb.go | 34 +++++++++++----------------------- 3 files changed, 19 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dad0076bf..8e3ee9704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#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`. ## 0.19.4 (October 28, 2022) diff --git a/mutable_tree.go b/mutable_tree.go index 4924264cd..c45eecd5f 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -632,17 +632,18 @@ func (tree *MutableTree) LoadVersionForOverwriting(targetVersion int64) (int64, return latestVersion, err } + tree.mtx.Lock() + defer tree.mtx.Unlock() + + tree.ndb.resetLatestVersion(latestVersion) + if !tree.skipFastStorageUpgrade { - if err := tree.enableFastStorageAndCommitLocked(); err != nil { + // it'll repopulates the fast node index because of version mismatch. + if _, err := tree.enableFastStorageAndCommitIfNotEnabled(); err != nil { return latestVersion, err } } - tree.ndb.resetLatestVersion(latestVersion) - - tree.mtx.Lock() - defer tree.mtx.Unlock() - for v := range tree.versions { if v > targetVersion { delete(tree.versions, v) @@ -708,12 +709,6 @@ func (tree *MutableTree) enableFastStorageAndCommitIfNotEnabled() (bool, error) return true, nil } -func (tree *MutableTree) enableFastStorageAndCommitLocked() error { - tree.mtx.Lock() - defer tree.mtx.Unlock() - return tree.enableFastStorageAndCommit() -} - func (tree *MutableTree) enableFastStorageAndCommit() error { var err error diff --git a/nodedb.go b/nodedb.go index 2d00d01ac..83d776a86 100644 --- a/nodedb.go +++ b/nodedb.go @@ -35,6 +35,7 @@ const ( defaultStorageVersionValue = "1.0.0" fastStorageVersionValue = "1.1.0" fastNodeCacheSize = 100000 + maxVersion = int64(math.MaxInt64) ) var ( @@ -452,7 +453,7 @@ 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) - err = ndb.traverseOrphans(func(key, hash []byte) error { + err = ndb.traverseRange(orphanKeyFormat.Key(version-1), orphanKeyFormat.Key(maxVersion), func(key, hash []byte) error { var fromVersion, toVersion int64 orphanKeyFormat.Scan(key, &toVersion, &fromVersion) @@ -477,7 +478,7 @@ func (ndb *nodeDB) DeleteVersionsFrom(version int64) error { } // Delete the version root entries - err = ndb.traverseRange(rootKeyFormat.Key(version), rootKeyFormat.Key(int64(math.MaxInt64)), func(k, v []byte) error { + err = ndb.traverseRange(rootKeyFormat.Key(version), rootKeyFormat.Key(maxVersion), func(k, v []byte) error { if err = ndb.batch.Delete(k); err != nil { return err } @@ -488,26 +489,7 @@ func (ndb *nodeDB) DeleteVersionsFrom(version int64) error { return err } - // Delete fast node entries - err = ndb.traverseFastNodes(func(keyWithPrefix, v []byte) error { - key := keyWithPrefix[1:] - fastNode, err := fastnode.DeserializeNode(key, v) - if err != nil { - return err - } - - if version <= fastNode.GetVersionLastUpdatedAt() { - if err = ndb.batch.Delete(keyWithPrefix); err != nil { - return err - } - ndb.fastNodeCache.Remove(key) - } - return nil - }) - - if err != nil { - return err - } + // NOTICE: we don't touch fast node indexes here, because it'll be rebuilt later because of version mismatch. return nil } @@ -605,6 +587,11 @@ func (ndb *nodeDB) deleteNodesFrom(version int64, hash []byte) error { 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 @@ -725,7 +712,7 @@ func (ndb *nodeDB) rootKey(version int64) []byte { func (ndb *nodeDB) getLatestVersion() (int64, error) { if ndb.latestVersion == 0 { var err error - ndb.latestVersion, err = ndb.getPreviousVersion(1<<63 - 1) + ndb.latestVersion, err = ndb.getPreviousVersion(maxVersion) if err != nil { return 0, err } @@ -789,6 +776,7 @@ func (ndb *nodeDB) traverseOrphans(fn func(keyWithPrefix, v []byte) error) error } // Traverse fast nodes and return error if any, nil otherwise +// nolint: unused func (ndb *nodeDB) traverseFastNodes(fn func(k, v []byte) error) error { return ndb.traversePrefix(fastKeyFormat.Key(), fn) }