Skip to content

Commit

Permalink
Merge pull request #1 from sei-protocol/yzang/add-memiavl
Browse files Browse the repository at this point in the history
[MemIAVL] Initial commit for memIAVL db and store
  • Loading branch information
yzang2019 committed Oct 11, 2023
2 parents 15cccd6 + b428f18 commit d8895db
Show file tree
Hide file tree
Showing 43 changed files with 9,157 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,12 @@

# Go workspace file
go.work
.DS_Store
*.swp
*.swo
*.swl
*.swm
*.swn
*.pyc
.dccache
.idea
Empty file added benchmark/README.md
Empty file.
28 changes: 28 additions & 0 deletions proto/memiavl/commit_info.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
syntax = "proto3";

option go_package = "github.com/sei-protocol/sei-db/memiavl";

import "gogoproto/gogo.proto";

// CommitInfo defines commit information used by the multi-store when committing
// a version/height.
message CommitInfo {
int64 version = 1;
repeated StoreInfo store_infos = 2 [(gogoproto.nullable) = false];
}

// StoreInfo defines store-specific commit information. It contains a reference
// between a store name and the commit ID.
message StoreInfo {
string name = 1;
CommitID commit_id = 2 [(gogoproto.nullable) = false];
}

// CommitID defines the committment information when a specific store is
// committed.
message CommitID {
option (gogoproto.goproto_stringer) = false;

int64 version = 1;
bytes hash = 2;
}
36 changes: 36 additions & 0 deletions proto/memiavl/wal.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
syntax = "proto3";
package memiavl;

option go_package = "github.com/sei-protocol/sei-db/memiavl";

import "gogoproto/gogo.proto";
import "iavl/changeset.proto";
import "memiavl/commit_info.proto";

// NamedChangeSet combine a tree name with the changeset
message NamedChangeSet {
iavl.ChangeSet changeset = 1 [(gogoproto.nullable) = false];
string name = 2;
}

// TreeNameUpgrade defines upgrade of tree names:
// - New tree: { name: "tree" }
// - Delete tree: { name: "tree", delete: true }
// - Rename tree: { name: "new-tree", rename_from: "old-tree" }
message TreeNameUpgrade {
string name = 1;
string rename_from = 2;
bool delete = 3;
}

// WALEntry is a single Write-Ahead-Log entry
message WALEntry {
repeated NamedChangeSet changesets = 1;
repeated TreeNameUpgrade upgrades = 2;
}

// MultiTreeMetadata stores the metadata for MultiTree
message MultiTreeMetadata {
CommitInfo commit_info = 1;
int64 initial_version = 2;
}
Empty file added sc/memiavl/README.md
Empty file.
33 changes: 33 additions & 0 deletions sc/memiavl/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package config

const (
DefaultCacheSize = 100000
DefaultSnapshotInterval = 10000
)

type MemIAVLConfig struct {
// Enable defines if the memiavl should be enabled.
Enable bool `mapstructure:"enable"`
// ZeroCopy defines if the memiavl should return slices pointing to mmap-ed buffers directly (zero-copy),
// the zero-copied slices must not be retained beyond current block's execution.
// the sdk address cache will be disabled if zero-copy is enabled.
ZeroCopy bool `mapstructure:"zero-copy"`
// AsyncCommitBuffer defines the size of asynchronous commit queue, this greatly improve block catching-up
// performance, -1 means synchronous commit.
AsyncCommitBuffer int `mapstructure:"async-commit-buffer"`
// SnapshotKeepRecent defines what many old snapshots (excluding the latest one) to keep after new snapshots are
// taken, defaults to 1 to make sure ibc relayers work.
SnapshotKeepRecent uint32 `mapstructure:"snapshot-keep-recent"`
// SnapshotInterval defines the block interval the memiavl snapshot is taken, default to 1000.
SnapshotInterval uint32 `mapstructure:"snapshot-interval"`
// CacheSize defines the size of the cache for each memiavl store.
CacheSize int `mapstructure:"cache-size"`
}

