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.
Files changed (92) hide show
  1. package/README.md +302 -101
  2. package/dist/index.d.ts +70 -72
  3. package/dist/index.js +70 -72
  4. package/dist/index.js.map +1 -1
  5. package/package.json +6 -6
  6. package/scripts/download_node_headers.sh +14 -7
  7. package/src/index.ts +6 -10
  8. package/src/node.zig +6 -3
  9. package/src/tigerbeetle/scripts/benchmark.sh +4 -4
  10. package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
  11. package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
  12. package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
  13. package/src/tigerbeetle/scripts/install.sh +19 -4
  14. package/src/tigerbeetle/scripts/install_zig.bat +5 -1
  15. package/src/tigerbeetle/scripts/install_zig.sh +24 -14
  16. package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
  17. package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
  18. package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
  19. package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
  20. package/src/tigerbeetle/src/benchmark.zig +4 -2
  21. package/src/tigerbeetle/src/benchmark_array_search.zig +3 -3
  22. package/src/tigerbeetle/src/c/tb_client/thread.zig +8 -9
  23. package/src/tigerbeetle/src/c/tb_client.h +100 -80
  24. package/src/tigerbeetle/src/c/tb_client.zig +4 -1
  25. package/src/tigerbeetle/src/cli.zig +1 -1
  26. package/src/tigerbeetle/src/config.zig +48 -16
  27. package/src/tigerbeetle/src/demo.zig +3 -1
  28. package/src/tigerbeetle/src/eytzinger_benchmark.zig +3 -3
  29. package/src/tigerbeetle/src/io/linux.zig +1 -1
  30. package/src/tigerbeetle/src/lsm/README.md +214 -0
  31. package/src/tigerbeetle/src/lsm/binary_search.zig +137 -10
  32. package/src/tigerbeetle/src/lsm/bloom_filter.zig +43 -0
  33. package/src/tigerbeetle/src/lsm/compaction.zig +352 -398
  34. package/src/tigerbeetle/src/lsm/composite_key.zig +2 -0
  35. package/src/tigerbeetle/src/lsm/eytzinger.zig +1 -1
  36. package/src/tigerbeetle/src/lsm/forest.zig +21 -447
  37. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +412 -0
  38. package/src/tigerbeetle/src/lsm/grid.zig +145 -69
  39. package/src/tigerbeetle/src/lsm/groove.zig +196 -133
  40. package/src/tigerbeetle/src/lsm/k_way_merge.zig +40 -18
  41. package/src/tigerbeetle/src/lsm/level_iterator.zig +28 -9
  42. package/src/tigerbeetle/src/lsm/manifest.zig +81 -181
  43. package/src/tigerbeetle/src/lsm/manifest_level.zig +210 -454
  44. package/src/tigerbeetle/src/lsm/manifest_log.zig +77 -28
  45. package/src/tigerbeetle/src/lsm/posted_groove.zig +64 -76
  46. package/src/tigerbeetle/src/lsm/segmented_array.zig +561 -241
  47. package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
  48. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
  49. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +62 -12
  50. package/src/tigerbeetle/src/lsm/table.zig +83 -48
  51. package/src/tigerbeetle/src/lsm/table_immutable.zig +30 -23
  52. package/src/tigerbeetle/src/lsm/table_iterator.zig +25 -14
  53. package/src/tigerbeetle/src/lsm/table_mutable.zig +63 -12
  54. package/src/tigerbeetle/src/lsm/test.zig +49 -55
  55. package/src/tigerbeetle/src/lsm/tree.zig +407 -402
  56. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +457 -0
  57. package/src/tigerbeetle/src/main.zig +28 -6
  58. package/src/tigerbeetle/src/message_bus.zig +2 -2
  59. package/src/tigerbeetle/src/message_pool.zig +14 -17
  60. package/src/tigerbeetle/src/simulator.zig +145 -112
  61. package/src/tigerbeetle/src/state_machine.zig +338 -228
  62. package/src/tigerbeetle/src/static_allocator.zig +65 -0
  63. package/src/tigerbeetle/src/storage.zig +3 -7
  64. package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
  65. package/src/tigerbeetle/src/test/accounting/workload.zig +819 -0
  66. package/src/tigerbeetle/src/test/cluster.zig +18 -48
  67. package/src/tigerbeetle/src/test/conductor.zig +365 -0
  68. package/src/tigerbeetle/src/test/fuzz.zig +121 -0
  69. package/src/tigerbeetle/src/test/id.zig +89 -0
  70. package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
  71. package/src/tigerbeetle/src/test/state_checker.zig +93 -69
  72. package/src/tigerbeetle/src/test/state_machine.zig +11 -35
  73. package/src/tigerbeetle/src/test/storage.zig +29 -8
  74. package/src/tigerbeetle/src/tigerbeetle.zig +14 -16
  75. package/src/tigerbeetle/src/unit_tests.zig +7 -0
  76. package/src/tigerbeetle/src/vopr.zig +494 -0
  77. package/src/tigerbeetle/src/vopr_hub/README.md +58 -0
  78. package/src/tigerbeetle/src/vopr_hub/SETUP.md +199 -0
  79. package/src/tigerbeetle/src/vopr_hub/go.mod +3 -0
  80. package/src/tigerbeetle/src/vopr_hub/main.go +1022 -0
  81. package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +3 -0
  82. package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +403 -0
  83. package/src/tigerbeetle/src/vsr/client.zig +13 -0
  84. package/src/tigerbeetle/src/vsr/journal.zig +16 -13
  85. package/src/tigerbeetle/src/vsr/replica.zig +924 -491
  86. package/src/tigerbeetle/src/vsr/superblock.zig +55 -37
  87. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -10
  88. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +2 -2
  89. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +18 -3
  90. package/src/tigerbeetle/src/vsr.zig +75 -55
  91. package/src/tigerbeetle/scripts/vopr.bat +0 -48
  92. 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
