Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(master): async pruning of orphan nodes #877

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bcdaadf
build(deps): remove golangci-lint from deps (backport #787) (#788)
mergify[bot] Jun 2, 2023
78072ce
fix: rootKey empty check by len equals 0 (backport #801) (#809)
mergify[bot] Aug 10, 2023
715d878
chore: retract v0.21.x line (backport #812) (#813)
mergify[bot] Aug 10, 2023
ec9839f
feat: Uses BatchWithFlusher in iavl tree (backport #807) (#816)
mergify[bot] Aug 11, 2023
4637357
refactor: make batcher configurable (backport #815) (#818)
mergify[bot] Aug 11, 2023
8ae3597
perf: flush import batches in parallel (backport #793) (#820)
mergify[bot] Aug 23, 2023
6baf530
build(deps): Bump golang.org/x/crypto from 0.11.0 to 0.12.0 (backport…
mergify[bot] Aug 23, 2023
9d71b8a
build(deps): Bump cosmossdk.io/log from 1.1.0 to 1.2.0 (backport #804…
mergify[bot] Aug 23, 2023
4827590
feat: Support concurrency for IAVL and fix Racing conditions (backpor…
mergify[bot] Aug 23, 2023
f528c1f
changelog prep for v1.0.0 (#824)
tac0turtle Aug 23, 2023
fdc599d
fix: data race of latestVersion (backport #834) (#835)
mergify[bot] Sep 13, 2023
c8922db
fix(nodedb): prevent deadlock by releasing DeleteVersionsFrom mutex o…
mergify[bot] Oct 17, 2023
d5b3b1e
fix: safe batch write (backport #838) (#845)
mergify[bot] Oct 20, 2023
72911cb
refactor: remove unnecessary/repeated/pedantic code in BatchWithFlush…
mergify[bot] Oct 25, 2023
b0d383c
changelog for v1 (#848)
tac0turtle Oct 30, 2023
7c66a6c
orphan change
czarcas7ic Dec 22, 2023
d8c630d
attempt at fix orphan concurrency
czarcas7ic Dec 23, 2023
36069e6
Revert "attempt at fix orphan concurrency"
czarcas7ic Dec 23, 2023
283e4ee
Dev orphan code
ValarDragon Dec 23, 2023
f983c8e
tune params
ValarDragon Dec 23, 2023
5570973
Fix stacking
ValarDragon Dec 23, 2023
ae885e6
Fix
ValarDragon Dec 23, 2023
ec431e9
Use 32Byte encoding + make legacy encoding improvements
ValarDragon Dec 24, 2023
49beaf0
Speedup key formatting
ValarDragon Dec 23, 2023
b589c45
Avoid making an extra heap copy in DecodeBytes
ValarDragon Dec 24, 2023
4ea1f0a
lints
czarcas7ic Jan 31, 2024
1f47598
lint
czarcas7ic Jan 31, 2024
69b8cdb
Merge branch 'master' into adam/master-osmo-concurrency
czarcas7ic Jan 31, 2024
ac6a6d0
Merge branch 'master' into adam/master-osmo-concurrency
czarcas7ic Jan 31, 2024
0f3941d
undo changelog change
czarcas7ic Jan 31, 2024
43ceaae
fix import
czarcas7ic Jan 31, 2024
7269aa0
fix prefix
czarcas7ic Jan 31, 2024
a4a3952
lint
czarcas7ic Jan 31, 2024
09ce958
add changelog
czarcas7ic Feb 1, 2024
9ec53ed
Update CHANGELOG.md
czarcas7ic Feb 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Changelog

## Unreleased
## v1.0.0 (October 30, 2023)

### Improvements

Expand Down
9 changes: 9 additions & 0 deletions batch.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package iavl

import (
"sync"

dbm "github.com/cosmos/cosmos-db"
)

Expand All @@ -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.
}

Expand Down Expand Up @@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If each batch accumulates in memory before flushing to disk doesn't it make sense to lock at write instead of Set/Delete?

defer b.mtx.Unlock()

batchSizeAfter, err := b.estimateSizeAfterSetting(key, value)
if err != nil {
return err
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions fastnode/fast_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 21 additions & 3 deletions internal/encoding/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -97,6 +98,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)
Expand Down
42 changes: 42 additions & 0 deletions keyformat/prefix_formatter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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
}
59 changes: 32 additions & 27 deletions mutable_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
32 changes: 23 additions & 9 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
Loading