Skip to content

Commit d099846

Browse files
committed
[Feat]: Split MemoryBTree key-value pairs into separate regions
1 parent f846d68 commit d099846

File tree

12 files changed

+365
-55
lines changed

12 files changed

+365
-55
lines changed

mops.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "memory-collection"
3-
version = "0.0.1"
3+
version = "0.0.2"
44
description = "A collection of data structures that store their data in stable memory"
55
repository = "https://github.com/NatLabs/memory-collection"
66
keywords = ["region", "sorted", "persistent", "map", "encoding"]
@@ -11,13 +11,15 @@ base = "0.12.1"
1111
itertools = "0.2.1"
1212
memory-region = "1.2.4"
1313
buffer-deque = "0.1.0"
14+
memory-collection = "0.0.1"
1415

1516
[dev-dependencies]
1617
test = "2.0.0"
1718
bench = "1.0.0"
1819
augmented-btrees = "0.5.2"
1920
fuzz = "0.2.1"
2021
MotokoStableBTree = "https://github.com/sardariuss/MotokoStableBTree#master@b590ede4489c2d4b2189299c3a2cc35dc4faa3fc"
22+
memory-collection-btree-v0-v0_0_1_migration = "https://github.com/NatLabs/memory-collection#master@bdba9e30ad95048634f23fe9c45c1e93c7787ce0"
2123

2224
[toolchain]
2325
wasmtime = "14.0.4"

