Skip to content
This repository was archived by the owner on Jan 20, 2026. It is now read-only.

Commit d8895db

Browse files
authored
Merge pull request #1 from sei-protocol/yzang/add-memiavl
[MemIAVL] Initial commit for memIAVL db and store
2 parents 15cccd6 + b428f18 commit d8895db

43 files changed

Lines changed: 9157 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,12 @@
1919

2020
# Go workspace file
2121
go.work
22+
.DS_Store
23+
*.swp
24+
*.swo
25+
*.swl
26+
*.swm
27+
*.swn
28+
*.pyc
29+
.dccache
30+
.idea

benchmark/README.md

Whitespace-only changes.

proto/memiavl/commit_info.proto

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
syntax = "proto3";
2+
3+
option go_package = "github.com/sei-protocol/sei-db/memiavl";
4+
5+
import "gogoproto/gogo.proto";
6+
7+
// CommitInfo defines commit information used by the multi-store when committing
8+
// a version/height.
9+
message CommitInfo {
10+
int64 version = 1;
11+
repeated StoreInfo store_infos = 2 [(gogoproto.nullable) = false];
12+
}
13+
14+
// StoreInfo defines store-specific commit information. It contains a reference
15+
// between a store name and the commit ID.
16+
message StoreInfo {
17+
string name = 1;
18+
CommitID commit_id = 2 [(gogoproto.nullable) = false];
19+
}
20+
21+
// CommitID defines the committment information when a specific store is
22+
// committed.
23+
message CommitID {
24+
option (gogoproto.goproto_stringer) = false;
25+
26+
int64 version = 1;
27+
bytes hash = 2;
28+
}

proto/memiavl/wal.proto

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
syntax = "proto3";
2+
package memiavl;
3+
4+
option go_package = "github.com/sei-protocol/sei-db/memiavl";
5+
6+
import "gogoproto/gogo.proto";
7+
import "iavl/changeset.proto";
8+
import "memiavl/commit_info.proto";
9+
10+
// NamedChangeSet combine a tree name with the changeset
11+
message NamedChangeSet {
12+
iavl.ChangeSet changeset = 1 [(gogoproto.nullable) = false];
13+
string name = 2;
14+
}
15+
16+
// TreeNameUpgrade defines upgrade of tree names:
17+
// - New tree: { name: "tree" }
18+
// - Delete tree: { name: "tree", delete: true }
19+
// - Rename tree: { name: "new-tree", rename_from: "old-tree" }
20+
message TreeNameUpgrade {
21+
string name = 1;
22+
string rename_from = 2;
23+
bool delete = 3;
24+
}
25+
26+
// WALEntry is a single Write-Ahead-Log entry
27+
message WALEntry {
28+
repeated NamedChangeSet changesets = 1;
29+
repeated TreeNameUpgrade upgrades = 2;
30+
}
31+
32+
// MultiTreeMetadata stores the metadata for MultiTree
33+
message MultiTreeMetadata {
34+
CommitInfo commit_info = 1;
35+
int64 initial_version = 2;
36+
}

sc/memiavl/README.md

Whitespace-only changes.

sc/memiavl/config/config.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package config
2+
3+
const (
4+
DefaultCacheSize = 100000
5+
DefaultSnapshotInterval = 10000
6+
)
7+
8+
type MemIAVLConfig struct {
9+
// Enable defines if the memiavl should be enabled.
10+
Enable bool `mapstructure:"enable"`
11+
// ZeroCopy defines if the memiavl should return slices pointing to mmap-ed buffers directly (zero-copy),
12+
// the zero-copied slices must not be retained beyond current block's execution.
13+
// the sdk address cache will be disabled if zero-copy is enabled.
14+
ZeroCopy bool `mapstructure:"zero-copy"`
15+
// AsyncCommitBuffer defines the size of asynchronous commit queue, this greatly improve block catching-up
16+
// performance, -1 means synchronous commit.
17+
AsyncCommitBuffer int `mapstructure:"async-commit-buffer"`
18+
// SnapshotKeepRecent defines what many old snapshots (excluding the latest one) to keep after new snapshots are
19+
// taken, defaults to 1 to make sure ibc relayers work.
20+
SnapshotKeepRecent uint32 `mapstructure:"snapshot-keep-recent"`
21+
// SnapshotInterval defines the block interval the memiavl snapshot is taken, default to 1000.
22+
SnapshotInterval uint32 `mapstructure:"snapshot-interval"`
23+
// CacheSize defines the size of the cache for each memiavl store.
24+
CacheSize int `mapstructure:"cache-size"`
25+
}
26+
27+
func DefaultMemIAVLConfig() MemIAVLConfig {
28+
return MemIAVLConfig{
29+
CacheSize: DefaultCacheSize,
30+
SnapshotInterval: DefaultSnapshotInterval,
31+
SnapshotKeepRecent: 1,
32+
}
33+
}

