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

Fast storage optimization for queries and iterations #5

Merged
merged 73 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
4dfa41e
update fast node cache in set
p0mvn Jan 12, 2022
3d65774
add debugging output when falling back to original logic
jtieri Dec 6, 2021
e48528b
return error instead of panic
jtieri Dec 6, 2021
3144b6d
WIP: refactor set ops to work with fast store
jtieri Dec 6, 2021
163f49a
update Set of mutable tree, begin unit testing
p0mvn Jan 12, 2022
d1d7d92
update GetVersioned to check fast nodes before trying the immutable
p0mvn Jan 12, 2022
0e76628
check fast node version before nil value check in get of immutable tree
p0mvn Jan 12, 2022
4e53d7f
fix small bugs and typos, continue writing unit tests for Set
p0mvn Jan 13, 2022
fb33de8
unit test saveFastNodeVersion
p0mvn Jan 13, 2022
cdbae73
simplify storing unsaved fast nodes
p0mvn Jan 13, 2022
30358a1
resolve a bug with not writing prefix for fast node to disk
p0mvn Jan 13, 2022
fd3a948
remove fast nodes from disk on save and clear fast cache when version…
p0mvn Jan 14, 2022
c54050f
resolve an issue with randomized tests caused by the fast node cache …
p0mvn Jan 14, 2022
0195356
split unsaved fast node changes into additions and removals
p0mvn Jan 14, 2022
2dcbd32
save fast node removals
p0mvn Jan 14, 2022
f0ff650
move fast node cache clearing to nodedb
p0mvn Jan 14, 2022
38f0be4
use regular logic only when fast node version is greater than immutab…
p0mvn Jan 14, 2022
196c272
clean up tree_random_test.go
p0mvn Jan 14, 2022
2583324
clear unsaved fast node removals on rollback
p0mvn Jan 14, 2022
6903b24
fix randomized test failures caused by a typo in ndb DeleteVersion fo…
p0mvn Jan 14, 2022
789efe8
implement GetFast method to preserve Get with correct index return, c…
p0mvn Jan 14, 2022
29775ca
ensure Get and GetFast return the same values in tree_random_test.go
p0mvn Jan 14, 2022
d9d9185
test fast node cache is live in random tree tests
p0mvn Jan 15, 2022
e0afb9d
improve mutable tree unit tests related to new functionality
p0mvn Jan 15, 2022
e2661d1
clean up tree_test.go
p0mvn Jan 15, 2022
bf6b2ad
implement GetVersionedFast to preserve the index in GetVersioned
p0mvn Jan 15, 2022
f7c6b12
restore accidentally deleted test in mutable tree test
p0mvn Jan 15, 2022
b6d937f
spurious whitespace
p0mvn Jan 15, 2022
e1915a9
refactor mutable tree
p0mvn Jan 15, 2022
d2a6187
fix comment in mutable tree
p0mvn Jan 15, 2022
ece487f
add benchmark results
p0mvn Jan 15, 2022
1fe01bc
avoid redundant full tree search in GetFast of immutable tree when fa…
p0mvn Jan 15, 2022
f149348
fix naming for bench test get on random keys
p0mvn Jan 15, 2022
2088ac8
use method for get latestversio in get fast
p0mvn Jan 15, 2022
5bed4af
optimize GetFast, perform a refactor to ensure that fast nodes on dis…
p0mvn Jan 16, 2022
544e4ab
add latest bench
p0mvn Jan 16, 2022
aa9f2b7
Fast Node Iteration (#7)
p0mvn Jan 20, 2022
727b949
refactor iterate methods of mutable and immutable trees
p0mvn Jan 20, 2022
84729ce
resolve some warnings
p0mvn Jan 20, 2022
4997938
remove old bench results
p0mvn Jan 20, 2022
b4165d1
refactor bench tests for iteration
p0mvn Jan 20, 2022
20a7fa6
Fast Cache Migration (#9)
p0mvn Jan 21, 2022
d28ccac
Rename Get to GetWithIndex and GetFast to Get
p0mvn Jan 21, 2022
c2a89d4
refactor and clean up bench tests
p0mvn Jan 21, 2022
682cb71
remove extra byte from fast node length
p0mvn Jan 21, 2022
a855368
clean up immutable tree
p0mvn Jan 21, 2022
f025374
refactor nil tree or ndb error for the iterators and their tests
p0mvn Jan 21, 2022
368a454
avoid exporting methods for getting unsaved additions and removals
p0mvn Jan 21, 2022
6d24a60
refactor fast upgrade to read from tree instead of traversing disk no…
p0mvn Jan 22, 2022
5ea4c4f
remove unneeded comment
p0mvn Jan 22, 2022
92dbfd1
refer to storage version consistently across the project
p0mvn Jan 22, 2022
bd1c445
fix more warnings
p0mvn Jan 22, 2022
a424853
optimize removal of fast nodes from cache on deletion
p0mvn Jan 22, 2022
fa0c081
small changes in teh mutable tree
p0mvn Jan 22, 2022
d0cdc1c
correct storage version key
p0mvn Jan 22, 2022
4944a59
auto set fast version in SaveVersion
p0mvn Jan 22, 2022
1319329
avoid exporting new methods of the immutable tree
p0mvn Jan 22, 2022
40b97ee
go fmt
p0mvn Jan 22, 2022
e9c85e0
Fix comment in fast iterator
p0mvn Jan 25, 2022
c85a17a
add extra comment for domain in fast iterator
p0mvn Jan 25, 2022
495ed3b
add comments for moving the iterator before the first element
p0mvn Jan 25, 2022
20abc09
add comment for describing what mirror is in assertIterator of testutils
p0mvn Jan 25, 2022
4584387
fix checking the mirror for descending iterator in tests
p0mvn Jan 25, 2022
0edf216
Update testutils_test.go with a comment
p0mvn Jan 25, 2022
0c1ad3f
Update benchmarks/bench_test.go with a comment for runKnownQueriesFast
p0mvn Jan 25, 2022
054cdbf
Update benchmarks/bench_test.go with a comment for runQueriesFast
p0mvn Jan 25, 2022
0bd59dc
export IsFastCacheEnabled and add an assert in bench tests
p0mvn Jan 25, 2022
2312d57
Update comment immutable_tree.go
p0mvn Jan 25, 2022
4f6953a
Update comment for migration in mutable_tree.go
p0mvn Jan 25, 2022
e309f7f
simlify Iterate in mutable tree, add debug log for
p0mvn Jan 25, 2022
2dbde70
Fast Cache - Downgrade - reupgrade protection and other improvements …
p0mvn Feb 7, 2022
c83f0c5
address comments from unsaved fast iterator PR
p0mvn Feb 8, 2022
f0f815e
expose isUpgradeable method on mutable tree and unit test (#17)
p0mvn Feb 8, 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
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)
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
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))
}
}

// 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))
}
}

// 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")
}
if string(val) != expected {
t.Errorf("Fast method - Unexpected value %v", string(val))
}
}

// 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")
}
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
109 changes: 100 additions & 9 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) {
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
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) {
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}

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)
ValarDragon marked this conversation as resolved.
Show resolved Hide resolved
iterate(b, itr, expectedSize)
itr.Close()
}
}

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()
}
}

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 {
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 @@ -193,8 +265,8 @@ func BenchmarkSmall(b *testing.B) {

func BenchmarkLarge(b *testing.B) {
benchmarks := []benchmark{
{"memdb", 1000000, 100, 16, 40},
{"goleveldb", 1000000, 100, 16, 40},
{"memdb", 1000000, 100, 4, 10},
{"goleveldb", 1000000, 100, 4, 10},
// FIXME: this crashes on init! Either remove support, or make it work.
// {"cleveldb", 100000, 100, 16, 40},
}
Expand Down Expand Up @@ -276,14 +348,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
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