From 7c66a6cadca0856ff5f07ae426d6434a6fc9384f Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Fri, 22 Dec 2023 16:07:50 -0700 Subject: [PATCH 01/16] orphan change --- batch.go | 9 +++++++++ go.mod | 4 ++-- nodedb.go | 37 ++++++++++++++++++++++++++++++++++--- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/batch.go b/batch.go index b25e879a2..a7e5e20b0 100644 --- a/batch.go +++ b/batch.go @@ -1,6 +1,8 @@ package iavl import ( + "sync" + dbm "github.com/cosmos/cosmos-db" ) @@ -11,6 +13,7 @@ type BatchWithFlusher struct { db dbm.DB // This is only used to create new batch batch dbm.Batch // Batched writing buffer. + mtx sync.Mutex flushThreshold int // The threshold to flush the batch to disk. } @@ -46,6 +49,9 @@ func (b *BatchWithFlusher) estimateSizeAfterSetting(key []byte, value []byte) (i // the batch is flushed to disk, cleared, and a new one is created with buffer pre-allocated to threshold. // The addition entry is then added to the batch. func (b *BatchWithFlusher) Set(key, value []byte) error { + b.mtx.Lock() + defer b.mtx.Unlock() + batchSizeAfter, err := b.estimateSizeAfterSetting(key, value) if err != nil { return err @@ -67,6 +73,9 @@ func (b *BatchWithFlusher) Set(key, value []byte) error { // the batch is flushed to disk, cleared, and a new one is created with buffer pre-allocated to threshold. // The deletion entry is then added to the batch. func (b *BatchWithFlusher) Delete(key []byte) error { + b.mtx.Lock() + defer b.mtx.Unlock() + batchSizeAfter, err := b.estimateSizeAfterSetting(key, []byte{}) if err != nil { return err diff --git a/go.mod b/go.mod index b38602555..356a691a7 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/emicklei/dot v1.4.2 github.com/golang/mock v1.6.0 github.com/stretchr/testify v1.8.4 - google.golang.org/protobuf v1.30.0 golang.org/x/crypto v0.12.0 + google.golang.org/protobuf v1.30.0 ) require ( @@ -49,8 +49,8 @@ require ( ) retract ( - v0.18.0 // This version is not used by the Cosmos SDK and adds a maintenance burden. // Use v1.x.x instead. [v0.21.0, v0.21.2] + v0.18.0 ) diff --git a/nodedb.go b/nodedb.go index d446b7c1d..d7852574f 100644 --- a/nodedb.go +++ b/nodedb.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "sync" + "time" "cosmossdk.io/log" dbm "github.com/cosmos/cosmos-db" @@ -470,9 +471,39 @@ func (ndb *nodeDB) deleteLegacyVersions() error { ndb.legacyLatestVersion = -1 // Delete all orphan nodes of the legacy versions - return ndb.traversePrefix(legacyOrphanKeyFormat.Key(), func(key, value []byte) error { - return ndb.batch.Delete(key) - }) + go func() { + if err := ndb.deleteOrphans(); err != nil { + ndb.logger.Error("failed to clean legacy orphans", "err", err) + } + }() + + return nil +} + +// deleteOrphans cleans all legacy orphans from the nodeDB. +func (ndb *nodeDB) deleteOrphans() error { + itr, err := dbm.IteratePrefix(ndb.db, legacyOrphanKeyFormat.Key()) + if err != nil { + return err + } + defer itr.Close() + + count := 0 + for ; itr.Valid(); itr.Next() { + if err := ndb.batch.Delete(itr.Key()); err != nil { + return err + } + + // Sleep for a while to avoid blocking the main thread i/o. + count++ + if count > 1000 { + count = 0 + time.Sleep(100 * time.Millisecond) + } + + } + + return nil } // DeleteVersionsFrom permanently deletes all tree versions from the given version upwards. From d8c630db39657106d6fb0dc02564e0401f4eb3e3 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Fri, 22 Dec 2023 17:22:44 -0700 Subject: [PATCH 02/16] attempt at fix orphan concurrency --- nodedb.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/nodedb.go b/nodedb.go index d7852574f..b809420ba 100644 --- a/nodedb.go +++ b/nodedb.go @@ -436,14 +436,17 @@ func (ndb *nodeDB) deleteLegacyVersions() error { legacyRootKeyFormat.Scan(itr.Key(), &curVersion) rootKeys = append(rootKeys, itr.Key()) if prevVersion > 0 { - if err := ndb.traverseOrphans(prevVersion, curVersion, func(orphan *Node) error { - return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) - }); err != nil { - return err - } + go func(prevVersion, curVersion int64) { + if err := ndb.traverseOrphans(prevVersion, curVersion, func(orphan *Node) error { + return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) + }); err != nil { + ndb.logger.Error("failed to delete orphans", "err", err) + } + }(prevVersion, curVersion) } prevVersion = curVersion } + // Delete the last version for the legacyLastVersion if curVersion > 0 { legacyLatestVersion, err := ndb.getLegacyLatestVersion() @@ -453,11 +456,13 @@ func (ndb *nodeDB) deleteLegacyVersions() error { if curVersion != legacyLatestVersion { return fmt.Errorf("expected legacyLatestVersion to be %d, got %d", legacyLatestVersion, curVersion) } - if err := ndb.traverseOrphans(curVersion, curVersion+1, func(orphan *Node) error { - return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) - }); err != nil { - return err - } + go func(curVersion int64) { + if err := ndb.traverseOrphans(curVersion, curVersion+1, func(orphan *Node) error { + return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) + }); err != nil { + ndb.logger.Error("failed to delete orphans", "err", err) + } + }(curVersion) } // Delete all roots of the legacy versions From 36069e607d2c8061946aa65098e3fd3ecdca63d9 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Fri, 22 Dec 2023 17:33:10 -0700 Subject: [PATCH 03/16] Revert "attempt at fix orphan concurrency" This reverts commit d8c630db39657106d6fb0dc02564e0401f4eb3e3. --- nodedb.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/nodedb.go b/nodedb.go index b809420ba..d7852574f 100644 --- a/nodedb.go +++ b/nodedb.go @@ -436,17 +436,14 @@ func (ndb *nodeDB) deleteLegacyVersions() error { legacyRootKeyFormat.Scan(itr.Key(), &curVersion) rootKeys = append(rootKeys, itr.Key()) if prevVersion > 0 { - go func(prevVersion, curVersion int64) { - if err := ndb.traverseOrphans(prevVersion, curVersion, func(orphan *Node) error { - return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) - }); err != nil { - ndb.logger.Error("failed to delete orphans", "err", err) - } - }(prevVersion, curVersion) + if err := ndb.traverseOrphans(prevVersion, curVersion, func(orphan *Node) error { + return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) + }); err != nil { + return err + } } prevVersion = curVersion } - // Delete the last version for the legacyLastVersion if curVersion > 0 { legacyLatestVersion, err := ndb.getLegacyLatestVersion() @@ -456,13 +453,11 @@ func (ndb *nodeDB) deleteLegacyVersions() error { if curVersion != legacyLatestVersion { return fmt.Errorf("expected legacyLatestVersion to be %d, got %d", legacyLatestVersion, curVersion) } - go func(curVersion int64) { - if err := ndb.traverseOrphans(curVersion, curVersion+1, func(orphan *Node) error { - return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) - }); err != nil { - ndb.logger.Error("failed to delete orphans", "err", err) - } - }(curVersion) + if err := ndb.traverseOrphans(curVersion, curVersion+1, func(orphan *Node) error { + return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) + }); err != nil { + return err + } } // Delete all roots of the legacy versions From 283e4ee3e45436efd7b85085aa00a6781238ce6e Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 22 Dec 2023 20:59:42 -0600 Subject: [PATCH 04/16] Dev orphan code --- nodedb.go | 99 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/nodedb.go b/nodedb.go index d7852574f..386f2162d 100644 --- a/nodedb.go +++ b/nodedb.go @@ -422,58 +422,73 @@ func (ndb *nodeDB) deleteLegacyNodes(version int64, nk []byte) error { // deleteLegacyVersions deletes all legacy versions from disk. func (ndb *nodeDB) deleteLegacyVersions() error { - // Check if we have a legacy version - itr, err := dbm.IteratePrefix(ndb.db, legacyRootKeyFormat.Key()) - if err != nil { - return err - } - defer itr.Close() - - // Delete orphans for all legacy versions - var prevVersion, curVersion int64 - var rootKeys [][]byte - for ; itr.Valid(); itr.Next() { - legacyRootKeyFormat.Scan(itr.Key(), &curVersion) - rootKeys = append(rootKeys, itr.Key()) - if prevVersion > 0 { - if err := ndb.traverseOrphans(prevVersion, curVersion, func(orphan *Node) error { - return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) - }); err != nil { - return err - } - } - prevVersion = curVersion - } - // Delete the last version for the legacyLastVersion - if curVersion > 0 { - legacyLatestVersion, err := ndb.getLegacyLatestVersion() + go func() { + // Check if we have a legacy version + itr, err := dbm.IteratePrefix(ndb.db, legacyRootKeyFormat.Key()) if err != nil { - return err + ndb.logger.Error(err.Error()) + return } - if curVersion != legacyLatestVersion { - return fmt.Errorf("expected legacyLatestVersion to be %d, got %d", legacyLatestVersion, curVersion) + defer itr.Close() + + // Delete orphans for all legacy versions + var prevVersion, curVersion int64 + var rootKeys [][]byte + counter := 0 + for ; itr.Valid(); itr.Next() { + legacyRootKeyFormat.Scan(itr.Key(), &curVersion) + rootKeys = append(rootKeys, itr.Key()) + if prevVersion > 0 { + if err := ndb.traverseOrphans(prevVersion, curVersion, func(orphan *Node) error { + counter += 1 + if counter == 1000 { + counter = 0 + time.Sleep(100 * time.Millisecond) + fmt.Println("IAVL sleep happening") + } + return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) + }); err != nil { + ndb.logger.Error(err.Error()) + return + } + } + prevVersion = curVersion } - if err := ndb.traverseOrphans(curVersion, curVersion+1, func(orphan *Node) error { - return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) - }); err != nil { - return err + // Delete the last version for the legacyLastVersion + if curVersion > 0 { + legacyLatestVersion, err := ndb.getLegacyLatestVersion() + if err != nil { + ndb.logger.Error(err.Error()) + return + } + if curVersion != legacyLatestVersion { + ndb.logger.Error("expected legacyLatestVersion to be %d, got %d", legacyLatestVersion, curVersion) + return + } + if err := ndb.traverseOrphans(curVersion, curVersion+1, func(orphan *Node) error { + return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) + }); err != nil { + ndb.logger.Error("failed to clean legacy orphans between versions", "err", err) + return + } } - } - // Delete all roots of the legacy versions - for _, rootKey := range rootKeys { - if err := ndb.batch.Delete(rootKey); err != nil { - return err + // Delete all roots of the legacy versions + for _, rootKey := range rootKeys { + if err := ndb.batch.Delete(rootKey); err != nil { + ndb.logger.Error("failed to clean legacy orphans root keys", "err", err) + return + } } - } - // Initialize the legacy latest version to -1 to demonstrate that all legacy versions have been deleted - ndb.legacyLatestVersion = -1 + // Initialize the legacy latest version to -1 to demonstrate that all legacy versions have been deleted + ndb.legacyLatestVersion = -1 - // Delete all orphan nodes of the legacy versions - go func() { + // Delete all orphan nodes of the legacy versions + // TODO: Is this just deadcode????? if err := ndb.deleteOrphans(); err != nil { ndb.logger.Error("failed to clean legacy orphans", "err", err) + return } }() From f983c8e704656c1deb1d8f21e4b775864b3c423f Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 22 Dec 2023 21:16:10 -0600 Subject: [PATCH 05/16] tune params --- nodedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodedb.go b/nodedb.go index 386f2162d..541b1e378 100644 --- a/nodedb.go +++ b/nodedb.go @@ -443,7 +443,7 @@ func (ndb *nodeDB) deleteLegacyVersions() error { counter += 1 if counter == 1000 { counter = 0 - time.Sleep(100 * time.Millisecond) + time.Sleep(1000 * time.Millisecond) fmt.Println("IAVL sleep happening") } return ndb.batch.Delete(ndb.nodeKey(orphan.GetKey())) From 55709735f6b0b12162609ee96567e084e30bb7f8 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 22 Dec 2023 21:35:29 -0600 Subject: [PATCH 06/16] Fix stacking --- nodedb.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/nodedb.go b/nodedb.go index 541b1e378..843725b7a 100644 --- a/nodedb.go +++ b/nodedb.go @@ -420,9 +420,27 @@ func (ndb *nodeDB) deleteLegacyNodes(version int64, nk []byte) error { return ndb.batch.Delete(ndb.legacyNodeKey(nk)) } +var isDeletingLegacyVersionsMutex *sync.Mutex +var isDeletingLegacyVersions bool = false + // deleteLegacyVersions deletes all legacy versions from disk. func (ndb *nodeDB) deleteLegacyVersions() error { + isDeletingLegacyVersionsMutex.Lock() + if isDeletingLegacyVersions { + isDeletingLegacyVersionsMutex.Unlock() + return nil + } else { + isDeletingLegacyVersions = true + isDeletingLegacyVersionsMutex.Unlock() + } + go func() { + defer func() { + isDeletingLegacyVersionsMutex.Lock() + isDeletingLegacyVersions = false + isDeletingLegacyVersionsMutex.Unlock() + }() + // Check if we have a legacy version itr, err := dbm.IteratePrefix(ndb.db, legacyRootKeyFormat.Key()) if err != nil { From ae885e64439f3758554ad439e90e28c7afb30ea0 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Fri, 22 Dec 2023 21:55:38 -0600 Subject: [PATCH 07/16] Fix --- nodedb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodedb.go b/nodedb.go index 843725b7a..5194a85e0 100644 --- a/nodedb.go +++ b/nodedb.go @@ -420,7 +420,7 @@ func (ndb *nodeDB) deleteLegacyNodes(version int64, nk []byte) error { return ndb.batch.Delete(ndb.legacyNodeKey(nk)) } -var isDeletingLegacyVersionsMutex *sync.Mutex +var isDeletingLegacyVersionsMutex *sync.Mutex = &sync.Mutex{} var isDeletingLegacyVersions bool = false // deleteLegacyVersions deletes all legacy versions from disk. From ec431e9bde62c338f03377fc0c994943b6270a83 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Sun, 24 Dec 2023 12:35:24 -0600 Subject: [PATCH 08/16] Use 32Byte encoding + make legacy encoding improvements --- internal/encoding/encoding.go | 17 ++++++++++ mutable_tree.go | 59 +++++++++++++++++++---------------- node.go | 32 +++++++++++++------ 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go index 17a994bc1..e198a7937 100644 --- a/internal/encoding/encoding.go +++ b/internal/encoding/encoding.go @@ -97,6 +97,23 @@ func EncodeBytes(w io.Writer, bz []byte) error { return err } +var hashLenBz []byte + +func init() { + hashLenBz = make([]byte, 1) + binary.PutUvarint(hashLenBz, 32) +} + +// Encode 32 byte long hash +func Encode32BytesHash(w io.Writer, bz []byte) error { + _, err := w.Write(hashLenBz) + if err != nil { + return err + } + _, err = w.Write(bz) + return err +} + // encodeBytesSlice length-prefixes the byte slice and returns it. func EncodeBytesSlice(bz []byte) ([]byte, error) { buf := bufPool.Get().(*bytes.Buffer) diff --git a/mutable_tree.go b/mutable_tree.go index e65dbd109..f342c04e0 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -266,34 +266,8 @@ func (tree *MutableTree) set(key []byte, value []byte) (updated bool, err error) func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) ( newSelf *Node, updated bool, err error, ) { - version := tree.version + 1 - if node.isLeaf() { - if !tree.skipFastStorageUpgrade { - tree.addUnsavedAddition(key, fastnode.NewNode(key, value, version)) - } - switch bytes.Compare(key, node.key) { - case -1: // setKey < leafKey - return &Node{ - key: node.key, - subtreeHeight: 1, - size: 2, - nodeKey: nil, - leftNode: NewNode(key, value), - rightNode: node, - }, false, nil - case 1: // setKey > leafKey - return &Node{ - key: key, - subtreeHeight: 1, - size: 2, - nodeKey: nil, - leftNode: node, - rightNode: NewNode(key, value), - }, false, nil - default: - return NewNode(key, value), true, nil - } + return tree.recursiveSetLeaf(node, key, value) } else { node, err = node.clone(tree) if err != nil { @@ -327,6 +301,37 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) ( } } +func (tree *MutableTree) recursiveSetLeaf(node *Node, key []byte, value []byte) ( + newSelf *Node, updated bool, err error, +) { + version := tree.version + 1 + if !tree.skipFastStorageUpgrade { + tree.addUnsavedAddition(key, fastnode.NewNode(key, value, version)) + } + switch bytes.Compare(key, node.key) { + case -1: // setKey < leafKey + return &Node{ + key: node.key, + subtreeHeight: 1, + size: 2, + nodeKey: nil, + leftNode: NewNode(key, value), + rightNode: node, + }, false, nil + case 1: // setKey > leafKey + return &Node{ + key: key, + subtreeHeight: 1, + size: 2, + nodeKey: nil, + leftNode: node, + rightNode: NewNode(key, value), + }, false, nil + default: + return NewNode(key, value), true, nil + } +} + // 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) { diff --git a/node.go b/node.go index 97d54a865..0f1bc0542 100644 --- a/node.go +++ b/node.go @@ -57,11 +57,15 @@ func GetRootKey(version int64) []byte { // Node represents a node in a Tree. type Node struct { - key []byte - value []byte - hash []byte - nodeKey *NodeKey - leftNodeKey []byte + key []byte + value []byte + hash []byte + nodeKey *NodeKey + // Legacy: LeftNodeHash + // v1: Left node ptr via Version/key + leftNodeKey []byte + // Legacy: RightNodeHash + // v1: Right node ptr via Version/key rightNodeKey []byte size int64 leftNode *Node @@ -517,19 +521,29 @@ func (node *Node) writeHashBytes(w io.Writer, version int64) error { // (e.g. ProofLeafNode.ValueHash) valueHash := sha256.Sum256(node.value) - err = encoding.EncodeBytes(w, valueHash[:]) + err = encoding.Encode32BytesHash(w, valueHash[:]) if err != nil { return fmt.Errorf("writing value, %w", err) } } else { - if node.leftNode == nil || node.rightNode == nil { + if (node.leftNode == nil && len(node.leftNodeKey) != 32) || (node.rightNode == nil && len(node.rightNodeKey) != 32) { return ErrEmptyChild } - err = encoding.EncodeBytes(w, node.leftNode.hash) + // If left/rightNodeKey is 32 bytes, it is a legacy node whose value is just the hash. + // We may have skipped fetching leftNode/rightNode. + if len(node.leftNodeKey) == 32 { + err = encoding.Encode32BytesHash(w, node.leftNodeKey) + } else { + err = encoding.Encode32BytesHash(w, node.leftNode.hash) + } if err != nil { return fmt.Errorf("writing left hash, %w", err) } - err = encoding.EncodeBytes(w, node.rightNode.hash) + if len(node.rightNodeKey) == 32 { + err = encoding.Encode32BytesHash(w, node.rightNodeKey) + } else { + err = encoding.Encode32BytesHash(w, node.rightNode.hash) + } if err != nil { return fmt.Errorf("writing right hash, %w", err) } From 49beaf02f6ced2e53b01ffd7473765dcb18a4311 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Sat, 23 Dec 2023 11:27:27 -0600 Subject: [PATCH 09/16] Speedup key formatting --- keyformat/prefix_formatter.go | 41 +++++++++++++++++++++++++++++++++++ nodedb.go | 18 +++++++-------- 2 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 keyformat/prefix_formatter.go diff --git a/keyformat/prefix_formatter.go b/keyformat/prefix_formatter.go new file mode 100644 index 000000000..8f345a1a6 --- /dev/null +++ b/keyformat/prefix_formatter.go @@ -0,0 +1,41 @@ +package keyformat + +import "encoding/binary" + +// This file builds some dedicated key formatters for what appears in benchmarks. + +// Prefixes a single byte before a 32 byte hash. +type FastPrefixFormatter struct { + prefix byte + length int + prefixSlice []byte +} + +func NewFastPrefixFormatter(prefix byte, length int) *FastPrefixFormatter { + return &FastPrefixFormatter{prefix: prefix, length: length, prefixSlice: []byte{prefix}} +} + +func (f *FastPrefixFormatter) Key(bz []byte) []byte { + key := make([]byte, 1+f.length) + key[0] = f.prefix + copy(key[1:], bz) + return key +} + +func (f *FastPrefixFormatter) Scan(key []byte, a interface{}) { + scan(a, key[1:]) +} + +func (f *FastPrefixFormatter) KeyInt64(bz int64) []byte { + key := make([]byte, 1+f.length) + key[0] = f.prefix + binary.BigEndian.PutUint64(key[1:], uint64(bz)) + return key +} + +func (f *FastPrefixFormatter) Prefix() []byte { + return f.prefixSlice +} +func (f *FastPrefixFormatter) Length() int { + return 1 + f.length +} diff --git a/nodedb.go b/nodedb.go index 5194a85e0..228a1aeef 100644 --- a/nodedb.go +++ b/nodedb.go @@ -41,10 +41,10 @@ const ( var ( // All new node keys are prefixed with the byte 's'. This ensures no collision is // possible with the legacy nodes, and makes them easier to traverse. They are indexed by the version and the local nonce. - nodeKeyFormat = keyformat.NewKeyFormat('s', int64Size+int32Size) // s + nodeKeyFormat = keyformat.NewFastPrefixFormatter('s', int64Size+int32Size) // s // This is only used for the iteration purpose. - nodeKeyPrefixFormat = keyformat.NewKeyFormat('s', int64Size) // s + nodeKeyPrefixFormat = keyformat.NewFastPrefixFormatter('s', int64Size) // s // 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. @@ -59,7 +59,7 @@ var ( metadataKeyFormat = keyformat.NewKeyFormat('m', 0) // m // All legacy node keys are prefixed with the byte 'n'. - legacyNodeKeyFormat = keyformat.NewKeyFormat('n', hashSize) // n + legacyNodeKeyFormat = keyformat.NewFastPrefixFormatter('n', hashSize) // n // All legacy orphan keys are prefixed with the byte 'o'. legacyOrphanKeyFormat = keyformat.NewKeyFormat('o', int64Size, int64Size, hashSize) // o @@ -584,7 +584,7 @@ func (ndb *nodeDB) DeleteVersionsFrom(fromVersion int64) error { } // Delete the nodes for new format - err = ndb.traverseRange(nodeKeyPrefixFormat.Key(fromVersion), nodeKeyPrefixFormat.Key(latest+1), func(k, v []byte) error { + err = ndb.traverseRange(nodeKeyPrefixFormat.KeyInt64(fromVersion), nodeKeyPrefixFormat.KeyInt64(latest+1), func(k, v []byte) error { return ndb.batch.Delete(k) }) @@ -664,7 +664,7 @@ func (ndb *nodeDB) nodeKey(nk []byte) []byte { } func (ndb *nodeDB) nodeKeyPrefix(version int64) []byte { - return nodeKeyPrefixFormat.Key(version) + return nodeKeyPrefixFormat.KeyInt64(version) } func (ndb *nodeDB) fastNodeKey(key []byte) []byte { @@ -760,8 +760,8 @@ func (ndb *nodeDB) getLatestVersion() (int64, error) { } itr, err := ndb.db.ReverseIterator( - nodeKeyPrefixFormat.Key(int64(1)), - nodeKeyPrefixFormat.Key(int64(math.MaxInt64)), + nodeKeyPrefixFormat.KeyInt64(int64(1)), + nodeKeyPrefixFormat.KeyInt64(int64(math.MaxInt64)), ) if err != nil { return 0, err @@ -1080,7 +1080,7 @@ func isReferenceToRoot(bz []byte) bool { func (ndb *nodeDB) traverseNodes(fn func(node *Node) error) error { nodes := []*Node{} - if err := ndb.traversePrefix(nodeKeyFormat.Key(), func(key, value []byte) error { + if err := ndb.traversePrefix(nodeKeyFormat.Prefix(), func(key, value []byte) error { if isReferenceToRoot(value) { return nil } @@ -1163,7 +1163,7 @@ func (ndb *nodeDB) String() (string, error) { index := 0 - err := ndb.traversePrefix(nodeKeyFormat.Key(), func(key, value []byte) error { + err := ndb.traversePrefix(nodeKeyFormat.Prefix(), func(key, value []byte) error { fmt.Fprintf(buf, "%s: %x\n", key, value) return nil }) From b589c452c8333b0d3c75a8c4c523eac577fa9232 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Sun, 24 Dec 2023 11:12:31 -0600 Subject: [PATCH 10/16] Avoid making an extra heap copy in DecodeBytes --- fastnode/fast_node.go | 1 + internal/encoding/encoding.go | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/fastnode/fast_node.go b/fastnode/fast_node.go index 149ac9b26..5d00bd997 100644 --- a/fastnode/fast_node.go +++ b/fastnode/fast_node.go @@ -30,6 +30,7 @@ func NewNode(key []byte, value []byte, version int64) *Node { } // DeserializeNode constructs an *FastNode from an encoded byte slice. +// It assumes we do not mutate this input []byte. func DeserializeNode(key []byte, buf []byte) (*Node, error) { ver, n, err := encoding.DecodeVarint(buf) if err != nil { diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go index e198a7937..1ebdd53f1 100644 --- a/internal/encoding/encoding.go +++ b/internal/encoding/encoding.go @@ -30,6 +30,7 @@ var uvarintPool = &sync.Pool{ // decodeBytes decodes a varint length-prefixed byte slice, returning it along with the number // of input bytes read. +// Assumes bz will not be mutated. func DecodeBytes(bz []byte) ([]byte, int, error) { s, n, err := DecodeUvarint(bz) if err != nil { @@ -51,9 +52,9 @@ func DecodeBytes(bz []byte) ([]byte, int, error) { if len(bz) < end { return nil, n, fmt.Errorf("insufficient bytes decoding []byte of length %v", size) } - bz2 := make([]byte, size) - copy(bz2, bz[n:end]) - return bz2, end, nil + // bz2 := make([]byte, size) + // copy(bz2, bz[n:end]) + return bz[n:end], end, nil } // decodeUvarint decodes a varint-encoded unsigned integer from a byte slice, returning it and the From 4ea1f0ade4c18d3d9dd224fd6e9ce402e207b7af Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Tue, 30 Jan 2024 18:49:04 -0700 Subject: [PATCH 11/16] lints --- .github/workflows/lint.yml | 12 ++++++------ nodedb.go | 13 +++++++------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4935210c8..1e03feecc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -12,11 +12,11 @@ jobs: name: golangci-lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 + - name: Check out repository code + uses: actions/checkout@v4 + - name: 🐿 Setup Golang + uses: actions/setup-go@v4 with: - go-version: '^1.20.0' + go-version: 1.21 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: v1.51.2 + run: make lint diff --git a/nodedb.go b/nodedb.go index 228a1aeef..0dfe45464 100644 --- a/nodedb.go +++ b/nodedb.go @@ -420,8 +420,10 @@ func (ndb *nodeDB) deleteLegacyNodes(version int64, nk []byte) error { return ndb.batch.Delete(ndb.legacyNodeKey(nk)) } -var isDeletingLegacyVersionsMutex *sync.Mutex = &sync.Mutex{} -var isDeletingLegacyVersions bool = false +var ( + isDeletingLegacyVersionsMutex = &sync.Mutex{} + isDeletingLegacyVersions = false +) // deleteLegacyVersions deletes all legacy versions from disk. func (ndb *nodeDB) deleteLegacyVersions() error { @@ -429,10 +431,9 @@ func (ndb *nodeDB) deleteLegacyVersions() error { if isDeletingLegacyVersions { isDeletingLegacyVersionsMutex.Unlock() return nil - } else { - isDeletingLegacyVersions = true - isDeletingLegacyVersionsMutex.Unlock() } + isDeletingLegacyVersions = true + isDeletingLegacyVersionsMutex.Unlock() go func() { defer func() { @@ -458,7 +459,7 @@ func (ndb *nodeDB) deleteLegacyVersions() error { rootKeys = append(rootKeys, itr.Key()) if prevVersion > 0 { if err := ndb.traverseOrphans(prevVersion, curVersion, func(orphan *Node) error { - counter += 1 + counter++ if counter == 1000 { counter = 0 time.Sleep(1000 * time.Millisecond) From 1f475983378092f5231e820e10a6fbf2cdd47dc9 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Tue, 30 Jan 2024 18:50:25 -0700 Subject: [PATCH 12/16] lint --- keyformat/prefix_formatter.go | 1 + 1 file changed, 1 insertion(+) diff --git a/keyformat/prefix_formatter.go b/keyformat/prefix_formatter.go index 8f345a1a6..873c3a7e1 100644 --- a/keyformat/prefix_formatter.go +++ b/keyformat/prefix_formatter.go @@ -36,6 +36,7 @@ func (f *FastPrefixFormatter) KeyInt64(bz int64) []byte { func (f *FastPrefixFormatter) Prefix() []byte { return f.prefixSlice } + func (f *FastPrefixFormatter) Length() int { return 1 + f.length } From cd45c0c705fb7933743847063faf2f9fc3e1784b Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Tue, 30 Jan 2024 19:18:17 -0700 Subject: [PATCH 13/16] lint --- mutable_tree.go | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/mutable_tree.go b/mutable_tree.go index f342c04e0..358a366e1 100644 --- a/mutable_tree.go +++ b/mutable_tree.go @@ -268,37 +268,36 @@ func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) ( ) { if node.isLeaf() { return tree.recursiveSetLeaf(node, key, value) - } else { - node, err = node.clone(tree) - if err != nil { - return nil, false, err - } - - if bytes.Compare(key, node.key) < 0 { - node.leftNode, updated, err = tree.recursiveSet(node.leftNode, key, value) - if err != nil { - return nil, updated, err - } - } else { - node.rightNode, updated, err = tree.recursiveSet(node.rightNode, key, value) - if err != nil { - return nil, updated, err - } - } + } + node, err = node.clone(tree) + if err != nil { + return nil, false, err + } - if updated { - return node, updated, nil - } - err = node.calcHeightAndSize(tree.ImmutableTree) + if bytes.Compare(key, node.key) < 0 { + node.leftNode, updated, err = tree.recursiveSet(node.leftNode, key, value) if err != nil { - return nil, false, err + return nil, updated, err } - newNode, err := tree.balance(node) + } else { + node.rightNode, updated, err = tree.recursiveSet(node.rightNode, key, value) if err != nil { - return nil, false, err + return nil, updated, err } - return newNode, updated, err } + + if updated { + return node, updated, nil + } + err = node.calcHeightAndSize(tree.ImmutableTree) + if err != nil { + return nil, false, err + } + newNode, err := tree.balance(node) + if err != nil { + return nil, false, err + } + return newNode, updated, err } func (tree *MutableTree) recursiveSetLeaf(node *Node, key []byte, value []byte) ( From 24512229e2a445fb37e93b151217624e24e0cc21 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Tue, 30 Jan 2024 19:40:29 -0700 Subject: [PATCH 14/16] clean up --- internal/encoding/encoding.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/encoding/encoding.go b/internal/encoding/encoding.go index 1ebdd53f1..78a4f9899 100644 --- a/internal/encoding/encoding.go +++ b/internal/encoding/encoding.go @@ -52,8 +52,6 @@ func DecodeBytes(bz []byte) ([]byte, int, error) { if len(bz) < end { return nil, n, fmt.Errorf("insufficient bytes decoding []byte of length %v", size) } - // bz2 := make([]byte, size) - // copy(bz2, bz[n:end]) return bz[n:end], end, nil } From 8b53289e2f724d510d7bc992dd3778004378398a Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Wed, 31 Jan 2024 22:09:51 -0700 Subject: [PATCH 15/16] changelog entry --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce47b3094..35c2c43f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Improvements + +- [#876](https://github.com/cosmos/iavl/pull/876) Make pruning of orphan nodes synchronous. + ## v1.0.0 (October 30, 2023) ### Improvements From 46609fe2688b749a0b45fbde66d68e731485d18d Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Wed, 7 Feb 2024 13:56:50 -0600 Subject: [PATCH 16/16] Update CHANGELOG.md Co-authored-by: cool-developer <51834436+cool-develope@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35c2c43f6..df5d7f7d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Improvements -- [#876](https://github.com/cosmos/iavl/pull/876) Make pruning of orphan nodes synchronous. +- [#876](https://github.com/cosmos/iavl/pull/876) Make pruning of legacy orphan nodes asynchronous. ## v1.0.0 (October 30, 2023)