sc/memiavl/config/toml.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package config
2+
3+
// DefaultConfigTemplate defines the configuration template for the memiavl configuration
4+
const DefaultConfigTemplate = `
5+
###############################################################################
6+
### MemIAVL Configuration ###
7+
###############################################################################
8+
9+
[memiavl]
10+
11+
# Enable defines if the memiavl should be enabled.
12+
enable = {{ .MemIAVL.Enable }}
13+
14+
# ZeroCopy defines if the memiavl should return slices pointing to mmap-ed buffers directly (zero-copy),
15+
# the zero-copied slices must not be retained beyond current block's execution.
16+
# the sdk address cache will be disabled if zero-copy is enabled.
17+
zero-copy = {{ .MemIAVL.ZeroCopy }}
18+
19+
# AsyncCommitBuffer defines the size of asynchronous commit queue, this greatly improve block catching-up
20+
# performance, -1 means synchronous commit.
21+
async-commit-buffer = {{ .MemIAVL.AsyncCommitBuffer }}
22+
23+
# SnapshotKeepRecent defines what many old snapshots (excluding the latest one) to keep after new snapshots are
24+
# taken, defaults to 1 to make sure ibc relayers work.
25+
snapshot-keep-recent = {{ .MemIAVL.SnapshotKeepRecent }}
26+
27+
# SnapshotInterval defines the block interval the memiavl snapshot is taken, default to 1000.
28+
snapshot-interval = {{ .MemIAVL.SnapshotInterval }}
29+
30+
# CacheSize defines the size of the cache for each memiavl store, default to 1000.
31+
cache-size = {{ .MemIAVL.CacheSize }}
32+
`

