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: Fast storage optimization for queries and iterations #468

Merged
merged 123 commits into from
Apr 9, 2022
Merged
Show file tree
Hide file tree
Changes from 89 commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
0d91ad8
Add unbounded key string to KeyFormat
ValarDragon Nov 21, 2021
4040da1
Add test vectors for unbounded length keys
ValarDragon Nov 22, 2021
3740d0b
Add some notes
ValarDragon Nov 22, 2021
e175568
update .gitignore
jtieri Nov 30, 2021
24cb701
Add FastNode struct
jtieri Nov 30, 2021
1c0188d
WIP: make Get work with new FastNode
jtieri Nov 30, 2021
67f000a
when retrieving fastnode fails, return errors vs. panic
jtieri Nov 30, 2021
ab5f2cd
add comments clarifying what index represents
jtieri Nov 30, 2021
3618100
make the linter happy
jtieri Nov 30, 2021
44c6035
Add small tweaks to fast_node.go
jtieri Nov 30, 2021
19fe40d
add TODO & small linter tweaks
jtieri Dec 1, 2021
fdfe996
Update comment
ValarDragon Dec 6, 2021
8b5b9a7
update fast node cache in set
p0mvn Jan 12, 2022
db293f9
add debugging output when falling back to original logic
jtieri Dec 6, 2021
606b56f
return error instead of panic
jtieri Dec 6, 2021
36f0760
WIP: refactor set ops to work with fast store
jtieri Dec 6, 2021
36b30a1
update Set of mutable tree, begin unit testing
p0mvn Jan 12, 2022
2a5c19b
update GetVersioned to check fast nodes before trying the immutable
p0mvn Jan 12, 2022
5e942a1
check fast node version before nil value check in get of immutable tree
p0mvn Jan 12, 2022
2dcf439
fix small bugs and typos, continue writing unit tests for Set
p0mvn Jan 13, 2022
1dfc795
unit test saveFastNodeVersion
p0mvn Jan 13, 2022
2e4daa7
simplify storing unsaved fast nodes
p0mvn Jan 13, 2022
6f4630c
resolve a bug with not writing prefix for fast node to disk
p0mvn Jan 13, 2022
599937d
remove fast nodes from disk on save and clear fast cache when version…
p0mvn Jan 14, 2022
348d7be
resolve an issue with randomized tests caused by the fast node cache …
p0mvn Jan 14, 2022
ec07dd1
split unsaved fast node changes into additions and removals
p0mvn Jan 14, 2022
ffb4c30
save fast node removals
p0mvn Jan 14, 2022
6a1d2d7
move fast node cache clearing to nodedb
p0mvn Jan 14, 2022
1db02b3
use regular logic only when fast node version is greater than immutab…
p0mvn Jan 14, 2022
f3dcb71
clean up tree_random_test.go
p0mvn Jan 14, 2022
0a765bb
clear unsaved fast node removals on rollback
p0mvn Jan 14, 2022
b8a9b0a
fix randomized test failures caused by a typo in ndb DeleteVersion fo…
p0mvn Jan 14, 2022
28da34c
implement GetFast method to preserve Get with correct index return, c…
p0mvn Jan 14, 2022
0a0ba33
ensure Get and GetFast return the same values in tree_random_test.go
p0mvn Jan 14, 2022
5bf8c81
test fast node cache is live in random tree tests
p0mvn Jan 15, 2022
41c1827
improve mutable tree unit tests related to new functionality
p0mvn Jan 15, 2022
5fb6d2b
clean up tree_test.go
p0mvn Jan 15, 2022
c8def88
implement GetVersionedFast to preserve the index in GetVersioned
p0mvn Jan 15, 2022
eca5e6c
restore accidentally deleted test in mutable tree test
p0mvn Jan 15, 2022
7a5c131
spurious whitespace
p0mvn Jan 15, 2022
f9ab00e
refactor mutable tree
p0mvn Jan 15, 2022
2f54c55
fix comment in mutable tree
p0mvn Jan 15, 2022
ff5a3ed
add benchmark results
p0mvn Jan 15, 2022
754e8e8
avoid redundant full tree search in GetFast of immutable tree when fa…
p0mvn Jan 15, 2022
f8c085b
fix naming for bench test get on random keys
p0mvn Jan 15, 2022
4064369
use method for get latestversio in get fast
p0mvn Jan 15, 2022
9abde82
optimize GetFast, perform a refactor to ensure that fast nodes on dis…
p0mvn Jan 16, 2022
35cacc4
add latest bench
p0mvn Jan 16, 2022
0e09c6b
Fast Node Iteration (#7)
p0mvn Jan 20, 2022
51b1f33
refactor iterate methods of mutable and immutable trees
p0mvn Jan 20, 2022
e773a16
resolve some warnings
p0mvn Jan 20, 2022
4120794
remove old bench results
p0mvn Jan 20, 2022
dfd175e
refactor bench tests for iteration
p0mvn Jan 20, 2022
c6d015d
Fast Cache Migration (#9)
p0mvn Jan 21, 2022
4d9facc
Rename Get to GetWithIndex and GetFast to Get
p0mvn Jan 21, 2022
f2c529c
refactor and clean up bench tests
p0mvn Jan 21, 2022
9b532aa
remove extra byte from fast node length
p0mvn Jan 21, 2022
dc316b9
clean up immutable tree
p0mvn Jan 21, 2022
b04993d
refactor nil tree or ndb error for the iterators and their tests
p0mvn Jan 21, 2022
d4b6e01
avoid exporting methods for getting unsaved additions and removals
p0mvn Jan 21, 2022
649387d
refactor fast upgrade to read from tree instead of traversing disk no…
p0mvn Jan 22, 2022
ca9904b
remove unneeded comment
p0mvn Jan 22, 2022
9f62bfb
refer to storage version consistently across the project
p0mvn Jan 22, 2022
3ca6772
fix more warnings
p0mvn Jan 22, 2022
2fb41e8
optimize removal of fast nodes from cache on deletion
p0mvn Jan 22, 2022
521d596
small changes in teh mutable tree
p0mvn Jan 22, 2022
176993d
correct storage version key
p0mvn Jan 22, 2022
4bb3ecb
auto set fast version in SaveVersion
p0mvn Jan 22, 2022
19d5195
avoid exporting new methods of the immutable tree
p0mvn Jan 22, 2022
eb8ae54
go fmt
p0mvn Jan 22, 2022
7678a17
Fix comment in fast iterator
p0mvn Jan 25, 2022
d98284a
add extra comment for domain in fast iterator
p0mvn Jan 25, 2022
c5bf2e5
add comments for moving the iterator before the first element
p0mvn Jan 25, 2022
d1e999a
add comment for describing what mirror is in assertIterator of testutils
p0mvn Jan 25, 2022
69f84a8
fix checking the mirror for descending iterator in tests
p0mvn Jan 25, 2022
5a67677
Update testutils_test.go with a comment
p0mvn Jan 25, 2022
c925b01
Update benchmarks/bench_test.go with a comment for runKnownQueriesFast
p0mvn Jan 25, 2022
313615e
Update benchmarks/bench_test.go with a comment for runQueriesFast
p0mvn Jan 25, 2022
08cde5a
export IsFastCacheEnabled and add an assert in bench tests
p0mvn Jan 25, 2022
b5f17cc
Update comment immutable_tree.go
p0mvn Jan 25, 2022
accf09d
Update comment for migration in mutable_tree.go
p0mvn Jan 25, 2022
ac7bc8b
simlify Iterate in mutable tree, add debug log for
p0mvn Jan 25, 2022
cbfd88a
Fast Cache - Downgrade - reupgrade protection and other improvements …
p0mvn Feb 7, 2022
b0ea918
address comments from unsaved fast iterator PR
p0mvn Feb 8, 2022
45e1ed4
expose isUpgradeable method on mutable tree and unit test (#17)
p0mvn Feb 8, 2022
611e18c
resolve problems with rebasing
p0mvn Feb 13, 2022
73cc200
update CHANGELOG.md
p0mvn Feb 13, 2022
ff8d21c
tendermint v0.35.0
p0mvn Feb 13, 2022
1e62146
fix String() bench
p0mvn Feb 13, 2022
d27a0c3
fix duplication lint in iterator_test.go
p0mvn Feb 15, 2022
7009a9c
fix lint for tree.ndb.DeleteFastNode error return not checked
p0mvn Feb 15, 2022
e4ed678
fix Error return value of `ndb.resetBatch` is not checked
p0mvn Feb 15, 2022
34d4070
fix Error return value of `ndb.traversePrefix` is not checked
p0mvn Feb 15, 2022
3e064e0
fix Error: struct of size 64 bytes could be of size 56 bytes
p0mvn Feb 15, 2022
3433ef1
fix Error: `comitted` is a misspelling of `committed`
p0mvn Feb 15, 2022
3783679
fix issues in basic_test.go
p0mvn Feb 15, 2022
6bc3fe6
address comments in fast_iterator.go
p0mvn Feb 15, 2022
4fe1d9f
address comments in immutable tree
p0mvn Feb 15, 2022
484e5a1
address comments in iterator.go
p0mvn Feb 15, 2022
81ac12e
address comments in key_format.go
p0mvn Feb 15, 2022
57dbc38
address remaining comments
p0mvn Feb 15, 2022
b016408
fix Error: Error return value of `ndb.batch.Write` is not checked
p0mvn Feb 15, 2022
eefb4e7
fix Error: receiver name t should be consistent with previous receive…
p0mvn Feb 15, 2022
80b2309
fix Error: struct of size 48 bytes could be of size 40 bytes
p0mvn Feb 15, 2022
e47a345
go fmt
p0mvn Feb 15, 2022
a124043
more linter fixes
p0mvn Feb 15, 2022
eebf868
fix remaining linter problems
p0mvn Feb 15, 2022
34555e3
upgrade tm-db and comment out broken bencher databases
p0mvn Feb 16, 2022
141d98d
skip iterations for BenchmarkLevelDBLargeData bench
p0mvn Feb 16, 2022
543206b
attempt to fix linter
p0mvn Feb 17, 2022
e44a79a
force GC, no cache during migration, auto heap profile (#19)
p0mvn Feb 20, 2022
1236c4c
sync access to fast node cache to avoid concurrent write fatal error …
p0mvn Feb 21, 2022
98d02eb
update go.mod and go.sum to match master versions
p0mvn Feb 28, 2022
2feedb7
merge master into roman/iavl_data_locality
p0mvn Feb 28, 2022
b5fd3db
Merge branch 'master' into roman/iavl_data_locality
p0mvn Mar 28, 2022
67b66c0
revert #23 (sync access to fast node cache), fix bug related to old h…
p0mvn Mar 3, 2022
69129bf
fix concurrent map panic when querying and comittting concurrently
p0mvn Mar 9, 2022
b4f8932
avoid clearing fast node cache during pruning (#35)
p0mvn Mar 21, 2022
71e959a
fix data race related to VersionExists (#36)
p0mvn Mar 22, 2022
b0d8cdf
hardcode fast node cache size to 100k
p0mvn Mar 27, 2022
f660a25
go fmt
p0mvn Mar 28, 2022
f56de3a
restore proof_ics23.go
p0mvn Mar 28, 2022
ff9f32d
fix linter
p0mvn Mar 28, 2022
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ cpu*.out
mem*.out
cpu*.pdf
mem*.pdf

# IDE files
.idea/*
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

### Improvements

- [\#468](https://github.com/cosmos/iavl/pull/468) Fast storage optimization for queries and iterations

## 0.17.3 (December 1, 2021)

### Improvements
Copy link
Collaborator

Choose a reason for hiding this comment

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

please remove the 3 lines above. We are targetting master, so #468 should go under Unreleased


- [\#445](https://github.com/cosmos/iavl/pull/445) Bump github.com/tendermint/tendermint to v0.35.0

### Bug Fixes
Expand Down
81 changes: 68 additions & 13 deletions basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,72 +35,127 @@ func TestBasic(t *testing.T) {

// Test 0x00
{
idx, val := tree.Get([]byte{0x00})
key := []byte{0x00}
expected := ""

idx, val := tree.GetWithIndex(key)
if val != nil {
t.Errorf("Expected no value to exist")
}
if idx != 0 {
t.Errorf("Unexpected idx %x", idx)
}
if string(val) != "" {
if string(val) != expected {
t.Errorf("Unexpected value %v", string(val))
}

val = tree.Get(key)
if val != nil {
t.Errorf("Fast method - expected no value to exist")
}
if string(val) != expected {
t.Errorf("Fast method - Unexpected value %v", string(val))
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Test "1"
{
idx, val := tree.Get([]byte("1"))
key := []byte("1")
expected := "one"

idx, val := tree.GetWithIndex(key)
if val == nil {
t.Errorf("Expected value to exist")
}
if idx != 0 {
t.Errorf("Unexpected idx %x", idx)
}
if string(val) != "one" {
if string(val) != expected {
t.Errorf("Unexpected value %v", string(val))
}

val = tree.Get(key)
if val == nil {
t.Errorf("Fast method - expected value to exist")
}
if string(val) != expected {
t.Errorf("Fast method - Unexpected value %v", string(val))
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Test "2"
{
idx, val := tree.Get([]byte("2"))
key := []byte("2")
expected := "TWO"

idx, val := tree.GetWithIndex(key)
if val == nil {
t.Errorf("Expected value to exist")
}
if idx != 1 {
t.Errorf("Unexpected idx %x", idx)
}
if string(val) != "TWO" {
if string(val) != expected {
t.Errorf("Unexpected value %v", string(val))
}

val = tree.Get(key)
if val == nil {
t.Errorf("Fast method - expected value to exist")
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
}
if string(val) != expected {
t.Errorf("Fast method - Unexpected value %v", string(val))
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Test "4"
{
idx, val := tree.Get([]byte("4"))
key := []byte("4")
expected := ""

idx, val := tree.GetWithIndex(key)
if val != nil {
t.Errorf("Expected no value to exist")
}
if idx != 2 {
t.Errorf("Unexpected idx %x", idx)
}
if string(val) != "" {
if string(val) != expected {
t.Errorf("Unexpected value %v", string(val))
}

val = tree.Get(key)
if val != nil {
t.Errorf("Fast method - expected no value to exist")
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
}
if string(val) != expected {
t.Errorf("Fast method - Unexpected value %v", string(val))
}
}

// Test "6"
{
idx, val := tree.Get([]byte("6"))
key := []byte("6")
expected := ""

idx, val := tree.GetWithIndex(key)
if val != nil {
t.Errorf("Expected no value to exist")
}
if idx != 3 {
t.Errorf("Unexpected idx %x", idx)
}
if string(val) != "" {
if string(val) != expected {
t.Errorf("Unexpected value %v", string(val))
}

val = tree.Get(key)
if val != nil {
t.Errorf("Fast method - expected no value to exist")
}
if string(val) != expected {
t.Errorf("Fast method - Unexpected value %v", string(val))
}
}
}

Expand Down Expand Up @@ -252,7 +307,7 @@ func TestIntegration(t *testing.T) {
if has := tree.Has([]byte(randstr(12))); has {
t.Error("Table has extra key")
}
if _, val := tree.Get([]byte(r.key)); string(val) != r.value {
if val := tree.Get([]byte(r.key)); string(val) != r.value {
t.Error("wrong value")
}
}
Expand All @@ -270,7 +325,7 @@ func TestIntegration(t *testing.T) {
if has := tree.Has([]byte(randstr(12))); has {
t.Error("Table has extra key")
}
_, val := tree.Get([]byte(r.key))
val := tree.Get([]byte(r.key))
if string(val) != r.value {
t.Error("wrong value")
}
Expand Down Expand Up @@ -388,7 +443,7 @@ func TestPersistence(t *testing.T) {
require.NoError(t, err)
t2.Load()
for key, value := range records {
_, t2value := t2.Get([]byte(key))
t2value := t2.Get([]byte(key))
if string(t2value) != value {
t.Fatalf("Invalid value. Expected %v, got %v", value, t2value)
}
Expand Down
105 changes: 98 additions & 7 deletions benchmarks/bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,93 @@ func commitTree(b *testing.B, t *iavl.MutableTree) {
}
}

func runQueries(b *testing.B, t *iavl.MutableTree, keyLen int) {
// queries random keys against live state. Keys are almost certainly not in the tree.
func runQueriesFast(b *testing.B, t *iavl.MutableTree, keyLen int) {
require.True(b, t.IsFastCacheEnabled())
for i := 0; i < b.N; i++ {
q := randBytes(keyLen)
t.Get(q)
}
}

func runKnownQueries(b *testing.B, t *iavl.MutableTree, keys [][]byte) {
// queries keys that are known to be in state
func runKnownQueriesFast(b *testing.B, t *iavl.MutableTree, keys [][]byte) {
require.True(b, t.IsFastCacheEnabled()) // to ensure fast storage is enabled
l := int32(len(keys))
for i := 0; i < b.N; i++ {
q := keys[rand.Int31n(l)]
t.Get(q)
}
}

func runQueriesSlow(b *testing.B, t *iavl.MutableTree, keyLen int) {
b.StopTimer()
// Save version to get an old immutable tree to query against,
// Fast storage is not enabled on old tree versions, allowing us to bench the desired behavior.
_, version, err := t.SaveVersion()
require.NoError(b, err)

itree, err := t.GetImmutable(version - 1)
require.NoError(b, err)
require.False(b, itree.IsFastCacheEnabled()) // to ensure fast storage is not enabled

b.StartTimer()
for i := 0; i < b.N; i++ {
q := randBytes(keyLen)
itree.GetWithIndex(q)
}
}

func runKnownQueriesSlow(b *testing.B, t *iavl.MutableTree, keys [][]byte) {
b.StopTimer()
// Save version to get an old immutable tree to query against,
// Fast storage is not enabled on old tree versions, allowing us to bench the desired behavior.
_, version, err := t.SaveVersion()
require.NoError(b, err)

itree, err := t.GetImmutable(version - 1)
require.NoError(b, err)
require.False(b, itree.IsFastCacheEnabled()) // to ensure fast storage is not enabled
b.StartTimer()
l := int32(len(keys))
for i := 0; i < b.N; i++ {
q := keys[rand.Int31n(l)]
itree.GetWithIndex(q)
Copy link
Contributor

Choose a reason for hiding this comment

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

We should also perhaps have an assertion so that if the result returned is nil that we crash so

index, value := itree.GetWithIndex(q)
require.True(b, index >= 0, "the index must not be negative")
require.NotNil(b, value, "the value should exist")

}
}

func runIterationFast(b *testing.B, t *iavl.MutableTree, expectedSize int) {
require.True(b, t.IsFastCacheEnabled()) // to ensure fast storage is enabled
for i := 0; i < b.N; i++ {
itr := t.ImmutableTree.Iterator(nil, nil, false)
iterate(b, itr, expectedSize)
itr.Close()
Copy link
Contributor

Choose a reason for hiding this comment

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

require.Nil(t, itr.Close(), ".Close should not error out")

}
}

func runIterationSlow(b *testing.B, t *iavl.MutableTree, expectedSize int) {
for i := 0; i < b.N; i++ {
itr := iavl.NewIterator(nil, nil, false, t.ImmutableTree) // create slow iterator directly
iterate(b, itr, expectedSize)
itr.Close()
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
}
}

func iterate(b *testing.B, itr db.Iterator, expectedSize int) {
b.StartTimer()
keyValuePairs := make([][][]byte, 0, expectedSize)
for i := 0; i < expectedSize && itr.Valid(); i++ {
itr.Next()
keyValuePairs = append(keyValuePairs, [][]byte{itr.Key(), itr.Value()})
}
b.StopTimer()
if len(keyValuePairs) != expectedSize {
b.Errorf("iteration count mismatch: %d != %d", len(keyValuePairs), expectedSize)
} else {
tac0turtle marked this conversation as resolved.
Show resolved Hide resolved
b.Logf("completed %d iterations", len(keyValuePairs))
}
}

// func runInsert(b *testing.B, t *iavl.MutableTree, keyLen, dataLen, blockSize int) *iavl.MutableTree {
// for i := 1; i <= b.N; i++ {
// t.Set(randBytes(keyLen), randBytes(dataLen))
Expand Down Expand Up @@ -132,7 +204,7 @@ func runBlock(b *testing.B, t *iavl.MutableTree, keyLen, dataLen, blockSize int,
data := randBytes(dataLen)

// perform query and write on check and then real
// check.Get(key)
// check.GetFast(key)
// check.Set(key, data)
real.Get(key)
real.Set(key, data)
Expand Down Expand Up @@ -287,14 +359,33 @@ func runSuite(b *testing.B, d db.DB, initSize, blockSize, keyLen, dataLen int) {

b.ResetTimer()

b.Run("query-miss", func(sub *testing.B) {
b.Run("query-no-in-tree-guarantee-fast", func(sub *testing.B) {
sub.ReportAllocs()
runQueriesFast(sub, t, keyLen)
})
b.Run("query-no-in-tree-guarantee-slow", func(sub *testing.B) {
sub.ReportAllocs()
runQueriesSlow(sub, t, keyLen)
})
//
b.Run("query-hits-fast", func(sub *testing.B) {
sub.ReportAllocs()
runKnownQueriesFast(sub, t, keys)
})
b.Run("query-hits-slow", func(sub *testing.B) {
sub.ReportAllocs()
runKnownQueriesSlow(sub, t, keys)
})
//
b.Run("iteration-fast", func(sub *testing.B) {
sub.ReportAllocs()
runQueries(sub, t, keyLen)
runIterationFast(sub, t, initSize)
})
b.Run("query-hits", func(sub *testing.B) {
b.Run("iteration-slow", func(sub *testing.B) {
sub.ReportAllocs()
runKnownQueries(sub, t, keys)
runIterationSlow(sub, t, initSize)
})
//
b.Run("update", func(sub *testing.B) {
sub.ReportAllocs()
t = runUpdate(sub, t, dataLen, blockSize, keys)
Expand Down
8 changes: 4 additions & 4 deletions export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ func setupExportTreeRandom(t *testing.T) *ImmutableTree {
keySize = 16
valueSize = 16

versions = 32 // number of versions to generate
versionOps = 4096 // number of operations (create/update/delete) per version
versions = 8 // number of versions to generate
versionOps = 1024 // number of operations (create/update/delete) per version
Copy link
Collaborator

Choose a reason for hiding this comment

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

why changing this?

updateRatio = 0.4 // ratio of updates out of all operations
deleteRatio = 0.2 // ratio of deletes out of all operations
)
Expand Down Expand Up @@ -211,8 +211,8 @@ func TestExporter_Import(t *testing.T) {
require.Equal(t, tree.Version(), newTree.Version(), "Tree version mismatch")

tree.Iterate(func(key, value []byte) bool {
index, _ := tree.Get(key)
newIndex, newValue := newTree.Get(key)
index, _ := tree.GetWithIndex(key)
newIndex, newValue := newTree.GetWithIndex(key)
require.Equal(t, index, newIndex, "Index mismatch for key %v", key)
require.Equal(t, value, newValue, "Value mismatch for key %v", key)
return false
Expand Down
Loading