tigerbeetle-node 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +302 -101
- package/dist/index.d.ts +70 -72
- package/dist/index.js +70 -72
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/scripts/download_node_headers.sh +14 -7
- package/src/index.ts +6 -10
- package/src/node.zig +6 -3
- package/src/tigerbeetle/scripts/benchmark.sh +4 -4
- package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
- package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
- package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
- package/src/tigerbeetle/scripts/install.sh +19 -4
- package/src/tigerbeetle/scripts/install_zig.bat +5 -1
- package/src/tigerbeetle/scripts/install_zig.sh +24 -14
- package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
- package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
- package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
- package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
- package/src/tigerbeetle/src/benchmark.zig +4 -2
- package/src/tigerbeetle/src/benchmark_array_search.zig +3 -3
- package/src/tigerbeetle/src/c/tb_client/thread.zig +8 -9
- package/src/tigerbeetle/src/c/tb_client.h +100 -80
- package/src/tigerbeetle/src/c/tb_client.zig +4 -1
- package/src/tigerbeetle/src/cli.zig +1 -1
- package/src/tigerbeetle/src/config.zig +48 -16
- package/src/tigerbeetle/src/demo.zig +3 -1
- package/src/tigerbeetle/src/eytzinger_benchmark.zig +3 -3
- package/src/tigerbeetle/src/io/linux.zig +1 -1
- package/src/tigerbeetle/src/lsm/README.md +214 -0
- package/src/tigerbeetle/src/lsm/binary_search.zig +137 -10
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +43 -0
- package/src/tigerbeetle/src/lsm/compaction.zig +352 -398
- package/src/tigerbeetle/src/lsm/composite_key.zig +2 -0
- package/src/tigerbeetle/src/lsm/eytzinger.zig +1 -1
- package/src/tigerbeetle/src/lsm/forest.zig +21 -447
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +412 -0
- package/src/tigerbeetle/src/lsm/grid.zig +145 -69
- package/src/tigerbeetle/src/lsm/groove.zig +196 -133
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +40 -18
- package/src/tigerbeetle/src/lsm/level_iterator.zig +28 -9
- package/src/tigerbeetle/src/lsm/manifest.zig +81 -181
- package/src/tigerbeetle/src/lsm/manifest_level.zig +210 -454
- package/src/tigerbeetle/src/lsm/manifest_log.zig +77 -28
- package/src/tigerbeetle/src/lsm/posted_groove.zig +64 -76
- package/src/tigerbeetle/src/lsm/segmented_array.zig +561 -241
- package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +62 -12
- package/src/tigerbeetle/src/lsm/table.zig +83 -48
- package/src/tigerbeetle/src/lsm/table_immutable.zig +30 -23
- package/src/tigerbeetle/src/lsm/table_iterator.zig +25 -14
- package/src/tigerbeetle/src/lsm/table_mutable.zig +63 -12
- package/src/tigerbeetle/src/lsm/test.zig +49 -55
- package/src/tigerbeetle/src/lsm/tree.zig +407 -402
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +457 -0
- package/src/tigerbeetle/src/main.zig +28 -6
- package/src/tigerbeetle/src/message_bus.zig +2 -2
- package/src/tigerbeetle/src/message_pool.zig +14 -17
- package/src/tigerbeetle/src/simulator.zig +145 -112
- package/src/tigerbeetle/src/state_machine.zig +338 -228
- package/src/tigerbeetle/src/static_allocator.zig +65 -0
- package/src/tigerbeetle/src/storage.zig +3 -7
- package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
- package/src/tigerbeetle/src/test/accounting/workload.zig +819 -0
- package/src/tigerbeetle/src/test/cluster.zig +18 -48
- package/src/tigerbeetle/src/test/conductor.zig +365 -0
- package/src/tigerbeetle/src/test/fuzz.zig +121 -0
- package/src/tigerbeetle/src/test/id.zig +89 -0
- package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
- package/src/tigerbeetle/src/test/state_checker.zig +93 -69
- package/src/tigerbeetle/src/test/state_machine.zig +11 -35
- package/src/tigerbeetle/src/test/storage.zig +29 -8
- package/src/tigerbeetle/src/tigerbeetle.zig +14 -16
- package/src/tigerbeetle/src/unit_tests.zig +7 -0
- package/src/tigerbeetle/src/vopr.zig +494 -0
- package/src/tigerbeetle/src/vopr_hub/README.md +58 -0
- package/src/tigerbeetle/src/vopr_hub/SETUP.md +199 -0
- package/src/tigerbeetle/src/vopr_hub/go.mod +3 -0
- package/src/tigerbeetle/src/vopr_hub/main.go +1022 -0
- package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +3 -0
- package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +403 -0
- package/src/tigerbeetle/src/vsr/client.zig +13 -0
- package/src/tigerbeetle/src/vsr/journal.zig +16 -13
- package/src/tigerbeetle/src/vsr/replica.zig +924 -491
- package/src/tigerbeetle/src/vsr/superblock.zig +55 -37
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -10
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +2 -2
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +18 -3
- package/src/tigerbeetle/src/vsr.zig +75 -55
- package/src/tigerbeetle/scripts/vopr.bat +0 -48
- package/src/tigerbeetle/scripts/vopr.sh +0 -33
|
@@ -22,9 +22,12 @@ pub fn TableImmutableType(comptime Table: type) type {
|
|
|
22
22
|
snapshot_min: u64,
|
|
23
23
|
free: bool,
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/// `commit_entries_max` is the maximum number of Values that can be inserted by a single commit.
|
|
26
|
+
pub fn init(allocator: mem.Allocator, commit_entries_max: u32) !TableImmutable {
|
|
27
|
+
assert(commit_entries_max > 0);
|
|
28
|
+
|
|
26
29
|
// The in-memory immutable table is the same size as the mutable table:
|
|
27
|
-
const value_count_max =
|
|
30
|
+
const value_count_max = commit_entries_max * config.lsm_batch_multiple;
|
|
28
31
|
const data_block_count = div_ceil(value_count_max, Table.data.value_count_max);
|
|
29
32
|
assert(data_block_count <= Table.data_block_count_max);
|
|
30
33
|
|
|
@@ -45,11 +48,13 @@ pub fn TableImmutableType(comptime Table: type) type {
|
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
pub inline fn key_min(table: *const TableImmutable) Key {
|
|
51
|
+
assert(!table.free);
|
|
48
52
|
assert(table.values.len > 0);
|
|
49
53
|
return key_from_value(&table.values[0]);
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
pub inline fn key_max(table: *const TableImmutable) Key {
|
|
57
|
+
assert(!table.free);
|
|
53
58
|
assert(table.values.len > 0);
|
|
54
59
|
return key_from_value(&table.values[table.values.len - 1]);
|
|
55
60
|
}
|
|
@@ -96,7 +101,7 @@ pub fn TableImmutableType(comptime Table: type) type {
|
|
|
96
101
|
assert(i > 0);
|
|
97
102
|
const left_key = key_from_value(&sorted_values[i - 1]);
|
|
98
103
|
const right_key = key_from_value(&sorted_values[i]);
|
|
99
|
-
assert(compare_keys(left_key, right_key)
|
|
104
|
+
assert(compare_keys(left_key, right_key) == .lt);
|
|
100
105
|
}
|
|
101
106
|
}
|
|
102
107
|
|
|
@@ -112,20 +117,19 @@ pub fn TableImmutableType(comptime Table: type) type {
|
|
|
112
117
|
pub fn get(table: *const TableImmutable, key: Key) ?*const Value {
|
|
113
118
|
assert(!table.free);
|
|
114
119
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
120
|
+
const result = binary_search.binary_search_values(
|
|
121
|
+
Key,
|
|
122
|
+
Value,
|
|
123
|
+
key_from_value,
|
|
124
|
+
compare_keys,
|
|
125
|
+
table.values,
|
|
126
|
+
key,
|
|
127
|
+
.{},
|
|
128
|
+
);
|
|
129
|
+
if (result.exact) {
|
|
130
|
+
const value = &table.values[result.index];
|
|
131
|
+
if (config.verify) assert(compare_keys(key, key_from_value(value)) == .eq);
|
|
132
|
+
return value;
|
|
129
133
|
}
|
|
130
134
|
|
|
131
135
|
return null;
|
|
@@ -140,7 +144,7 @@ pub fn TableImmutableIteratorType(comptime Table: type, comptime Storage: type)
|
|
|
140
144
|
const TableImmutableIterator = @This();
|
|
141
145
|
const TableImmutable = TableImmutableType(Table);
|
|
142
146
|
|
|
143
|
-
table: *TableImmutable,
|
|
147
|
+
table: *const TableImmutable,
|
|
144
148
|
values_index: u32,
|
|
145
149
|
|
|
146
150
|
pub fn init(allocator: mem.Allocator) !TableImmutableIterator {
|
|
@@ -158,7 +162,7 @@ pub fn TableImmutableIteratorType(comptime Table: type, comptime Storage: type)
|
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
pub const Context = struct {
|
|
161
|
-
table: *TableImmutable,
|
|
165
|
+
table: *const TableImmutable,
|
|
162
166
|
};
|
|
163
167
|
|
|
164
168
|
pub fn start(
|
|
@@ -174,21 +178,24 @@ pub fn TableImmutableIteratorType(comptime Table: type, comptime Storage: type)
|
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
pub fn tick(it: *const TableImmutableIterator) bool {
|
|
177
|
-
|
|
181
|
+
assert(!it.table.free);
|
|
178
182
|
return false; // No I/O is performed as it's all in memory.
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
pub fn buffered_all_values(it: *const TableImmutableIterator) bool {
|
|
182
|
-
|
|
186
|
+
assert(!it.table.free);
|
|
183
187
|
return true; // All values are "buffered" in memory.
|
|
184
188
|
}
|
|
185
189
|
|
|
186
|
-
pub fn peek(it: *const TableImmutableIterator)
|
|
187
|
-
|
|
190
|
+
pub fn peek(it: *const TableImmutableIterator) error{Empty, Drained}!Table.Key {
|
|
191
|
+
// NOTE: This iterator is never Drained as all values are in memory (tick is a no-op).
|
|
192
|
+
assert(!it.table.free);
|
|
193
|
+
if (it.values_index == it.table.values.len) return error.Empty;
|
|
188
194
|
return Table.key_from_value(&it.table.values[it.values_index]);
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
pub fn pop(it: *TableImmutableIterator) Table.Value {
|
|
198
|
+
assert(!it.table.free);
|
|
192
199
|
defer it.values_index += 1;
|
|
193
200
|
return it.table.values[it.values_index];
|
|
194
201
|
}
|
|
@@ -9,6 +9,7 @@ const RingBuffer = @import("../ring_buffer.zig").RingBuffer;
|
|
|
9
9
|
const ManifestType = @import("manifest.zig").ManifestType;
|
|
10
10
|
const GridType = @import("grid.zig").GridType;
|
|
11
11
|
|
|
12
|
+
/// A TableIterator iterates a table's values in ascending-key order.
|
|
12
13
|
pub fn TableIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
13
14
|
return struct {
|
|
14
15
|
const TableIterator = @This();
|
|
@@ -38,9 +39,8 @@ pub fn TableIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
38
39
|
/// out of blocks in the blocks ring buffer but haven't buffered a full block of
|
|
39
40
|
/// values in memory. In this case, we copy values from the head of blocks to this
|
|
40
41
|
/// ring buffer to make that block available for reading further values.
|
|
41
|
-
/// Thus, we guarantee that iterators will always have at least a block's worth
|
|
42
|
-
/// values buffered.
|
|
43
|
-
/// iteration is complete.
|
|
42
|
+
/// Thus, we guarantee that iterators will always have at least a block's worth
|
|
43
|
+
/// of values buffered.
|
|
44
44
|
values: ValuesRingBuffer,
|
|
45
45
|
|
|
46
46
|
data_blocks: RingBuffer(Grid.BlockPtr, 2, .array),
|
|
@@ -100,8 +100,8 @@ pub fn TableIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
100
100
|
|
|
101
101
|
pub const Context = struct {
|
|
102
102
|
grid: *Grid,
|
|
103
|
-
address: u64,
|
|
104
|
-
checksum: u128,
|
|
103
|
+
address: u64, // Table index block address.
|
|
104
|
+
checksum: u128, // Table index block checksum.
|
|
105
105
|
index_block_callback: ?IndexBlockCallback = null,
|
|
106
106
|
};
|
|
107
107
|
|
|
@@ -148,6 +148,7 @@ pub fn TableIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
148
148
|
&it.read,
|
|
149
149
|
it.address,
|
|
150
150
|
it.checksum,
|
|
151
|
+
.index,
|
|
151
152
|
);
|
|
152
153
|
return true;
|
|
153
154
|
}
|
|
@@ -171,29 +172,31 @@ pub fn TableIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
171
172
|
|
|
172
173
|
assert(!it.read_pending);
|
|
173
174
|
it.read_pending = true;
|
|
174
|
-
it.grid.read_block(on_read, &it.read, address, checksum);
|
|
175
|
+
it.grid.read_block(on_read, &it.read, address, checksum, .data);
|
|
175
176
|
}
|
|
176
177
|
|
|
177
178
|
fn on_read_table_index(read: *Grid.Read, block: Grid.BlockPtrConst) void {
|
|
178
179
|
const it = @fieldParentPtr(TableIterator, "read", read);
|
|
179
180
|
assert(it.read_pending);
|
|
181
|
+
assert(it.data_block_index == 0);
|
|
180
182
|
it.read_pending = false;
|
|
181
183
|
|
|
182
184
|
assert(it.read_table_index);
|
|
183
185
|
it.read_table_index = false;
|
|
184
186
|
|
|
187
|
+
// Copy the bytes read into a buffer owned by the iterator since the Grid
|
|
188
|
+
// only guarantees the provided pointer to be valid in this callback.
|
|
189
|
+
mem.copy(u8, it.index_block, block);
|
|
190
|
+
|
|
185
191
|
if (it.index_block_callback) |callback| {
|
|
186
192
|
it.index_block_callback = null;
|
|
187
193
|
callback(it, block);
|
|
188
194
|
}
|
|
189
195
|
|
|
190
|
-
// Copy the bytes read into a buffer owned by the iterator since the Grid
|
|
191
|
-
// only guarantees the provided pointer to be valid in this callback.
|
|
192
|
-
mem.copy(u8, it.index_block, block);
|
|
193
|
-
|
|
194
196
|
const read_pending = it.tick();
|
|
195
197
|
// After reading the table index, we always read at least one data block.
|
|
196
198
|
assert(read_pending);
|
|
199
|
+
assert(it.read_pending);
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
fn on_read(read: *Grid.Read, block: Grid.BlockPtrConst) void {
|
|
@@ -256,22 +259,30 @@ pub fn TableIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
256
259
|
it.buffered_value_count() >= Table.data.value_count_max;
|
|
257
260
|
}
|
|
258
261
|
|
|
259
|
-
|
|
262
|
+
/// Returns either:
|
|
263
|
+
/// - the next Key, if available.
|
|
264
|
+
/// - error.Empty when there are no values remaining to iterate.
|
|
265
|
+
/// - error.Drained when the iterator isn't empty, but some values
|
|
266
|
+
/// still need to be buffered into memory via tick().
|
|
267
|
+
pub fn peek(it: TableIterator) error{Empty, Drained}!Table.Key {
|
|
260
268
|
assert(!it.read_pending);
|
|
261
269
|
assert(!it.read_table_index);
|
|
262
270
|
|
|
263
271
|
if (it.values.head_ptr_const()) |value| return Table.key_from_value(value);
|
|
264
272
|
|
|
265
273
|
const block = it.data_blocks.head() orelse {
|
|
266
|
-
|
|
267
|
-
|
|
274
|
+
// NOTE: Even if there are no values to peek, some may be unbuffered.
|
|
275
|
+
// We call buffered_all_values() to distinguish between the iterator
|
|
276
|
+
// being empty and needing to tic() to refill values.
|
|
277
|
+
if (!it.buffered_all_values()) return error.Drained;
|
|
278
|
+
return error.Empty;
|
|
268
279
|
};
|
|
269
280
|
|
|
270
281
|
const values = Table.data_block_values_used(block);
|
|
271
282
|
return Table.key_from_value(&values[it.value]);
|
|
272
283
|
}
|
|
273
284
|
|
|
274
|
-
/// This
|
|
285
|
+
/// This may only be called after peek() returns a Key (and not Empty or Drained)
|
|
275
286
|
pub fn pop(it: *TableIterator) Table.Value {
|
|
276
287
|
assert(!it.read_pending);
|
|
277
288
|
assert(!it.read_table_index);
|
|
@@ -5,6 +5,7 @@ const assert = std.debug.assert;
|
|
|
5
5
|
|
|
6
6
|
const config = @import("../config.zig");
|
|
7
7
|
const div_ceil = @import("../util.zig").div_ceil;
|
|
8
|
+
const SetAssociativeCache = @import("set_associative_cache.zig").SetAssociativeCache;
|
|
8
9
|
|
|
9
10
|
/// Range queries are not supported on the TableMutable, it must first be made immutable.
|
|
10
11
|
pub fn TableMutableType(comptime Table: type) type {
|
|
@@ -20,15 +21,51 @@ pub fn TableMutableType(comptime Table: type) type {
|
|
|
20
21
|
const load_factor = 50;
|
|
21
22
|
const Values = std.HashMapUnmanaged(Value, void, Table.HashMapContextValue, load_factor);
|
|
22
23
|
|
|
24
|
+
pub const ValuesCache = SetAssociativeCache(
|
|
25
|
+
Key,
|
|
26
|
+
Value,
|
|
27
|
+
Table.key_from_value,
|
|
28
|
+
struct {
|
|
29
|
+
inline fn hash(key: Key) u64 {
|
|
30
|
+
return std.hash.Wyhash.hash(0, mem.asBytes(&key));
|
|
31
|
+
}
|
|
32
|
+
}.hash,
|
|
33
|
+
struct {
|
|
34
|
+
inline fn equal(a: Key, b: Key) bool {
|
|
35
|
+
return compare_keys(a, b) == .eq;
|
|
36
|
+
}
|
|
37
|
+
}.equal,
|
|
38
|
+
.{},
|
|
39
|
+
);
|
|
40
|
+
|
|
23
41
|
value_count_max: u32,
|
|
24
42
|
values: Values = .{},
|
|
25
|
-
dirty: bool = false,
|
|
26
43
|
|
|
27
|
-
|
|
44
|
+
/// This is used to accelerate point lookups and is not used for range queries.
|
|
45
|
+
/// Secondary index trees used only for range queries can therefore set this to null.
|
|
46
|
+
///
|
|
47
|
+
/// The values cache is only used for the latest snapshot for simplicity.
|
|
48
|
+
/// Earlier snapshots will still be able to utilize the block cache.
|
|
49
|
+
///
|
|
50
|
+
/// The values cache is updated (in bulk) when the mutable table is sorted and frozen,
|
|
51
|
+
/// rather than updating on every `put()`/`remove()`.
|
|
52
|
+
/// This amortizes cache inserts for hot keys in the mutable table, and avoids redundantly
|
|
53
|
+
/// storing duplicate values in both the mutable table and values cache.
|
|
54
|
+
// TODO Share cache between trees of different grooves:
|
|
55
|
+
// "A set associative cache of values shared by trees with the same key/value sizes.
|
|
56
|
+
// The value type will be []u8 and this will be shared by trees with the same value size."
|
|
57
|
+
values_cache: ?*ValuesCache,
|
|
58
|
+
|
|
59
|
+
/// `commit_entries_max` is the maximum number of Values that can be inserted by a single commit.
|
|
60
|
+
pub fn init(
|
|
61
|
+
allocator: mem.Allocator,
|
|
62
|
+
values_cache: ?*ValuesCache,
|
|
63
|
+
commit_entries_max: u32,
|
|
64
|
+
) !TableMutable {
|
|
28
65
|
comptime assert(config.lsm_batch_multiple > 0);
|
|
29
|
-
assert(
|
|
66
|
+
assert(commit_entries_max > 0);
|
|
30
67
|
|
|
31
|
-
const value_count_max =
|
|
68
|
+
const value_count_max = commit_entries_max * config.lsm_batch_multiple;
|
|
32
69
|
const data_block_count = div_ceil(value_count_max, Table.data.value_count_max);
|
|
33
70
|
assert(data_block_count <= Table.data_block_count_max);
|
|
34
71
|
|
|
@@ -39,6 +76,7 @@ pub fn TableMutableType(comptime Table: type) type {
|
|
|
39
76
|
return TableMutable{
|
|
40
77
|
.value_count_max = value_count_max,
|
|
41
78
|
.values = values,
|
|
79
|
+
.values_cache = values_cache,
|
|
42
80
|
};
|
|
43
81
|
}
|
|
44
82
|
|
|
@@ -47,31 +85,43 @@ pub fn TableMutableType(comptime Table: type) type {
|
|
|
47
85
|
}
|
|
48
86
|
|
|
49
87
|
pub fn get(table: *const TableMutable, key: Key) ?*const Value {
|
|
50
|
-
|
|
88
|
+
if (table.values.getKeyPtr(tombstone_from_key(key))) |value| {
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
if (table.values_cache) |cache| {
|
|
92
|
+
// Check the cache after the mutable table (see `values_cache` for explanation).
|
|
93
|
+
if (cache.get(key)) |value| return value;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
51
96
|
}
|
|
52
97
|
|
|
53
98
|
pub fn put(table: *TableMutable, value: *const Value) void {
|
|
54
99
|
// If the key is already present in the hash map, the old key will not be overwritten
|
|
55
100
|
// by the new one if using e.g. putAssumeCapacity(). Instead we must use the lower
|
|
56
101
|
// level getOrPut() API and manually overwrite the old key.
|
|
57
|
-
const
|
|
58
|
-
|
|
102
|
+
const upsert = table.values.getOrPutAssumeCapacity(value.*);
|
|
103
|
+
upsert.key_ptr.* = value.*;
|
|
59
104
|
|
|
60
105
|
// The hash map's load factor may allow for more capacity because of rounding:
|
|
61
106
|
assert(table.values.count() <= table.value_count_max);
|
|
62
|
-
|
|
107
|
+
|
|
108
|
+
if (table.values_cache) |cache| {
|
|
109
|
+
cache.insert(key_from_value(value)).* = value.*;
|
|
110
|
+
}
|
|
63
111
|
}
|
|
64
112
|
|
|
65
113
|
pub fn remove(table: *TableMutable, value: *const Value) void {
|
|
66
114
|
// If the key is already present in the hash map, the old key will not be overwritten
|
|
67
115
|
// by the new one if using e.g. putAssumeCapacity(). Instead we must use the lower
|
|
68
116
|
// level getOrPut() API and manually overwrite the old key.
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
117
|
+
const upsert = table.values.getOrPutAssumeCapacity(value.*);
|
|
118
|
+
upsert.key_ptr.* = tombstone_from_key(key_from_value(value));
|
|
119
|
+
|
|
72
120
|
assert(table.values.count() <= table.value_count_max);
|
|
73
121
|
}
|
|
74
122
|
|
|
123
|
+
/// This may return `false` even when committing would succeed — it pessimistically
|
|
124
|
+
/// assumes that none of the batch's keys are already in `table.values`.
|
|
75
125
|
pub fn can_commit_batch(table: *TableMutable, batch_count: u32) bool {
|
|
76
126
|
assert(batch_count <= table.value_count_max);
|
|
77
127
|
return (table.count() + batch_count) <= table.value_count_max;
|
|
@@ -81,7 +131,6 @@ pub fn TableMutableType(comptime Table: type) type {
|
|
|
81
131
|
assert(table.values.count() > 0);
|
|
82
132
|
table.values.clearRetainingCapacity();
|
|
83
133
|
assert(table.values.count() == 0);
|
|
84
|
-
table.dirty = false;
|
|
85
134
|
}
|
|
86
135
|
|
|
87
136
|
pub fn count(table: *const TableMutable) u32 {
|
|
@@ -104,6 +153,8 @@ pub fn TableMutableType(comptime Table: type) type {
|
|
|
104
153
|
var it = table.values.keyIterator();
|
|
105
154
|
while (it.next()) |value| : (i += 1) {
|
|
106
155
|
values_max[i] = value.*;
|
|
156
|
+
|
|
157
|
+
if (table.values_cache) |cache| cache.insert(key_from_value(value)).* = value.*;
|
|
107
158
|
}
|
|
108
159
|
|
|
109
160
|
const values = values_max[0..i];
|
|
@@ -13,49 +13,31 @@ const Transfer = @import("../tigerbeetle.zig").Transfer;
|
|
|
13
13
|
const Account = @import("../tigerbeetle.zig").Account;
|
|
14
14
|
const Storage = @import("../storage.zig").Storage;
|
|
15
15
|
const IO = @import("../io.zig").IO;
|
|
16
|
+
const StateMachine = @import("../state_machine.zig").StateMachineType(Storage, .{
|
|
17
|
+
.message_body_size_max = config.message_body_size_max,
|
|
18
|
+
});
|
|
16
19
|
|
|
17
20
|
const GridType = @import("grid.zig").GridType;
|
|
18
21
|
const GrooveType = @import("groove.zig").GrooveType;
|
|
19
|
-
const
|
|
22
|
+
const Forest = StateMachine.Forest;
|
|
20
23
|
|
|
21
24
|
const Grid = GridType(Storage);
|
|
22
25
|
const SuperBlock = vsr.SuperBlockType(Storage);
|
|
23
|
-
const Forest = ForestType(Storage, .{
|
|
24
|
-
.accounts = GrooveType(
|
|
25
|
-
Storage,
|
|
26
|
-
Account,
|
|
27
|
-
.{
|
|
28
|
-
.ignored = &[_][]const u8{ "reserved", "flags" },
|
|
29
|
-
.derived = .{},
|
|
30
|
-
},
|
|
31
|
-
),
|
|
32
|
-
.transfers = GrooveType(
|
|
33
|
-
Storage,
|
|
34
|
-
Transfer,
|
|
35
|
-
.{
|
|
36
|
-
.ignored = &[_][]const u8{ "reserved", "flags" },
|
|
37
|
-
.derived = .{},
|
|
38
|
-
},
|
|
39
|
-
),
|
|
40
|
-
});
|
|
41
26
|
|
|
42
27
|
const Environment = struct {
|
|
43
28
|
const cluster = 32;
|
|
44
29
|
const replica = 4;
|
|
45
|
-
const size_max = (512 + 64) * 1024 * 1024;
|
|
30
|
+
const size_max = vsr.Zone.superblock.size().? + vsr.Zone.wal.size().? + (512 + 64) * 1024 * 1024;
|
|
46
31
|
|
|
47
32
|
const node_count = 1024;
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
.commit_count_max = 8191,
|
|
57
|
-
},
|
|
58
|
-
};
|
|
33
|
+
const cache_entries_max = 2 * 1024 * 1024;
|
|
34
|
+
const forest_options = StateMachine.forest_options(.{
|
|
35
|
+
// Ignored by StateMachine.forest_options().
|
|
36
|
+
.lsm_forest_node_count = undefined,
|
|
37
|
+
.cache_entries_accounts = cache_entries_max,
|
|
38
|
+
.cache_entries_transfers = cache_entries_max,
|
|
39
|
+
.cache_entries_posted = cache_entries_max,
|
|
40
|
+
});
|
|
59
41
|
|
|
60
42
|
const State = enum {
|
|
61
43
|
uninit,
|
|
@@ -78,6 +60,8 @@ const Environment = struct {
|
|
|
78
60
|
superblock_context: SuperBlock.Context,
|
|
79
61
|
grid: Grid,
|
|
80
62
|
forest: Forest,
|
|
63
|
+
// We need @fieldParentPtr() of forest, so we can't use an optional Forest.
|
|
64
|
+
forest_exists: bool = false,
|
|
81
65
|
|
|
82
66
|
fn init(env: *Environment, must_create: bool) !void {
|
|
83
67
|
env.state = .uninit;
|
|
@@ -105,8 +89,9 @@ const Environment = struct {
|
|
|
105
89
|
env.grid = try Grid.init(allocator, &env.superblock);
|
|
106
90
|
errdefer env.grid.deinit(allocator);
|
|
107
91
|
|
|
108
|
-
|
|
109
|
-
|
|
92
|
+
// Forest must be initialized with an open superblock.
|
|
93
|
+
env.forest = undefined;
|
|
94
|
+
env.forest_exists = false;
|
|
110
95
|
|
|
111
96
|
env.state = .init;
|
|
112
97
|
}
|
|
@@ -115,7 +100,10 @@ const Environment = struct {
|
|
|
115
100
|
assert(env.state != .uninit);
|
|
116
101
|
defer env.state = .uninit;
|
|
117
102
|
|
|
118
|
-
env.
|
|
103
|
+
if (env.forest_exists) {
|
|
104
|
+
env.forest.deinit(allocator);
|
|
105
|
+
env.forest_exists = false;
|
|
106
|
+
}
|
|
119
107
|
env.grid.deinit(allocator);
|
|
120
108
|
env.superblock.deinit(allocator);
|
|
121
109
|
env.message_pool.deinit(allocator);
|
|
@@ -176,6 +164,9 @@ const Environment = struct {
|
|
|
176
164
|
const env = @fieldParentPtr(@This(), "superblock_context", superblock_context);
|
|
177
165
|
assert(env.state == .init);
|
|
178
166
|
env.state = .superblock_open;
|
|
167
|
+
|
|
168
|
+
env.forest = Forest.init(allocator, &env.grid, node_count, forest_options) catch unreachable;
|
|
169
|
+
env.forest_exists = true;
|
|
179
170
|
env.forest.open(forest_open_callback);
|
|
180
171
|
}
|
|
181
172
|
|
|
@@ -247,7 +238,7 @@ const Environment = struct {
|
|
|
247
238
|
comptime visibility: Visibility,
|
|
248
239
|
groove: anytype,
|
|
249
240
|
objects: anytype,
|
|
250
|
-
comptime
|
|
241
|
+
comptime commit_entries_max: u32,
|
|
251
242
|
) !void {
|
|
252
243
|
const Groove = @TypeOf(groove.*);
|
|
253
244
|
const Object = @TypeOf(objects[0]);
|
|
@@ -260,9 +251,10 @@ const Environment = struct {
|
|
|
260
251
|
|
|
261
252
|
fn verify(assertion: *@This()) void {
|
|
262
253
|
assert(assertion.verify_count == 0);
|
|
263
|
-
assertion.verify_count = std.math.min(
|
|
254
|
+
assertion.verify_count = std.math.min(commit_entries_max, assertion.objects.len);
|
|
264
255
|
if (assertion.verify_count == 0) return;
|
|
265
256
|
|
|
257
|
+
assertion.groove.prefetch_setup(null);
|
|
266
258
|
for (assertion.objects[0..assertion.verify_count]) |*object| {
|
|
267
259
|
assertion.groove.prefetch_enqueue(object.id);
|
|
268
260
|
}
|
|
@@ -275,9 +267,8 @@ const Environment = struct {
|
|
|
275
267
|
assert(assertion.verify_count > 0);
|
|
276
268
|
|
|
277
269
|
{
|
|
278
|
-
defer assertion.groove.prefetch_clear();
|
|
279
270
|
for (assertion.objects[0..assertion.verify_count]) |*object| {
|
|
280
|
-
log.debug("verifying {} for id={}", .{visibility, object.id});
|
|
271
|
+
log.debug("verifying {} for id={}", .{ visibility, object.id });
|
|
281
272
|
const result = assertion.groove.get(object.id);
|
|
282
273
|
|
|
283
274
|
switch (visibility) {
|
|
@@ -285,7 +276,7 @@ const Environment = struct {
|
|
|
285
276
|
.visible => {
|
|
286
277
|
assert(result != null);
|
|
287
278
|
assert(std.mem.eql(u8, std.mem.asBytes(result.?), std.mem.asBytes(object)));
|
|
288
|
-
}
|
|
279
|
+
},
|
|
289
280
|
}
|
|
290
281
|
}
|
|
291
282
|
}
|
|
@@ -319,18 +310,18 @@ const Environment = struct {
|
|
|
319
310
|
var inserted = std.ArrayList(Account).init(allocator);
|
|
320
311
|
defer inserted.deinit();
|
|
321
312
|
|
|
322
|
-
const accounts_to_insert_per_op = 1; //
|
|
313
|
+
const accounts_to_insert_per_op = 1; // forest_options.accounts.commit_entries_max;
|
|
323
314
|
const iterations = 4;
|
|
324
315
|
|
|
325
|
-
var op: u64 =
|
|
326
|
-
var id: u128 =
|
|
316
|
+
var op: u64 = 1;
|
|
317
|
+
var id: u128 = 1;
|
|
327
318
|
var timestamp: u64 = 42;
|
|
328
319
|
var crash_probability = std.rand.DefaultPrng.init(1337);
|
|
329
320
|
|
|
330
321
|
var iter: usize = 0;
|
|
331
322
|
while (iter < (accounts_to_insert_per_op * config.lsm_batch_multiple * iterations)) : (iter += 1) {
|
|
332
323
|
// Insert a bunch of accounts
|
|
333
|
-
|
|
324
|
+
|
|
334
325
|
var i: u32 = 0;
|
|
335
326
|
while (i < accounts_to_insert_per_op) : (i += 1) {
|
|
336
327
|
defer id += 1;
|
|
@@ -349,7 +340,7 @@ const Environment = struct {
|
|
|
349
340
|
.credits_posted = 42,
|
|
350
341
|
};
|
|
351
342
|
|
|
352
|
-
|
|
343
|
+
// Insert an account ...
|
|
353
344
|
const groove = &env.forest.grooves.accounts;
|
|
354
345
|
groove.put(&account);
|
|
355
346
|
|
|
@@ -357,8 +348,8 @@ const Environment = struct {
|
|
|
357
348
|
try env.assert_visibility(
|
|
358
349
|
.visible,
|
|
359
350
|
&env.forest.grooves.accounts,
|
|
360
|
-
@as([]const Account, &.{
|
|
361
|
-
|
|
351
|
+
@as([]const Account, &.{account}),
|
|
352
|
+
forest_options.accounts.tree_options_object.commit_entries_max,
|
|
362
353
|
);
|
|
363
354
|
|
|
364
355
|
// Record the successfull insertion.
|
|
@@ -369,15 +360,20 @@ const Environment = struct {
|
|
|
369
360
|
try env.compact(op);
|
|
370
361
|
defer op += 1;
|
|
371
362
|
|
|
372
|
-
//
|
|
363
|
+
// Checkpoint when the forest finishes compaction.
|
|
364
|
+
// Don't repeat a checkpoint (commit_min must always advance).
|
|
373
365
|
const checkpoint_op = op -| config.lsm_batch_multiple;
|
|
374
|
-
if (checkpoint_op % config.lsm_batch_multiple == config.lsm_batch_multiple - 1
|
|
366
|
+
if (checkpoint_op % config.lsm_batch_multiple == config.lsm_batch_multiple - 1 and
|
|
367
|
+
checkpoint_op != env.superblock.staging.vsr_state.commit_min)
|
|
368
|
+
{
|
|
375
369
|
// Checkpoint the forest then superblock
|
|
370
|
+
env.superblock.staging.vsr_state.commit_min = checkpoint_op;
|
|
371
|
+
env.superblock.staging.vsr_state.commit_max = checkpoint_op;
|
|
376
372
|
try env.checkpoint();
|
|
377
373
|
|
|
378
|
-
const checkpointed = inserted.items[0..
|
|
374
|
+
const checkpointed = inserted.items[0 .. checkpoint_op * accounts_to_insert_per_op];
|
|
379
375
|
const uncommitted = inserted.items[checkpointed.len..];
|
|
380
|
-
log.debug("checkpointed={d} uncommitted={d}", .{checkpointed.len, uncommitted.len});
|
|
376
|
+
log.debug("checkpointed={d} uncommitted={d}", .{ checkpointed.len, uncommitted.len });
|
|
381
377
|
assert(uncommitted.len == config.lsm_batch_multiple * accounts_to_insert_per_op);
|
|
382
378
|
|
|
383
379
|
// Randomly initiate a crash
|
|
@@ -397,7 +393,7 @@ const Environment = struct {
|
|
|
397
393
|
.invisible,
|
|
398
394
|
&env.forest.grooves.accounts,
|
|
399
395
|
uncommitted,
|
|
400
|
-
|
|
396
|
+
forest_options.accounts.tree_options_object.commit_entries_max,
|
|
401
397
|
);
|
|
402
398
|
|
|
403
399
|
// Reset everything to after checkpoint
|
|
@@ -407,14 +403,14 @@ const Environment = struct {
|
|
|
407
403
|
timestamp = checkpointed[checkpointed.len - 1].timestamp + 1;
|
|
408
404
|
}
|
|
409
405
|
crashing = false;
|
|
410
|
-
}
|
|
406
|
+
}
|
|
411
407
|
|
|
412
408
|
// Double check the forest contains the checkpointed values (positive space)
|
|
413
409
|
try env.assert_visibility(
|
|
414
410
|
.visible,
|
|
415
411
|
&env.forest.grooves.accounts,
|
|
416
412
|
checkpointed,
|
|
417
|
-
|
|
413
|
+
forest_options.accounts.tree_options_object.commit_entries_max,
|
|
418
414
|
);
|
|
419
415
|
}
|
|
420
416
|
}
|
|
@@ -425,5 +421,3 @@ pub fn main() !void {
|
|
|
425
421
|
try Environment.format(); // NOTE: this can be commented out after first run to speed up testing.
|
|
426
422
|
try Environment.run(); //try do_simple();
|
|
427
423
|
}
|
|
428
|
-
|
|
429
|
-
|