src/MemoryBTree/Base.mo

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@ module {
3434
public type MemoryCmp<A> = MemoryCmp.MemoryCmp<A>;
3535

3636
public type MemoryBTree = Migrations.MemoryBTree;
37-
public type Node = Migrations.Node;
38-
public type Leaf = Migrations.Leaf;
39-
public type Branch = Migrations.Branch;
4037
public type VersionedMemoryBTree = Migrations.VersionedMemoryBTree;
4138

4239
public type MemoryBlock = T.MemoryBlock;
@@ -60,13 +57,10 @@ module {
6057
var depth = 0;
6158
var is_root_a_leaf = true;
6259

63-
metadata = MemoryRegion.new();
64-
blocks = MemoryRegion.new();
65-
blobs = MemoryRegion.new();
66-
6760
leaves = MemoryRegion.new();
6861
branches = MemoryRegion.new();
6962
data = MemoryRegion.new();
63+
values = MemoryRegion.new();
7064

7165
};
7266

@@ -95,7 +89,7 @@ module {
9589
public let POINTER_SIZE = 12;
9690
public let LAYOUT_VERSION = 0;
9791

98-
let MC = {
92+
public let MC = {
9993

10094
REGION_HEADER_SIZE = 64;
10195

@@ -110,12 +104,24 @@ module {
110104
COUNT_ADDRESS = 22; // 8 bytes
111105
DEPTH_ADDRESS = 30; // 1 byte
112106
IS_ROOT_A_LEAF_ADDRESS = 31; // 1 byte
107+
VALUES_REGION_ID_ADDRESS = 32; // 4 bytes
113108

114109
// values
115110
MAGIC : Blob = "BTR";
116111
LAYOUT_VERSION : Nat8 = 0;
117112
};
118113

114+
VALUES = {
115+
// addresses
116+
MAGIC_ADDRESS = 0;
117+
LAYOUT_VERSION_ADDRESS = 3;
118+
DATA_REGION_ID_ADDRESS = 4; // 4 bytes
119+
120+
// values
121+
MAGIC : Blob = "VLS";
122+
LAYOUT_VERSION : Nat8 = 0;
123+
};
124+
119125
BRANCHES = {
120126
// addresses
121127
MAGIC_ADDRESS = 0;
@@ -154,8 +160,16 @@ module {
154160
MemoryRegion.storeNat64(btree.data, MC.DATA.ROOT_ADDRESS, 0); // set to default value, will be updated once a node is created
155161
MemoryRegion.storeNat64(btree.data, MC.DATA.COUNT_ADDRESS, 0);
156162
MemoryRegion.storeNat8(btree.data, MC.DATA.DEPTH_ADDRESS, 0);
163+
MemoryRegion.storeNat8(btree.data, MC.DATA.IS_ROOT_A_LEAF_ADDRESS, 0);
164+
MemoryRegion.storeNat32(btree.data, MC.DATA.VALUES_REGION_ID_ADDRESS, Nat32.fromNat(MemoryRegion.id(btree.values)));
157165
assert MemoryRegion.allocated(btree.data) == MC.REGION_HEADER_SIZE;
158166

167+
ignore MemoryRegion.allocate(btree.values, MC.REGION_HEADER_SIZE);
168+
MemoryRegion.storeBlob(btree.values, MC.VALUES.MAGIC_ADDRESS, MC.VALUES.MAGIC);
169+
MemoryRegion.storeNat8(btree.values, MC.VALUES.LAYOUT_VERSION_ADDRESS, MC.VALUES.LAYOUT_VERSION);
170+
MemoryRegion.storeNat32(btree.values, MC.VALUES.DATA_REGION_ID_ADDRESS, Nat32.fromNat(MemoryRegion.id(btree.data)));
171+
assert MemoryRegion.allocated(btree.values) == MC.REGION_HEADER_SIZE;
172+
159173
ignore MemoryRegion.allocate(btree.branches, MC.REGION_HEADER_SIZE);
160174
MemoryRegion.storeBlob(btree.branches, MC.BRANCHES.MAGIC_ADDRESS, MC.BRANCHES.MAGIC);
161175
MemoryRegion.storeNat8(btree.branches, MC.BRANCHES.LAYOUT_VERSION_ADDRESS, MC.BRANCHES.LAYOUT_VERSION);
@@ -187,7 +201,7 @@ module {
187201
};
188202

189203
public func toVersioned(btree : MemoryBTree) : VersionedMemoryBTree {
190-
#v0(btree);
204+
Migrations.addVersion(btree);
191205
};
192206

193207
public func bytes(btree : MemoryBTree) : Nat {

src/MemoryBTree/Migrations/V0_0_1.mo

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import Nat "mo:base/Nat";
2+
3+
import MemoryRegion "mo:memory-region/MemoryRegion";
4+
import RevIter "mo:itertools/RevIter";
5+
// import Branch "mo:augmented-btrees/BpTree/Branch";
6+
7+
import Blobify "../../TypeUtils/Blobify";
8+
import MemoryCmp "../../TypeUtils/MemoryCmp";
9+
10+
module V0_0_1 {
11+
// thinking the version should match the mops version where the layout or heap structure was last changed
12+
13+
public type Address = Nat;
14+
type Size = Nat;
15+
16+
public type MemoryBlock = (Address, Size);
17+
18+
type MemoryRegionV1 = MemoryRegion.MemoryRegionV1;
19+
type Blobify<A> = Blobify.Blobify<A>;
20+
type RevIter<A> = RevIter.RevIter<A>;
21+
22+
public type MemoryCmp<A> = MemoryCmp.MemoryCmp<A>;
23+
24+
public type MemoryBTree = {
25+
is_set : Bool; // is true, only keys are stored
26+
node_capacity : Nat;
27+
var count : Nat;
28+
var root : Nat;
29+
var branch_count : Nat; // number of branch nodes
30+
var leaf_count : Nat; // number of leaf nodes
31+
var depth : Nat;
32+
var is_root_a_leaf : Bool;
33+
34+
leaves : MemoryRegionV1;
35+
branches : MemoryRegionV1;
36+
data : MemoryRegionV1;
37+
values : MemoryRegionV1;
38+
39+
};
40+
41+
public type Leaf = (
42+
nats : [var Nat], // [address, index, count]
43+
adjacent_nodes : [var ?Nat], // [parent, prev, next] (is_root if parent is null)
44+
key_blocks : [var ?(MemoryBlock)], // [... ((key address, key size), key blob)]
45+
val_blocks : [var ?(MemoryBlock)],
46+
kv_blobs : [var ?(Blob, Blob)],
47+
_branch_children_nodes : [var ?Nat], // [... child address]
48+
_branch_keys_blobs : [var ?Blob],
49+
);
50+
51+
public type Branch = (
52+
nats : [var Nat], // [address, index, count, subtree_size]
53+
parent : [var ?Nat], // parent
54+
key_blocks : [var ?(MemoryBlock)], // [... ((key address, key size), key blob)]
55+
_leaf_val_blocks : [var ?(MemoryBlock)],
56+
_leaf_kv_blobs : [var ?(Blob, Blob)],
57+
children_nodes : [var ?Nat], // [... child address]
58+
keys_blobs : [var ?Blob],
59+
);
60+
61+
};

src/MemoryBTree/Migrations/lib.mo

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,42 @@
11
import Debug "mo:base/Debug";
2+
import Nat32 "mo:base/Nat32";
3+
4+
import MemoryRegion "mo:memory-region/MemoryRegion";
25

36
import V0 "V0";
7+
import V0_0_1 "V0_0_1";
48

59
module Migrations {
610

7-
public type MemoryBTree = V0.MemoryBTree;
8-
public type Leaf = V0.Leaf;
9-
public type Node = V0.Node;
10-
public type Branch = V0.Branch;
11+
// should update to the latest version
12+
public type MemoryBTree = V0_0_1.MemoryBTree;
13+
public type Leaf = V0_0_1.Leaf;
14+
public type Branch = V0_0_1.Branch;
1115

1216
public type VersionedMemoryBTree = {
1317
#v0 : V0.MemoryBTree;
18+
#v0_0_1 : V0_0_1.MemoryBTree;
1419
};
1520

1621
public type StableStore = VersionedMemoryBTree;
1722

1823
public func upgrade(versions : VersionedMemoryBTree) : VersionedMemoryBTree {
1924
switch (versions) {
20-
case (#v0(v0)) versions;
25+
case (#v0(v0)) {
26+
Debug.trap("Migration Error: Migrating from #v0 is not supported");
27+
};
28+
case (#v0_0_1(v0_0_1)) versions;
2129
};
2230
};
2331

2432
public func getCurrentVersion(versions : VersionedMemoryBTree) : MemoryBTree {
2533
switch (versions) {
26-
case (#v0(v0)) v0;
34+
case (#v0_0_1(curr)) curr;
2735
case (_) Debug.trap("Unsupported version. Please upgrade the memory buffer to the latest version.");
2836
};
2937
};
38+
39+
public func addVersion(btree : MemoryBTree) : VersionedMemoryBTree {
40+
#v0_0_1(btree);
41+
};
3042
};
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import Debug "mo:base/Debug";
2+
import Nat32 "mo:base/Nat32";
3+
import Nat16 "mo:base/Nat16";
4+
import Nat64 "mo:base/Nat64";
5+
import Nat8 "mo:base/Nat8";
6+
import Iter "mo:base/Iter";
7+
8+
import MemoryRegion "mo:memory-region/MemoryRegion";
9+
import Itertools "mo:itertools/Iter";
10+
11+
import V0 "V0";
12+
import V0_0_1 "V0_0_1";
13+
14+
module Migrations {
15+
16+
// should update to the latest version
17+
public type MemoryBTree = V0_0_1.MemoryBTree;
18+
public type Leaf = V0_0_1.Leaf;
19+
public type Branch = V0_0_1.Branch;
20+
21+
public type VersionedMemoryBTree = {
22+
#v0 : V0.MemoryBTree;
23+
#v0_0_1 : V0_0_1.MemoryBTree;
24+
};
25+
26+
public type StableStore = VersionedMemoryBTree;
27+
type Address = Nat;
28+
29+
public func get_depth(btree : MemoryBTree, node_address : Nat) : Nat {
30+
let depth = MemoryRegion.loadNat8(btree.branches, node_address + 3) |> Nat8.toNat(_);
31+
32+
depth;
33+
};
34+
35+
public func has_leaves(btree : MemoryBTree, branch_address : Nat) : Bool {
36+
let depth = get_depth(btree, branch_address);
37+
depth == 2;
38+
};
39+
40+
public func CHILDREN_START(btree : MemoryBTree) : Nat {
41+
64 + ((btree.node_capacity - 1) * 8);
42+
};
43+
public func get_child_offset(btree : MemoryBTree, branch_address : Nat, i : Nat) : Nat {
44+
branch_address + CHILDREN_START(btree) + (i * 8);
45+
};
46+
public func get_child(btree : MemoryBTree, branch_address : Nat, i : Nat) : ?Nat {
47+
48+
MemoryRegion.loadNat64(btree.branches, get_child_offset(btree, branch_address, i))
49+
|> ?Nat64.toNat(_);
50+
};
51+
52+
public func get_min_leaf_address(btree : MemoryBTree) : Nat {
53+
var curr = btree.root;
54+
var is_address_a_leaf = btree.is_root_a_leaf;
55+
56+
loop {
57+
switch (is_address_a_leaf) {
58+
case (false) {
59+
let ?first_child = get_child(btree, curr, 0) else Debug.trap("get_min_leaf: accessed a null value");
60+
is_address_a_leaf := has_leaves(btree, curr);
61+
curr := first_child;
62+
};
63+
case (true) return curr;
64+
};
65+
};
66+
};
67+
68+
public func get_kv_address_offset(leaf_address : Nat, i : Nat) : Nat {
69+
leaf_address + 64 + (i * 8);
70+
};
71+
72+
public func leaf_get_kv_address(btree : MemoryBTree, address : Nat, i : Nat) : ?Nat {
73+
let kv_address_offset = get_kv_address_offset(address, i);
74+
let opt_id = MemoryRegion.loadNat64(btree.leaves, kv_address_offset);
75+
76+
if (opt_id == 0x00) null else ?(Nat64.toNat(opt_id));
77+
};
78+
79+
public func leaf_get_count(btree : MemoryBTree, address : Nat) : Nat {
80+
MemoryRegion.loadNat16(btree.leaves, address + 6) |> Nat16.toNat(_);
81+
};
82+
public func leaf_get_kv_address_offset(leaf_address : Nat, i : Nat) : Nat {
83+
leaf_address + 64 + (i * 8);
84+
};
85+
86+
public func leaf_get_next(btree : MemoryBTree, address : Nat) : ?Nat {
87+
88+
let next = MemoryRegion.loadNat64(btree.leaves, address + 24);
89+
if (next == 0x00) return null;
90+
?Nat64.toNat(next);
91+
};
92+
93+
public func kv_block_addresses(btree : MemoryBTree) : Iter.Iter<Address> {
94+
95+
let min_leaf = get_min_leaf_address(btree);
96+
var i = 0;
97+
var leaf_count = leaf_get_count(btree, min_leaf);
98+
var var_leaf = ?min_leaf;
99+
100+
object {
101+
public func next() : ?Address {
102+
let ?leaf = var_leaf else return null;
103+
104+
if (i >= leaf_count) {
105+
switch (leaf_get_next(btree, leaf)) {
106+
case (null) var_leaf := null;
107+
case (?next_address) {
108+
var_leaf := ?next_address;
109+
leaf_count := leaf_get_count(btree, leaf);
110+
};
111+
};
112+
113+
i := 0;
114+
return next();
115+
};
116+
117+
let address = leaf_get_kv_address(btree, leaf, i);
118+
i += 1;
119+
return address;
120+
};
121+
};
122+
123+
};
124+
125+
public func upgrade(versions : VersionedMemoryBTree) : VersionedMemoryBTree {
126+
switch (versions) {
127+
case (#v0(v0)) {
128+
let values = MemoryRegion.new();
129+
130+
let VALUES_REGION_ID_ADDRESS = 32;
131+
MemoryRegion.storeNat32(v0.data, VALUES_REGION_ID_ADDRESS, Nat32.fromNat(MemoryRegion.id(values)));
132+
133+
// move all values from data region to values region
134+
// might not upgrade if the data is too large
135+
136+
let REFERENCE_COUNT_START = 0;
137+
let KEY_SIZE_START = 1;
138+
let VAL_POINTER_START = 3;
139+
let VAL_SIZE_START = 11;
140+
let KEY_BLOB_START = 15;
141+
142+
let BLOCK_ENTRY_SIZE = 15;
143+
144+
var key_block_address = 64;
145+
146+
for (i in Itertools.range(0, v0.count)) {
147+
let key_size = MemoryRegion.loadNat16(v0.data, key_block_address + KEY_SIZE_START) |> Nat16.toNat(_);
148+
let val_size = MemoryRegion.loadNat32(v0.data, key_block_address + VAL_SIZE_START) |> Nat32.toNat(_);
149+
let val_address = MemoryRegion.loadNat64(v0.data, key_block_address + VAL_POINTER_START) |> Nat64.toNat(_);
150+
151+
let val_blob = MemoryRegion.loadBlob(v0.data, val_address, val_size);
152+
MemoryRegion.deallocate(v0.data, val_address, val_size);
153+
154+
let new_val_address = MemoryRegion.addBlob(values, val_blob);
155+
156+
MemoryRegion.storeNat64(v0.data, key_block_address + VAL_POINTER_START, Nat64.fromNat(new_val_address));
157+
158+
key_block_address += BLOCK_ENTRY_SIZE + key_size + val_size;
159+
160+
};
161+
162+
#v0_0_1({
163+
v0 with values;
164+
var count = v0.count;
165+
var root = v0.root;
166+
var branch_count = v0.branch_count;
167+
var leaf_count = v0.leaf_count;
168+
var depth = v0.depth;
169+
var is_root_a_leaf = v0.is_root_a_leaf;
170+
});
171+
};
172+
case (#v0_0_1(v0_0_1)) versions;
173+
};
174+
};
175+
176+
};

0 commit comments

Comments
 (0)