func DefaultMemIAVLConfig() MemIAVLConfig {
return MemIAVLConfig{
CacheSize: DefaultCacheSize,
SnapshotInterval: DefaultSnapshotInterval,
SnapshotKeepRecent: 1,
}
}
32 changes: 32 additions & 0 deletions sc/memiavl/config/toml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package config

// DefaultConfigTemplate defines the configuration template for the memiavl configuration
const DefaultConfigTemplate = `
###############################################################################
### MemIAVL Configuration ###
###############################################################################
[memiavl]
# Enable defines if the memiavl should be enabled.
enable = {{ .MemIAVL.Enable }}
# ZeroCopy defines if the memiavl should return slices pointing to mmap-ed buffers directly (zero-copy),
# the zero-copied slices must not be retained beyond current block's execution.
# the sdk address cache will be disabled if zero-copy is enabled.
zero-copy = {{ .MemIAVL.ZeroCopy }}
# AsyncCommitBuffer defines the size of asynchronous commit queue, this greatly improve block catching-up
# performance, -1 means synchronous commit.
async-commit-buffer = {{ .MemIAVL.AsyncCommitBuffer }}
# SnapshotKeepRecent defines what many old snapshots (excluding the latest one) to keep after new snapshots are
# taken, defaults to 1 to make sure ibc relayers work.
snapshot-keep-recent = {{ .MemIAVL.SnapshotKeepRecent }}
# SnapshotInterval defines the block interval the memiavl snapshot is taken, default to 1000.
snapshot-interval = {{ .MemIAVL.SnapshotInterval }}
# CacheSize defines the size of the cache for each memiavl store, default to 1000.
cache-size = {{ .MemIAVL.CacheSize }}
`
239 changes: 239 additions & 0 deletions sc/memiavl/db/benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package memiavl

import (
"bytes"
"encoding/binary"
"math/rand"
"sort"
"testing"

iavlcache "github.com/cosmos/iavl/cache"
"github.com/stretchr/testify/require"
"github.com/tidwall/btree"
)

func BenchmarkByteCompare(b *testing.B) {
var x, y [32]byte
for i := 0; i < b.N; i++ {
_ = bytes.Compare(x[:], y[:])
}
}

func BenchmarkRandomGet(b *testing.B) {
amount := 1000000
items := genRandItems(amount)
targetKey := items[500].key
targetValue := items[500].value
targetItem := itemT{key: targetKey}

tree := New(0)
for _, item := range items {
tree.set(item.key, item.value)
}

snapshotDir := b.TempDir()
err := tree.WriteSnapshot(snapshotDir)
require.NoError(b, err)
snapshot, err := OpenSnapshot(snapshotDir)
require.NoError(b, err)
defer snapshot.Close()

b.Run("memiavl", func(b *testing.B) {
require.Equal(b, targetValue, tree.Get(targetKey))

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = tree.Get(targetKey)
}
})
b.Run("memiavl-disk", func(b *testing.B) {
diskTree := NewFromSnapshot(snapshot, true, 0)
require.Equal(b, targetValue, diskTree.Get(targetKey))

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = diskTree.Get(targetKey)
}
})
b.Run("memiavl-disk-cache-hit", func(b *testing.B) {
diskTree := NewFromSnapshot(snapshot, true, 1)
require.Equal(b, targetValue, diskTree.Get(targetKey))

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = diskTree.Get(targetKey)
}
})
b.Run("memiavl-disk-cache-miss", func(b *testing.B) {
diskTree := NewFromSnapshot(snapshot, true, 0)
// enforce an empty cache to emulate cache miss
diskTree.cache = iavlcache.New(0)
require.Equal(b, targetValue, diskTree.Get(targetKey))

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = diskTree.Get(targetKey)
}
})
b.Run("btree-degree-2", func(b *testing.B) {
bt2 := btree.NewBTreeGOptions(lessG, btree.Options{
NoLocks: true,
Degree: 2,
})
for _, item := range items {
bt2.Set(item)
}
v, _ := bt2.Get(targetItem)
require.Equal(b, targetValue, v.value)

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = bt2.Get(targetItem)
}
})
b.Run("btree-degree-32", func(b *testing.B) {
bt32 := btree.NewBTreeGOptions(lessG, btree.Options{
NoLocks: true,
Degree: 32,
})
for _, item := range items {
bt32.Set(item)
}
v, _ := bt32.Get(targetItem)
require.Equal(b, targetValue, v.value)

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = bt32.Get(targetItem)
}
})
b.Run("iavl-lru", func(b *testing.B) {
cache := iavlcache.New(amount)
for _, item := range items {
cache.Add(NewIavlCacheNode(item.key, item.value))
}
v := cache.Get(targetItem.key).(iavlCacheNode).value
require.Equal(b, targetValue, v)

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = cache.Get(targetKey).(iavlCacheNode).value
}
})
b.Run("go-map", func(b *testing.B) {
m := make(map[string][]byte, amount)
for _, item := range items {
m[string(item.key)] = item.value
}
v := m[string(targetItem.key)]
require.Equal(b, targetValue, v)

b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = m[string(targetKey)]
}
})

