Skip to content

Commit e1fa67e

Browse files
czarcas7icValarDragoncool-develope
authored
feat(v1.x.x): async pruning of orphan nodes (#876)
Co-authored-by: Dev Ojha <dojha@berkeley.edu> Co-authored-by: cool-developer <51834436+cool-develope@users.noreply.github.com>
1 parent b0d383c commit e1fa67e

File tree

10 files changed

+275
-118
lines changed

10 files changed

+275
-118
lines changed

.github/workflows/lint.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ jobs:
1212
name: golangci-lint
1313
runs-on: ubuntu-latest
1414
steps:
15-
- uses: actions/checkout@v3
16-
- uses: actions/setup-go@v4
15+
- name: Check out repository code
16+
uses: actions/checkout@v4
17+
- name: 🐿 Setup Golang
18+
uses: actions/setup-go@v4
1719
with:
18-
go-version: '^1.20.0'
20+
go-version: 1.21
1921
- name: golangci-lint
20-
uses: golangci/golangci-lint-action@v3
21-
with:
22-
version: v1.51.2
22+
run: make lint

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Improvements
6+
7+
- [#876](https://github.com/cosmos/iavl/pull/876) Make pruning of legacy orphan nodes asynchronous.
8+
39
## v1.0.0 (October 30, 2023)
410

511
### Improvements

batch.go

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package iavl
22

33
import (
4+
"sync"
5+
46
dbm "github.com/cosmos/cosmos-db"
57
)
68

@@ -11,6 +13,7 @@ type BatchWithFlusher struct {
1113
db dbm.DB // This is only used to create new batch
1214
batch dbm.Batch // Batched writing buffer.
1315

16+
mtx sync.Mutex
1417
flushThreshold int // The threshold to flush the batch to disk.
1518
}
1619

@@ -46,6 +49,9 @@ func (b *BatchWithFlusher) estimateSizeAfterSetting(key []byte, value []byte) (i
4649
// the batch is flushed to disk, cleared, and a new one is created with buffer pre-allocated to threshold.
4750
// The addition entry is then added to the batch.
4851
func (b *BatchWithFlusher) Set(key, value []byte) error {
52+
b.mtx.Lock()
53+
defer b.mtx.Unlock()
54+
4955
batchSizeAfter, err := b.estimateSizeAfterSetting(key, value)
5056
if err != nil {
5157
return err
@@ -67,6 +73,9 @@ func (b *BatchWithFlusher) Set(key, value []byte) error {
6773
// the batch is flushed to disk, cleared, and a new one is created with buffer pre-allocated to threshold.
6874
// The deletion entry is then added to the batch.
6975
func (b *BatchWithFlusher) Delete(key []byte) error {
76+
b.mtx.Lock()
77+
defer b.mtx.Unlock()
78+
7079
batchSizeAfter, err := b.estimateSizeAfterSetting(key, []byte{})
7180
if err != nil {
7281
return err

fastnode/fast_node.go

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func NewNode(key []byte, value []byte, version int64) *Node {
3030
}
3131

3232
// DeserializeNode constructs an *FastNode from an encoded byte slice.
33+
// It assumes we do not mutate this input []byte.
3334
func DeserializeNode(key []byte, buf []byte) (*Node, error) {
3435
ver, n, err := encoding.DecodeVarint(buf)
3536
if err != nil {

go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ require (
99
github.com/emicklei/dot v1.4.2
1010
github.com/golang/mock v1.6.0
1111
github.com/stretchr/testify v1.8.4
12-
google.golang.org/protobuf v1.30.0
1312
golang.org/x/crypto v0.12.0
13+
google.golang.org/protobuf v1.30.0
1414
)
1515

1616
require (
@@ -49,8 +49,8 @@ require (
4949
)
5050

5151
retract (
52-
v0.18.0
5352
// This version is not used by the Cosmos SDK and adds a maintenance burden.
5453
// Use v1.x.x instead.
5554
[v0.21.0, v0.21.2]
55+
v0.18.0
5656
)

internal/encoding/encoding.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ var uvarintPool = &sync.Pool{
3030

3131
// decodeBytes decodes a varint length-prefixed byte slice, returning it along with the number
3232
// of input bytes read.
33+
// Assumes bz will not be mutated.
3334
func DecodeBytes(bz []byte) ([]byte, int, error) {
3435
s, n, err := DecodeUvarint(bz)
3536
if err != nil {
@@ -51,9 +52,7 @@ func DecodeBytes(bz []byte) ([]byte, int, error) {
5152
if len(bz) < end {
5253
return nil, n, fmt.Errorf("insufficient bytes decoding []byte of length %v", size)
5354
}
54-
bz2 := make([]byte, size)
55-
copy(bz2, bz[n:end])
56-
return bz2, end, nil
55+
return bz[n:end], end, nil
5756
}
5857

5958
// decodeUvarint decodes a varint-encoded unsigned integer from a byte slice, returning it and the
@@ -97,6 +96,23 @@ func EncodeBytes(w io.Writer, bz []byte) error {
9796
return err
9897
}
9998

99+
var hashLenBz []byte
100+
101+
func init() {
102+
hashLenBz = make([]byte, 1)
103+
binary.PutUvarint(hashLenBz, 32)
104+
}
105+
106+
// Encode 32 byte long hash
107+
func Encode32BytesHash(w io.Writer, bz []byte) error {
108+
_, err := w.Write(hashLenBz)
109+
if err != nil {
110+
return err
111+
}
112+
_, err = w.Write(bz)
113+
return err
114+
}
115+
100116
// encodeBytesSlice length-prefixes the byte slice and returns it.
101117
func EncodeBytesSlice(bz []byte) ([]byte, error) {
102118
buf := bufPool.Get().(*bytes.Buffer)

keyformat/prefix_formatter.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package keyformat
2+
3+
import "encoding/binary"
4+
5+
// This file builds some dedicated key formatters for what appears in benchmarks.
6+
7+
// Prefixes a single byte before a 32 byte hash.
8+
type FastPrefixFormatter struct {
9+
prefix byte
10+
length int
11+
prefixSlice []byte
12+
}
13+
14+
func NewFastPrefixFormatter(prefix byte, length int) *FastPrefixFormatter {
15+
return &FastPrefixFormatter{prefix: prefix, length: length, prefixSlice: []byte{prefix}}
16+
}
17+
18+
func (f *FastPrefixFormatter) Key(bz []byte) []byte {
19+
key := make([]byte, 1+f.length)
20+
key[0] = f.prefix
21+
copy(key[1:], bz)
22+
return key
23+
}
24+
25+
func (f *FastPrefixFormatter) Scan(key []byte, a interface{}) {
26+
scan(a, key[1:])
27+
}
28+
29+
func (f *FastPrefixFormatter) KeyInt64(bz int64) []byte {
30+
key := make([]byte, 1+f.length)
31+
key[0] = f.prefix
32+
binary.BigEndian.PutUint64(key[1:], uint64(bz))
33+
return key
34+
}
35+
36+
func (f *FastPrefixFormatter) Prefix() []byte {
37+
return f.prefixSlice
38+
}
39+
40+
func (f *FastPrefixFormatter) Length() int {
41+
return 1 + f.length
42+
}

mutable_tree.go

+55-51
Original file line numberDiff line numberDiff line change
@@ -266,64 +266,68 @@ func (tree *MutableTree) set(key []byte, value []byte) (updated bool, err error)
266266
func (tree *MutableTree) recursiveSet(node *Node, key []byte, value []byte) (
267267
newSelf *Node, updated bool, err error,
268268
) {
269-
version := tree.version + 1
270-
271269
if node.isLeaf() {
272-
if !tree.skipFastStorageUpgrade {
273-
tree.addUnsavedAddition(key, fastnode.NewNode(key, value, version))
274-
}
275-
switch bytes.Compare(key, node.key) {
276-
case -1: // setKey < leafKey
277-
return &Node{
278-
key: node.key,
279-
subtreeHeight: 1,
280-
size: 2,
281-
nodeKey: nil,
282-
leftNode: NewNode(key, value),
283-
rightNode: node,
284-
}, false, nil
285-
case 1: // setKey > leafKey
286-
return &Node{
287-
key: key,
288-
subtreeHeight: 1,
289-
size: 2,
290-
nodeKey: nil,
291-
leftNode: node,
292-
rightNode: NewNode(key, value),
293-
}, false, nil
294-
default:
295-
return NewNode(key, value), true, nil
270+
return tree.recursiveSetLeaf(node, key, value)
271+
}
272+
node, err = node.clone(tree)
273+
if err != nil {
274+
return nil, false, err
275+
}
276+
277+
if bytes.Compare(key, node.key) < 0 {
278+
node.leftNode, updated, err = tree.recursiveSet(node.leftNode, key, value)
279+
if err != nil {
280+
return nil, updated, err
296281
}
297282
} else {
298-
node, err = node.clone(tree)
283+
node.rightNode, updated, err = tree.recursiveSet(node.rightNode, key, value)
299284
if err != nil {
300-
return nil, false, err
285+
return nil, updated, err
301286
}
287+
}
302288

303-
if bytes.Compare(key, node.key) < 0 {
304-
node.leftNode, updated, err = tree.recursiveSet(node.leftNode, key, value)
305-
if err != nil {
306-
return nil, updated, err
307-
}
308-
} else {
309-
node.rightNode, updated, err = tree.recursiveSet(node.rightNode, key, value)
310-
if err != nil {
311-
return nil, updated, err
312-
}
313-
}
289+
if updated {
290+
return node, updated, nil
291+
}
292+
err = node.calcHeightAndSize(tree.ImmutableTree)
293+
if err != nil {
294+
return nil, false, err
295+
}
296+
newNode, err := tree.balance(node)
297+
if err != nil {
298+
return nil, false, err
299+
}
300+
return newNode, updated, err
301+
}
314302

315-
if updated {
316-
return node, updated, nil
317-
}
318-
err = node.calcHeightAndSize(tree.ImmutableTree)
319-
if err != nil {
320-
return nil, false, err
321-
}
322-
newNode, err := tree.balance(node)
323-
if err != nil {
324-
return nil, false, err
325-
}
326-
return newNode, updated, err
303+
func (tree *MutableTree) recursiveSetLeaf(node *Node, key []byte, value []byte) (
304+
newSelf *Node, updated bool, err error,
305+
) {
306+
version := tree.version + 1
307+
if !tree.skipFastStorageUpgrade {
308+
tree.addUnsavedAddition(key, fastnode.NewNode(key, value, version))
309+
}
310+
switch bytes.Compare(key, node.key) {
311+
case -1: // setKey < leafKey
312+
return &Node{
313+
key: node.key,
314+
subtreeHeight: 1,
315+
size: 2,
316+
nodeKey: nil,
317+
leftNode: NewNode(key, value),
318+
rightNode: node,
319+
}, false, nil
320+
case 1: // setKey > leafKey
321+
return &Node{
322+
key: key,
323+
subtreeHeight: 1,
324+
size: 2,
325+
nodeKey: nil,
326+
leftNode: node,
327+
rightNode: NewNode(key, value),
328+
}, false, nil
329+
default:
330+
return NewNode(key, value), true, nil
327331
}
328332
}
329333

node.go

+23-9
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,15 @@ func GetRootKey(version int64) []byte {
5757

5858
// Node represents a node in a Tree.
5959
type Node struct {
60-
key []byte
61-
value []byte
62-
hash []byte
63-
nodeKey *NodeKey
64-
leftNodeKey []byte
60+
key []byte
61+
value []byte
62+
hash []byte
63+
nodeKey *NodeKey
64+
// Legacy: LeftNodeHash
65+
// v1: Left node ptr via Version/key
66+
leftNodeKey []byte
67+
// Legacy: RightNodeHash
68+
// v1: Right node ptr via Version/key
6569
rightNodeKey []byte
6670
size int64
6771
leftNode *Node
@@ -517,19 +521,29 @@ func (node *Node) writeHashBytes(w io.Writer, version int64) error {
517521
// (e.g. ProofLeafNode.ValueHash)
518522
valueHash := sha256.Sum256(node.value)
519523

520-
err = encoding.EncodeBytes(w, valueHash[:])
524+
err = encoding.Encode32BytesHash(w, valueHash[:])
521525
if err != nil {
522526
return fmt.Errorf("writing value, %w", err)
523527
}
524528
} else {
525-
if node.leftNode == nil || node.rightNode == nil {
529+
if (node.leftNode == nil && len(node.leftNodeKey) != 32) || (node.rightNode == nil && len(node.rightNodeKey) != 32) {
526530
return ErrEmptyChild
527531
}
528-
err = encoding.EncodeBytes(w, node.leftNode.hash)
532+
// If left/rightNodeKey is 32 bytes, it is a legacy node whose value is just the hash.
533+
// We may have skipped fetching leftNode/rightNode.
534+
if len(node.leftNodeKey) == 32 {
535+
err = encoding.Encode32BytesHash(w, node.leftNodeKey)
536+
} else {
537+
err = encoding.Encode32BytesHash(w, node.leftNode.hash)
538+
}
529539
if err != nil {
530540
return fmt.Errorf("writing left hash, %w", err)
531541
}
532-
err = encoding.EncodeBytes(w, node.rightNode.hash)
542+
if len(node.rightNodeKey) == 32 {
543+
err = encoding.Encode32BytesHash(w, node.rightNodeKey)
544+
} else {
545+
err = encoding.Encode32BytesHash(w, node.rightNode.hash)
546+
}
533547
if err != nil {
534548
return fmt.Errorf("writing right hash, %w", err)
535549
}

0 commit comments

Comments
 (0)