- pub fn init(allocator: mem.Allocator, commit_count_max: u32) !TableImmutable {
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 = commit_count_max * config.lsm_batch_multiple;
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) != .gt);
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
- if (table.values.len > 0) {
116
- const result = binary_search.binary_search_values(
117
- Key,
118
- Value,
119
- key_from_value,
120
- compare_keys,
121
- table.values,
122
- key,
123
- );
124
- if (result.exact) {
125
- const value = &table.values[result.index];
126
- if (config.verify) assert(compare_keys(key, key_from_value(value)) == .eq);
127
- return value;
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
- _ = it;
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
- _ = it;
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) ?Table.Key {
187
- if (it.values_index == it.table.values.len) return null;
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 of
42
- /// values buffered. This simplifies the peek() interface as null always means that
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
- pub fn peek(it: TableIterator) ?Table.Key {
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
- assert(it.data_block_index == Table.index_data_blocks_used(it.index_block));
267
- return null;
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 is only safe to call after peek() has returned non-null.
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
- pub fn init(allocator: mem.Allocator, commit_count_max: u32) !TableMutable {
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(commit_count_max > 0);
66
+ assert(commit_entries_max > 0);
30
67
 
31
- const value_count_max = commit_count_max * config.lsm_batch_multiple;
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
- return table.values.getKeyPtr(tombstone_from_key(key));
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 gop = table.values.getOrPutAssumeCapacity(value.*);
58
- gop.key_ptr.* = value.*;
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
- table.dirty = true;
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 tombstone = tombstone_from_key(key_from_value(value));
70
- const gop = table.values.getOrPutAssumeCapacity(tombstone);
71
- gop.key_ptr.* = tombstone;
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 ForestType = @import("forest.zig").ForestType;
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 cache_size = 2 * 1024 * 1024;
49
- const forest_config = .{
50
- .transfers = .{
51
- .cache_size = cache_size,
52
- .commit_count_max = 8191 * 2,
53
- },
54
- .accounts = .{
55
- .cache_size = cache_size,
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
- env.forest = try Forest.init(allocator, &env.grid, node_count, forest_config);
109
- errdefer env.forest.deinit(allocator);
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.forest.deinit(allocator);
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 commit_count_max: u32,
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(commit_count_max, assertion.objects.len);
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; // forest_config.accounts.commit_count_max;
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 = 0;
326
- var id: u128 = 0;
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
- // Insert an account ...
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, &.{ account }),
361
- forest_config.accounts.commit_count_max,
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
- // checkpoint the records when the forest is likely finished compaction.
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..((checkpoint_op + 1) * accounts_to_insert_per_op)];
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
- forest_config.accounts.commit_count_max,
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
- forest_config.accounts.commit_count_max,
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
-