sc/memiavl/db/benchmark_test.go

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package memiavl
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"math/rand"
7+
"sort"
8+
"testing"
9+
10+
iavlcache "github.com/cosmos/iavl/cache"
11+
"github.com/stretchr/testify/require"
12+
"github.com/tidwall/btree"
13+
)
14+
15+
func BenchmarkByteCompare(b *testing.B) {
16+
var x, y [32]byte
17+
for i := 0; i < b.N; i++ {
18+
_ = bytes.Compare(x[:], y[:])
19+
}
20+
}
21+
22+
func BenchmarkRandomGet(b *testing.B) {
23+
amount := 1000000
24+
items := genRandItems(amount)
25+
targetKey := items[500].key
26+
targetValue := items[500].value
27+
targetItem := itemT{key: targetKey}
28+
29+
tree := New(0)
30+
for _, item := range items {
31+
tree.set(item.key, item.value)
32+
}
33+
34+
snapshotDir := b.TempDir()
35+
err := tree.WriteSnapshot(snapshotDir)
36+
require.NoError(b, err)
37+
snapshot, err := OpenSnapshot(snapshotDir)
38+
require.NoError(b, err)
39+
defer snapshot.Close()
40+
41+
b.Run("memiavl", func(b *testing.B) {
42+
require.Equal(b, targetValue, tree.Get(targetKey))
43+
44+
b.ResetTimer()
45+
for i := 0; i < b.N; i++ {
46+
_ = tree.Get(targetKey)
47+
}
48+
})
49+
b.Run("memiavl-disk", func(b *testing.B) {
50+
diskTree := NewFromSnapshot(snapshot, true, 0)
51+
require.Equal(b, targetValue, diskTree.Get(targetKey))
52+
53+
b.ResetTimer()
54+
for i := 0; i < b.N; i++ {
55+
_ = diskTree.Get(targetKey)
56+
}
57+
})
58+
b.Run("memiavl-disk-cache-hit", func(b *testing.B) {
59+
diskTree := NewFromSnapshot(snapshot, true, 1)
60+
require.Equal(b, targetValue, diskTree.Get(targetKey))
61+
62+
b.ResetTimer()
63+
for i := 0; i < b.N; i++ {
64+
_ = diskTree.Get(targetKey)
65+
}
66+
})
67+
b.Run("memiavl-disk-cache-miss", func(b *testing.B) {
68+
diskTree := NewFromSnapshot(snapshot, true, 0)
69+
// enforce an empty cache to emulate cache miss
70+
diskTree.cache = iavlcache.New(0)
71+
require.Equal(b, targetValue, diskTree.Get(targetKey))
72+
73+
b.ResetTimer()
74+
for i := 0; i < b.N; i++ {
75+
_ = diskTree.Get(targetKey)
76+
}
77+
})
78+
b.Run("btree-degree-2", func(b *testing.B) {
79+
bt2 := btree.NewBTreeGOptions(lessG, btree.Options{
80+
NoLocks: true,
81+
Degree: 2,
82+
})
83+
for _, item := range items {
84+
bt2.Set(item)
85+
}
86+
v, _ := bt2.Get(targetItem)
87+
require.Equal(b, targetValue, v.value)
88+
89+
b.ResetTimer()
90+
for i := 0; i < b.N; i++ {
91+
_, _ = bt2.Get(targetItem)
92+
}
93+
})
94+
b.Run("btree-degree-32", func(b *testing.B) {
95+
bt32 := btree.NewBTreeGOptions(lessG, btree.Options{
96+
NoLocks: true,
97+
Degree: 32,
98+
})
99+
for _, item := range items {
100+
bt32.Set(item)
101+
}
102+
v, _ := bt32.Get(targetItem)
103+
require.Equal(b, targetValue, v.value)
104+
105+
b.ResetTimer()
106+
for i := 0; i < b.N; i++ {
107+
_, _ = bt32.Get(targetItem)
108+
}
109+
})
110+
b.Run("iavl-lru", func(b *testing.B) {
111+
cache := iavlcache.New(amount)
112+
for _, item := range items {
113+
cache.Add(NewIavlCacheNode(item.key, item.value))
114+
}
115+
v := cache.Get(targetItem.key).(iavlCacheNode).value
116+
require.Equal(b, targetValue, v)
117+
118+
b.ResetTimer()
119+
for i := 0; i < b.N; i++ {
120+
_ = cache.Get(targetKey).(iavlCacheNode).value
121+
}
122+
})
123+
b.Run("go-map", func(b *testing.B) {
124+
m := make(map[string][]byte, amount)
125+
for _, item := range items {
126+
m[string(item.key)] = item.value
127+
}
128+
v := m[string(targetItem.key)]
129+
require.Equal(b, targetValue, v)
130+
131+
b.ResetTimer()
132+
for i := 0; i < b.N; i++ {
133+
_ = m[string(targetKey)]
134+
}
135+
})
136+
137+
b.Run("binary-search", func(b *testing.B) {
138+
// the last benchmark sort the items in place
139+
sort.Slice(items, func(i, j int) bool {
140+
return bytes.Compare(items[i].key, items[j].key) < 0
141+
})
142+
cmp := func(i int) bool { return bytes.Compare(items[i].key, targetKey) != -1 }
143+
i := sort.Search(len(items), cmp)
144+
require.Equal(b, targetValue, items[i].value)
145+
146+
b.ResetTimer()
147+
for i := 0; i < b.N; i++ {
148+
n := sort.Search(len(items), cmp)
149+
_ = items[n].value
150+
}
151+
})
152+
}
153+
154+
func BenchmarkRandomSet(b *testing.B) {
155+
items := genRandItems(1000000)
156+
b.ResetTimer()
157+
b.Run("memiavl", func(b *testing.B) {
158+
for i := 0; i < b.N; i++ {
159+
tree := New(0)
160+
for _, item := range items {
161+
tree.set(item.key, item.value)
162+
}
163+
}
164+
})
165+
b.Run("tree2", func(b *testing.B) {
166+
for i := 0; i < b.N; i++ {
167+
bt := btree.NewBTreeGOptions(lessG, btree.Options{
168+
NoLocks: true,
169+
Degree: 2,
170+
})
171+
for _, item := range items {
172+
bt.Set(item)
173+
}
174+
}
175+
})
176+
b.Run("tree32", func(b *testing.B) {
177+
for i := 0; i < b.N; i++ {
178+
bt := btree.NewBTreeGOptions(lessG, btree.Options{
179+
NoLocks: true,
180+
Degree: 32,
181+
})
182+
for _, item := range items {
183+
bt.Set(item)
184+
}
185+
}
186+
})
187+
}
188+
189+
type itemT struct {
190+
key, value []byte
191+
}
192+
193+
func lessG(a, b itemT) bool {
194+
return bytes.Compare(a.key, b.key) == -1
195+
}
196+
197+
func int64ToItemT(n uint64) itemT {
198+
var key, value [8]byte
199+
binary.BigEndian.PutUint64(key[:], n)
200+
binary.LittleEndian.PutUint64(value[:], n)
201+
return itemT{
202+
key: key[:],
203+
value: value[:],
204+
}
205+
}
206+
207+
func genRandItems(n int) []itemT {
208+
r := rand.New(rand.NewSource(0))
209+
items := make([]itemT, n)
210+
itemsM := make(map[uint64]bool)
211+
for i := 0; i < n; i++ {
212+
for {
213+
key := uint64(r.Int63n(10000000000000000))
214+
if !itemsM[key] {
215+
itemsM[key] = true
216+
items[i] = int64ToItemT(key)
217+
break
218+
}
219+
}
220+
}
221+
return items
222+
}
223+
224+
type iavlCacheNode struct {
225+
key []byte
226+
value []byte
227+
}
228+
229+
func NewIavlCacheNode(key, value []byte) iavlCacheNode {
230+
return iavlCacheNode{key, value}
231+
}
232+
233+
func (n iavlCacheNode) GetKey() []byte {
234+
return n.key
235+
}
236+
237+
func (n iavlCacheNode) GetCacheKey() []byte {
238+
return n.key
239+
}

0 commit comments

Comments
 (0)