b.Run("binary-search", func(b *testing.B) {
// the last benchmark sort the items in place
sort.Slice(items, func(i, j int) bool {
return bytes.Compare(items[i].key, items[j].key) < 0
})
cmp := func(i int) bool { return bytes.Compare(items[i].key, targetKey) != -1 }
i := sort.Search(len(items), cmp)
require.Equal(b, targetValue, items[i].value)

b.ResetTimer()
for i := 0; i < b.N; i++ {
n := sort.Search(len(items), cmp)
_ = items[n].value
}
})
}

func BenchmarkRandomSet(b *testing.B) {
items := genRandItems(1000000)
b.ResetTimer()
b.Run("memiavl", func(b *testing.B) {
for i := 0; i < b.N; i++ {
tree := New(0)
for _, item := range items {
tree.set(item.key, item.value)
}
}
})
b.Run("tree2", func(b *testing.B) {
for i := 0; i < b.N; i++ {
bt := btree.NewBTreeGOptions(lessG, btree.Options{
NoLocks: true,
Degree: 2,
})
for _, item := range items {
bt.Set(item)
}
}
})
b.Run("tree32", func(b *testing.B) {
for i := 0; i < b.N; i++ {
bt := btree.NewBTreeGOptions(lessG, btree.Options{
NoLocks: true,
Degree: 32,
})
for _, item := range items {
bt.Set(item)
}
}
})
}

type itemT struct {
key, value []byte
}

func lessG(a, b itemT) bool {
return bytes.Compare(a.key, b.key) == -1
}

func int64ToItemT(n uint64) itemT {
var key, value [8]byte
binary.BigEndian.PutUint64(key[:], n)
binary.LittleEndian.PutUint64(value[:], n)
return itemT{
key: key[:],
value: value[:],
}
}

func genRandItems(n int) []itemT {
r := rand.New(rand.NewSource(0))
items := make([]itemT, n)
itemsM := make(map[uint64]bool)
for i := 0; i < n; i++ {
for {
key := uint64(r.Int63n(10000000000000000))
if !itemsM[key] {
itemsM[key] = true
items[i] = int64ToItemT(key)
break
}
}
}
return items
}

type iavlCacheNode struct {
key []byte
value []byte
}

func NewIavlCacheNode(key, value []byte) iavlCacheNode {
return iavlCacheNode{key, value}
}

func (n iavlCacheNode) GetKey() []byte {
return n.key
}

func (n iavlCacheNode) GetCacheKey() []byte {
return n.key
}
Loading

0 comments on commit d8895db

Please sign in to comment.