tigerbeetle-node 0.10.0 → 0.11.1

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 (109) 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 +9 -8
  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/scripts/validate_docs.sh +17 -0
  21. package/src/tigerbeetle/src/benchmark.zig +29 -13
  22. package/src/tigerbeetle/src/c/tb_client/context.zig +248 -47
  23. package/src/tigerbeetle/src/c/tb_client/echo_client.zig +108 -0
  24. package/src/tigerbeetle/src/c/tb_client/packet.zig +2 -2
  25. package/src/tigerbeetle/src/c/tb_client/signal.zig +2 -4
  26. package/src/tigerbeetle/src/c/tb_client/thread.zig +17 -257
  27. package/src/tigerbeetle/src/c/tb_client.h +118 -84
  28. package/src/tigerbeetle/src/c/tb_client.zig +88 -23
  29. package/src/tigerbeetle/src/c/tb_client_header_test.zig +135 -0
  30. package/src/tigerbeetle/src/c/test.zig +371 -1
  31. package/src/tigerbeetle/src/cli.zig +37 -7
  32. package/src/tigerbeetle/src/config.zig +58 -17
  33. package/src/tigerbeetle/src/demo.zig +5 -2
  34. package/src/tigerbeetle/src/demo_01_create_accounts.zig +1 -1
  35. package/src/tigerbeetle/src/demo_03_create_transfers.zig +13 -0
  36. package/src/tigerbeetle/src/ewah.zig +11 -33
  37. package/src/tigerbeetle/src/ewah_benchmark.zig +8 -9
  38. package/src/tigerbeetle/src/io/linux.zig +1 -1
  39. package/src/tigerbeetle/src/lsm/README.md +308 -0
  40. package/src/tigerbeetle/src/lsm/binary_search.zig +137 -10
  41. package/src/tigerbeetle/src/lsm/bloom_filter.zig +43 -0
  42. package/src/tigerbeetle/src/lsm/compaction.zig +376 -397
  43. package/src/tigerbeetle/src/lsm/composite_key.zig +2 -0
  44. package/src/tigerbeetle/src/lsm/eytzinger.zig +1 -1
  45. package/src/tigerbeetle/src/{eytzinger_benchmark.zig → lsm/eytzinger_benchmark.zig} +34 -21
  46. package/src/tigerbeetle/src/lsm/forest.zig +21 -447
  47. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +414 -0
  48. package/src/tigerbeetle/src/lsm/grid.zig +170 -76
  49. package/src/tigerbeetle/src/lsm/groove.zig +197 -133
  50. package/src/tigerbeetle/src/lsm/k_way_merge.zig +40 -18
  51. package/src/tigerbeetle/src/lsm/level_iterator.zig +28 -9
  52. package/src/tigerbeetle/src/lsm/manifest.zig +93 -180
  53. package/src/tigerbeetle/src/lsm/manifest_level.zig +161 -454
  54. package/src/tigerbeetle/src/lsm/manifest_log.zig +243 -356
  55. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +665 -0
  56. package/src/tigerbeetle/src/lsm/node_pool.zig +4 -0
  57. package/src/tigerbeetle/src/lsm/posted_groove.zig +65 -76
  58. package/src/tigerbeetle/src/lsm/segmented_array.zig +580 -251
  59. package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
  60. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
  61. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +62 -12
  62. package/src/tigerbeetle/src/lsm/table.zig +115 -68
  63. package/src/tigerbeetle/src/lsm/table_immutable.zig +30 -23
  64. package/src/tigerbeetle/src/lsm/table_iterator.zig +27 -17
  65. package/src/tigerbeetle/src/lsm/table_mutable.zig +63 -12
  66. package/src/tigerbeetle/src/lsm/test.zig +61 -56
  67. package/src/tigerbeetle/src/lsm/tree.zig +450 -407
  68. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +461 -0
  69. package/src/tigerbeetle/src/main.zig +83 -8
  70. package/src/tigerbeetle/src/message_bus.zig +20 -9
  71. package/src/tigerbeetle/src/message_pool.zig +22 -19
  72. package/src/tigerbeetle/src/ring_buffer.zig +7 -3
  73. package/src/tigerbeetle/src/simulator.zig +179 -119
  74. package/src/tigerbeetle/src/state_machine.zig +381 -246
  75. package/src/tigerbeetle/src/static_allocator.zig +65 -0
  76. package/src/tigerbeetle/src/storage.zig +3 -7
  77. package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
  78. package/src/tigerbeetle/src/test/accounting/workload.zig +823 -0
  79. package/src/tigerbeetle/src/test/cluster.zig +33 -81
  80. package/src/tigerbeetle/src/test/conductor.zig +366 -0
  81. package/src/tigerbeetle/src/test/fuzz.zig +121 -0
  82. package/src/tigerbeetle/src/test/id.zig +89 -0
  83. package/src/tigerbeetle/src/test/network.zig +45 -19
  84. package/src/tigerbeetle/src/test/packet_simulator.zig +40 -29
  85. package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
  86. package/src/tigerbeetle/src/test/state_checker.zig +91 -69
  87. package/src/tigerbeetle/src/test/state_machine.zig +11 -35
  88. package/src/tigerbeetle/src/test/storage.zig +470 -106
  89. package/src/tigerbeetle/src/test/storage_checker.zig +204 -0
  90. package/src/tigerbeetle/src/tigerbeetle.zig +15 -16
  91. package/src/tigerbeetle/src/unit_tests.zig +13 -1
  92. package/src/tigerbeetle/src/util.zig +97 -11
  93. package/src/tigerbeetle/src/vopr.zig +495 -0
  94. package/src/tigerbeetle/src/vsr/client.zig +21 -3
  95. package/src/tigerbeetle/src/vsr/journal.zig +293 -212
  96. package/src/tigerbeetle/src/vsr/replica.zig +1086 -515
  97. package/src/tigerbeetle/src/vsr/superblock.zig +382 -637
  98. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +14 -16
  99. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +416 -153
  100. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +332 -0
  101. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +349 -0
  102. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +62 -12
  103. package/src/tigerbeetle/src/vsr/superblock_quorums.zig +394 -0
  104. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +312 -0
  105. package/src/tigerbeetle/src/vsr.zig +94 -60
  106. package/src/tigerbeetle/scripts/vopr.bat +0 -48
  107. package/src/tigerbeetle/scripts/vopr.sh +0 -33
  108. package/src/tigerbeetle/src/benchmark_array_search.zig +0 -317
  109. package/src/tigerbeetle/src/benchmarks/perf.zig +0 -299
@@ -28,11 +28,20 @@ const log = std.log.scoped(.manifest_log);
28
28
 
29
29
  const config = @import("../config.zig");
30
30
  const vsr = @import("../vsr.zig");
31
+ const util = @import("../util.zig");
31
32
 
32
33
  const SuperBlockType = vsr.SuperBlockType;
33
34
  const GridType = @import("grid.zig").GridType;
35
+ const BlockType = @import("grid.zig").BlockType;
36
+ const tree = @import("tree.zig");
34
37
  const RingBuffer = @import("../ring_buffer.zig").RingBuffer;
35
38
 
39
+ /// ManifestLog block schema:
40
+ /// │ vsr.Header │ operation=BlockType.manifest
41
+ /// │ [entry_count_max]Label │ level index, insert|remove
42
+ /// │ [≤entry_count_max]TableInfo │
43
+ /// │ […]u8{0} │ padding (to end of block)
44
+ /// Label and TableInfo entries correspond.
36
45
  pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
37
46
  return struct {
38
47
  const ManifestLog = @This();
@@ -40,8 +49,10 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
40
49
  const SuperBlock = SuperBlockType(Storage);
41
50
  const Grid = GridType(Storage);
42
51
 
43
- const BlockPtr = *align(config.sector_size) [config.block_size]u8;
44
- const BlockPtrConst = *align(config.sector_size) const [config.block_size]u8;
52
+ pub const Block = ManifestLogBlockType(Storage, TableInfo);
53
+ const BlockPtr = Grid.BlockPtr;
54
+ const BlockPtrConst = Grid.BlockPtrConst;
55
+ const Label = Block.Label;
45
56
 
46
57
  pub const Callback = fn (manifest_log: *ManifestLog) void;
47
58
 
@@ -51,11 +62,6 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
51
62
  table: *const TableInfo,
52
63
  ) void;
53
64
 
54
- pub const Label = packed struct {
55
- level: u7,
56
- event: enum(u1) { insert, remove },
57
- };
58
-
59
65
  const alignment = 16;
60
66
 
61
67
  comptime {
@@ -71,72 +77,92 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
71
77
  // However, we still store Label ahead of TableInfo to save space on the network.
72
78
  // This means we store fewer entries per manifest block, to gain less padding,
73
79
  // since we must store entry_count_max of whichever array is first in the layout.
74
- // For a better understanding of this decision, see block_size() below.
80
+ // For a better understanding of this decision, see Block.size() below.
75
81
  assert(@sizeOf(TableInfo) % alignment == 0);
76
82
  }
77
83
 
78
- const block_body_size = config.block_size - @sizeOf(vsr.Header);
79
- const entry_size = @sizeOf(Label) + @sizeOf(TableInfo);
80
- const entry_count_max_unaligned = @divFloor(block_body_size, entry_size);
81
- const entry_count_max = @divFloor(entry_count_max_unaligned, alignment) * alignment;
84
+ /// The maximum number of table updates to the manifest by a half-measure of table
85
+ /// compaction (not including manifest log compaction).
86
+ ///
87
+ /// Input tables are updated in the manifest (snapshot_max is reduced).
88
+ /// Input tables are removed from the manifest (if not held by a persistent snapshot).
89
+ /// Output tables are inserted into the manifest.
90
+ // TODO If insert-then-remove can update in-memory, then we can only count input tables once.
91
+ pub const compaction_appends_max = tree.compactions_max *
92
+ (tree.compaction_tables_input_max + // Update snapshot_max.
93
+ tree.compaction_tables_input_max + // Remove.
94
+ tree.compaction_tables_output_max);
95
+
96
+ const blocks_count_appends = util.div_ceil(compaction_appends_max, Block.entry_count_max);
97
+
98
+ /// The upper-bound of manifest log blocks we must buffer.
99
+ ///
100
+ /// `blocks` must have sufficient capacity for:
101
+ /// - a manifest log compaction (+1 block in the worst case)
102
+ /// - a leftover open block from the previous ops (+1 block)
103
+ /// - table updates from a half bar of compactions
104
+ /// (This is typically +1 block, but may be more when the block size is small).
105
+ /// TODO(Beat compaction): blocks_count_appends only needs enough for 1 beat.
106
+ const blocks_count_max = 1 + 1 + blocks_count_appends;
82
107
 
83
108
  comptime {
84
- assert(entry_count_max > 0);
85
- assert((entry_count_max * @sizeOf(Label)) % alignment == 0);
86
- assert((entry_count_max * @sizeOf(TableInfo)) % alignment == 0);
109
+ assert(blocks_count_max >= 3);
110
+ assert(blocks_count_max == 3 or config.block_size < 64 * 1024);
87
111
  }
88
112
 
89
113
  superblock: *SuperBlock,
90
114
  grid: *Grid,
115
+ grid_reservation: ?Grid.Reservation = null,
91
116
  tree_hash: u128,
92
117
 
93
118
  /// The head block is used to accumulate a full block, to be written at the next flush.
94
119
  /// The remaining blocks must accommodate all further appends.
95
- // TODO Assert the relation between the number of blocks, and flush/compact/append.
96
- blocks: RingBuffer(BlockPtr, 3, .array),
120
+ blocks: RingBuffer(BlockPtr, blocks_count_max, .array),
97
121
 
98
122
  /// The number of blocks that have been appended to, filled up, and then closed.
99
123
  blocks_closed: u8 = 0,
100
124
 
101
125
  /// The number of entries in the open block.
126
+ ///
127
+ /// Invariants:
128
+ /// - When `entry_count = 0`, there is no open block.
129
+ /// - `entry_count < entry_count_max`. When `entry_count` reaches the maximum, the open
130
+ /// block is closed, and `entry_count` resets to 0.
102
131
  entry_count: u32 = 0,
103
132
 
104
133
  opened: bool = false,
105
134
  open_event: OpenEvent = undefined,
106
135
  open_iterator: SuperBlock.Manifest.IteratorReverse = undefined,
107
136
 
137
+ /// Set for the duration of `compact`.
108
138
  reading: bool = false,
109
139
  read: Grid.Read = undefined,
110
- read_callback: Callback = undefined,
140
+ read_callback: ?Callback = null,
111
141
  read_block_reference: ?SuperBlock.Manifest.BlockReference = null,
112
142
 
143
+ /// Set for the duration of `flush` and `checkpoint`.
113
144
  writing: bool = false,
114
145
  write: Grid.Write = undefined,
115
- write_callback: Callback = undefined,
146
+ write_callback: ?Callback = null,
116
147
 
117
148
  pub fn init(allocator: mem.Allocator, grid: *Grid, tree_hash: u128) !ManifestLog {
118
149
  // TODO RingBuffer for .pointer should be extended to take care of alignment:
119
150
 
120
- const a = try allocator.alignedAlloc(u8, config.sector_size, config.block_size);
121
- errdefer allocator.free(a);
151
+ var blocks: [blocks_count_max]BlockPtr = undefined;
152
+ for (blocks) |*block, i| {
153
+ errdefer for (blocks[0..i]) |b| allocator.free(b);
122
154
 
123
- const b = try allocator.alignedAlloc(u8, config.sector_size, config.block_size);
124
- errdefer allocator.free(b);
125
-
126
- const c = try allocator.alignedAlloc(u8, config.sector_size, config.block_size);
127
- errdefer allocator.free(b);
155
+ const block_slice =
156
+ try allocator.alignedAlloc(u8, config.sector_size, config.block_size);
157
+ block.* = block_slice[0..config.block_size];
158
+ }
159
+ errdefer for (blocks) |b| allocator.free(b);
128
160
 
129
161
  return ManifestLog{
130
162
  .superblock = grid.superblock,
131
163
  .grid = grid,
132
164
  .tree_hash = tree_hash,
133
- .blocks = .{
134
- .buffer = .{
135
- a[0..config.block_size],
136
- b[0..config.block_size],
137
- c[0..config.block_size],
138
- },
139
- },
165
+ .blocks = .{ .buffer = blocks },
140
166
  };
141
167
  }
142
168
 
@@ -154,6 +180,11 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
154
180
  assert(!manifest_log.opened);
155
181
  assert(!manifest_log.reading);
156
182
  assert(!manifest_log.writing);
183
+ assert(manifest_log.read_callback == null);
184
+
185
+ assert(manifest_log.blocks.count == 0);
186
+ assert(manifest_log.blocks_closed == 0);
187
+ assert(manifest_log.entry_count == 0);
157
188
 
158
189
  manifest_log.open_event = event;
159
190
  manifest_log.open_iterator = manifest_log.superblock.manifest.iterator_reverse(
@@ -171,6 +202,10 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
171
202
  assert(manifest_log.reading);
172
203
  assert(!manifest_log.writing);
173
204
 
205
+ assert(manifest_log.blocks.count == 0);
206
+ assert(manifest_log.blocks_closed == 0);
207
+ assert(manifest_log.entry_count == 0);
208
+
174
209
  manifest_log.read_block_reference = manifest_log.open_iterator.next();
175
210
 
176
211
  if (manifest_log.read_block_reference) |block| {
@@ -182,15 +217,16 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
182
217
  &manifest_log.read,
183
218
  block.address,
184
219
  block.checksum,
220
+ .manifest,
185
221
  );
186
222
  } else {
187
223
  manifest_log.opened = true;
188
224
  manifest_log.open_event = undefined;
189
225
  manifest_log.open_iterator = undefined;
190
226
 
191
- const callback = manifest_log.read_callback;
227
+ const callback = manifest_log.read_callback.?;
192
228
  manifest_log.reading = false;
193
- manifest_log.read_callback = undefined;
229
+ manifest_log.read_callback = null;
194
230
  assert(manifest_log.read_block_reference == null);
195
231
 
196
232
  callback(manifest_log);
@@ -206,9 +242,9 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
206
242
  const block_reference = manifest_log.read_block_reference.?;
207
243
  verify_block(block, block_reference.checksum, block_reference.address);
208
244
 
209
- const entry_count = block_entry_count(block);
210
- const labels_used = labels_const(block)[0..entry_count];
211
- const tables_used = tables_const(block)[0..entry_count];
245
+ const entry_count = Block.entry_count(block);
246
+ const labels_used = Block.labels_const(block)[0..entry_count];
247
+ const tables_used = Block.tables_const(block)[0..entry_count];
212
248
 
213
249
  const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
214
250
 
@@ -229,6 +265,10 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
229
265
  }
230
266
  }
231
267
 
268
+ if (Block.entry_count(block) < Block.entry_count_max) {
269
+ manifest.queue_for_compaction(block_reference.address);
270
+ }
271
+
232
272
  log.debug("{}: opened: checksum={} address={} entries={}", .{
233
273
  manifest_log.tree_hash,
234
274
  block_reference.checksum,
@@ -243,12 +283,14 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
243
283
  /// A move is only recorded as an insert, there is no remove from the previous level, since
244
284
  /// this is safer (no potential to get the event order wrong) and reduces fragmentation.
245
285
  pub fn insert(manifest_log: *ManifestLog, level: u7, table: *const TableInfo) void {
286
+ assert(!manifest_log.writing);
246
287
  manifest_log.append(.{ .level = level, .event = .insert }, table);
247
288
  }
248
289
 
249
290
  /// Appends the removal of a table from a level.
250
291
  /// The table must have previously been inserted to the manifest log.
251
292
  pub fn remove(manifest_log: *ManifestLog, level: u7, table: *const TableInfo) void {
293
+ assert(!manifest_log.writing);
252
294
  manifest_log.append(.{ .level = level, .event = .remove }, table);
253
295
  }
254
296
 
@@ -259,17 +301,14 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
259
301
  assert(table.snapshot_min > 0);
260
302
  assert(table.snapshot_max > table.snapshot_min);
261
303
 
262
- if (manifest_log.blocks.empty()) {
263
- manifest_log.acquire_block();
264
- } else if (manifest_log.entry_count == entry_count_max) {
265
- assert(manifest_log.blocks.count > 0);
266
- manifest_log.close_block();
304
+ if (manifest_log.entry_count == 0) {
305
+ assert(manifest_log.blocks.count == manifest_log.blocks_closed);
267
306
  manifest_log.acquire_block();
268
307
  } else if (manifest_log.entry_count > 0) {
269
308
  assert(manifest_log.blocks.count > 0);
270
309
  }
271
310
 
272
- assert(manifest_log.entry_count < entry_count_max);
311
+ assert(manifest_log.entry_count < Block.entry_count_max);
273
312
  assert(manifest_log.blocks.count - manifest_log.blocks_closed == 1);
274
313
 
275
314
  log.debug(
@@ -288,11 +327,11 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
288
327
 
289
328
  const block: BlockPtr = manifest_log.blocks.tail().?;
290
329
  const entry = manifest_log.entry_count;
291
- labels(block)[entry] = label;
292
- tables(block)[entry] = table.*;
330
+ Block.labels(block)[entry] = label;
331
+ Block.tables(block)[entry] = table.*;
293
332
 
294
333
  const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
295
- const address = block_address(block);
334
+ const address = Block.address(block);
296
335
  if (manifest.update_table_extent(table.address, address, entry)) |previous_block| {
297
336
  manifest.queue_for_compaction(previous_block);
298
337
  if (label.event == .remove) manifest.queue_for_compaction(address);
@@ -302,12 +341,17 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
302
341
  }
303
342
 
304
343
  manifest_log.entry_count += 1;
344
+ if (manifest_log.entry_count == Block.entry_count_max) {
345
+ manifest_log.close_block();
346
+ assert(manifest_log.entry_count == 0);
347
+ }
305
348
  }
306
349
 
307
- pub fn flush(manifest_log: *ManifestLog, callback: Callback) void {
350
+ fn flush(manifest_log: *ManifestLog, callback: Callback) void {
308
351
  assert(manifest_log.opened);
309
352
  assert(!manifest_log.reading);
310
353
  assert(!manifest_log.writing);
354
+ assert(manifest_log.write_callback == null);
311
355
 
312
356
  manifest_log.writing = true;
313
357
  manifest_log.write_callback = callback;
@@ -316,6 +360,26 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
316
360
  manifest_log.tree_hash,
317
361
  manifest_log.blocks_closed,
318
362
  });
363
+
364
+ // The manifest is updated synchronously relative to the beginning of compact() and
365
+ // checkpoint() so that the SuperBlock.Manifest.append()s are deterministic relative
366
+ // to other trees' manifest logs.
367
+ const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
368
+ var i: usize = 0;
369
+ while (i < manifest_log.blocks_closed) : (i += 1) {
370
+ const block = manifest_log.blocks.get_ptr(i).?.*;
371
+ verify_block(block, null, null);
372
+
373
+ const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
374
+ const address = Block.address(block);
375
+ assert(address > 0);
376
+
377
+ manifest.append(manifest_log.tree_hash, header.checksum, address);
378
+ if (Block.entry_count(block) < Block.entry_count_max) {
379
+ manifest.queue_for_compaction(address);
380
+ }
381
+ }
382
+
319
383
  manifest_log.write_block();
320
384
  }
321
385
 
@@ -329,11 +393,11 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
329
393
  assert(manifest_log.entry_count == 0);
330
394
  } else {
331
395
  assert(manifest_log.blocks.count == 1);
332
- assert(manifest_log.entry_count < entry_count_max);
396
+ assert(manifest_log.entry_count < Block.entry_count_max);
333
397
  }
334
398
 
335
- const callback = manifest_log.write_callback;
336
- manifest_log.write_callback = undefined;
399
+ const callback = manifest_log.write_callback.?;
400
+ manifest_log.write_callback = null;
337
401
  manifest_log.writing = false;
338
402
 
339
403
  callback(manifest_log);
@@ -344,15 +408,16 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
344
408
  verify_block(block, null, null);
345
409
 
346
410
  const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
347
- const address = block_address(block);
411
+ const address = Block.address(block);
348
412
  assert(address > 0);
349
413
 
350
- const entry_count = block_entry_count(block);
414
+ const entry_count = Block.entry_count(block);
351
415
 
352
416
  if (manifest_log.blocks_closed == 1 and manifest_log.blocks.count == 1) {
417
+ // This might be the last block of a checkpoint, which can be a partial block.
353
418
  assert(entry_count > 0);
354
419
  } else {
355
- assert(entry_count == entry_count_max);
420
+ assert(entry_count == Block.entry_count_max);
356
421
  }
357
422
 
358
423
  log.debug("{}: write_block: checksum={} address={} entries={}", .{
@@ -375,18 +440,6 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
375
440
  assert(manifest_log.opened);
376
441
  assert(manifest_log.writing);
377
442
 
378
- const block = manifest_log.blocks.head().?;
379
- verify_block(block, null, null);
380
-
381
- const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
382
- const address = block_address(block);
383
- assert(address > 0);
384
-
385
- const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
386
-
387
- manifest.append(manifest_log.tree_hash, header.checksum, address);
388
- if (block_entry_count(block) < entry_count_max) manifest.queue_for_compaction(address);
389
-
390
443
  manifest_log.blocks_closed -= 1;
391
444
  manifest_log.blocks.advance_head();
392
445
  assert(manifest_log.blocks_closed <= manifest_log.blocks.count);
@@ -394,42 +447,82 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
394
447
  manifest_log.write_block();
395
448
  }
396
449
 
450
+ pub fn reserve(manifest_log: *ManifestLog) void {
451
+ assert(manifest_log.opened);
452
+ assert(!manifest_log.reading);
453
+ assert(!manifest_log.writing);
454
+ assert(manifest_log.read_callback == null);
455
+ assert(manifest_log.write_callback == null);
456
+ assert(manifest_log.grid_reservation == null);
457
+ // reserve() is called at the start of compaction, so we have:
458
+ // - at most 1 closed block, and
459
+ // - at most 1 open block
460
+ // due to the last log compaction plus a leftover partial block.
461
+ assert(manifest_log.blocks_closed <= 1);
462
+ assert(manifest_log.blocks.count <= manifest_log.blocks_closed + 1);
463
+
464
+ // TODO Make sure this cannot fail — before compaction begins verify that enough free
465
+ // blocks are available for all reservations.
466
+ // +1 for the manifest log block compaction, which acquires at most one block.
467
+ manifest_log.grid_reservation = manifest_log.grid.reserve(1 + blocks_count_appends).?;
468
+ }
469
+
470
+ /// `compact` does not close a partial block; that is only necessary during `checkpoint`.
397
471
  pub fn compact(manifest_log: *ManifestLog, callback: Callback) void {
472
+ assert(manifest_log.opened);
398
473
  assert(!manifest_log.reading);
474
+ assert(!manifest_log.writing);
475
+ assert(manifest_log.read_callback == null);
476
+ assert(manifest_log.write_callback == null);
477
+ assert(manifest_log.grid_reservation != null);
478
+
479
+ const free_set = manifest_log.grid.superblock.free_set;
480
+ assert(free_set.count_free_reserved(manifest_log.grid_reservation.?) >= 1);
481
+
399
482
  manifest_log.read_callback = callback;
400
- manifest_log.flush(flush_callback);
483
+ manifest_log.flush(compact_flush_callback);
401
484
  }
402
485
 
403
- fn flush_callback(manifest_log: *ManifestLog) void {
404
- const callback = manifest_log.read_callback;
405
- manifest_log.read_callback = undefined;
486
+ fn compact_flush_callback(manifest_log: *ManifestLog) void {
487
+ const callback = manifest_log.read_callback.?;
406
488
 
407
489
  assert(manifest_log.opened);
408
490
  assert(!manifest_log.reading);
409
491
  assert(!manifest_log.writing);
492
+ assert(manifest_log.blocks_closed == 0);
493
+ assert(manifest_log.grid_reservation != null);
410
494
 
411
495
  const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
412
496
 
497
+ // Compact a single manifest block — to minimize latency spikes, we want to do the bare
498
+ // minimum of compaction work required.
499
+ // TODO Compact more than 1 block if fragmentation is outstripping the compaction rate.
500
+ // (Make sure to update the grid block reservation to account for this).
501
+ // Or assert that compactions cannot update blocks fast enough to outpace manifest
502
+ // log compaction (relative to the number of updates that fit in a manifest log block).
413
503
  if (manifest.oldest_block_queued_for_compaction(manifest_log.tree_hash)) |block| {
414
504
  assert(block.tree == manifest_log.tree_hash);
415
505
  assert(block.address > 0);
416
506
 
417
507
  manifest_log.reading = true;
418
- manifest_log.read_callback = callback;
419
508
  manifest_log.read_block_reference = block;
420
509
 
421
510
  manifest_log.grid.read_block(
422
- compact_callback,
511
+ compact_read_block_callback,
423
512
  &manifest_log.read,
424
513
  block.address,
425
514
  block.checksum,
515
+ .manifest,
426
516
  );
427
517
  } else {
518
+ manifest_log.read_callback = null;
519
+ manifest_log.grid.forfeit(manifest_log.grid_reservation.?);
520
+ manifest_log.grid_reservation = null;
428
521
  callback(manifest_log);
429
522
  }
430
523
  }
431
524
 
432
- fn compact_callback(read: *Grid.Read, block: BlockPtrConst) void {
525
+ fn compact_read_block_callback(read: *Grid.Read, block: BlockPtrConst) void {
433
526
  const manifest_log = @fieldParentPtr(ManifestLog, "read", read);
434
527
  assert(manifest_log.opened);
435
528
  assert(manifest_log.reading);
@@ -438,9 +531,9 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
438
531
  const block_reference = manifest_log.read_block_reference.?;
439
532
  verify_block(block, block_reference.checksum, block_reference.address);
440
533
 
441
- const entry_count = block_entry_count(block);
442
- const labels_used = labels_const(block)[0..entry_count];
443
- const tables_used = tables_const(block)[0..entry_count];
534
+ const entry_count = Block.entry_count(block);
535
+ const labels_used = Block.labels_const(block)[0..entry_count];
536
+ const tables_used = Block.tables_const(block)[0..entry_count];
444
537
 
445
538
  const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
446
539
  assert(manifest.tables.count() > 0);
@@ -479,7 +572,7 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
479
572
 
480
573
  // Blocks may be compacted if they contain frees, or are not completely full.
481
574
  // For example, a partial block may be flushed as part of a checkpoint.
482
- assert(frees > 0 or entry_count < entry_count_max);
575
+ assert(frees > 0 or entry_count < Block.entry_count_max);
483
576
 
484
577
  assert(manifest.queued_for_compaction(block_reference.address));
485
578
  manifest.remove(
@@ -489,11 +582,13 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
489
582
  );
490
583
  assert(!manifest.queued_for_compaction(block_reference.address));
491
584
 
492
- manifest_log.superblock.free_set.release_at_checkpoint(block_reference.address);
585
+ manifest_log.grid.release(block_reference.address);
586
+ manifest_log.grid.forfeit(manifest_log.grid_reservation.?);
587
+ manifest_log.grid_reservation = null;
493
588
 
494
- const callback = manifest_log.read_callback;
589
+ const callback = manifest_log.read_callback.?;
495
590
  manifest_log.reading = false;
496
- manifest_log.read_callback = undefined;
591
+ manifest_log.read_callback = null;
497
592
  manifest_log.read_block_reference = null;
498
593
 
499
594
  callback(manifest_log);
@@ -503,9 +598,8 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
503
598
  assert(manifest_log.opened);
504
599
  assert(!manifest_log.reading);
505
600
  assert(!manifest_log.writing);
506
-
507
- manifest_log.writing = true;
508
- manifest_log.write_callback = callback;
601
+ assert(manifest_log.write_callback == null);
602
+ assert(manifest_log.grid_reservation == null);
509
603
 
510
604
  if (manifest_log.entry_count > 0) {
511
605
  manifest_log.close_block();
@@ -514,12 +608,13 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
514
608
  assert(manifest_log.blocks_closed == manifest_log.blocks.count);
515
609
  }
516
610
 
517
- log.debug("checkpoint: writing {} block(s)", .{manifest_log.blocks_closed});
518
- manifest_log.write_block();
611
+ manifest_log.flush(callback);
519
612
  }
520
613
 
521
614
  fn acquire_block(manifest_log: *ManifestLog) void {
615
+ assert(manifest_log.opened);
522
616
  assert(manifest_log.entry_count == 0);
617
+ assert(manifest_log.blocks.count == manifest_log.blocks_closed);
523
618
  assert(!manifest_log.blocks.full());
524
619
 
525
620
  manifest_log.blocks.advance_tail();
@@ -529,27 +624,29 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
529
624
  const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
530
625
  header.* = .{
531
626
  .cluster = manifest_log.superblock.working.cluster,
532
- .op = manifest_log.superblock.free_set.acquire().?,
627
+ .op = manifest_log.grid.acquire(manifest_log.grid_reservation.?),
533
628
  .size = undefined,
534
629
  .command = .block,
630
+ .operation = BlockType.manifest.operation(),
535
631
  };
536
632
  }
537
633
 
538
634
  fn close_block(manifest_log: *ManifestLog) void {
539
- const block: BlockPtr = manifest_log.blocks.tail().?;
635
+ assert(manifest_log.blocks.count == manifest_log.blocks_closed + 1);
540
636
 
637
+ const block: BlockPtr = manifest_log.blocks.tail().?;
541
638
  const entry_count = manifest_log.entry_count;
542
639
  assert(entry_count > 0);
543
- assert(entry_count <= entry_count_max);
640
+ assert(entry_count <= Block.entry_count_max);
544
641
 
545
642
  const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
546
643
  assert(header.cluster == manifest_log.superblock.working.cluster);
547
644
  assert(header.op > 0);
548
- header.size = block_size(entry_count);
549
645
  assert(header.command == .block);
646
+ header.size = Block.size(entry_count);
550
647
 
551
648
  // Zero unused labels:
552
- mem.set(u8, mem.sliceAsBytes(labels(block)[entry_count..]), 0);
649
+ mem.set(u8, mem.sliceAsBytes(Block.labels(block)[entry_count..]), 0);
553
650
 
554
651
  // Zero unused tables, and padding:
555
652
  mem.set(u8, block[header.size..], 0);
@@ -558,21 +655,23 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
558
655
  header.set_checksum();
559
656
 
560
657
  verify_block(block, null, null);
561
- assert(block_entry_count(block) == entry_count);
658
+ assert(Block.entry_count(block) == entry_count);
562
659
 
563
660
  log.debug("{}: close_block: checksum={} address={} entries={}", .{
564
661
  manifest_log.tree_hash,
565
662
  header.checksum,
566
- block_address(block),
663
+ Block.address(block),
567
664
  entry_count,
568
665
  });
569
666
 
570
667
  manifest_log.blocks_closed += 1;
571
668
  manifest_log.entry_count = 0;
669
+ assert(manifest_log.blocks.count == manifest_log.blocks_closed);
572
670
  }
573
671
 
574
672
  fn verify_block(block: BlockPtrConst, checksum: ?u128, address: ?u64) void {
575
673
  const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
674
+ assert(BlockType.from(header.operation) == .manifest);
576
675
 
577
676
  if (config.verify) {
578
677
  assert(header.valid_checksum());
@@ -581,63 +680,92 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
581
680
 
582
681
  assert(checksum == null or header.checksum == checksum.?);
583
682
 
584
- assert(block_address(block) > 0);
585
- assert(address == null or block_address(block) == address.?);
683
+ assert(Block.address(block) > 0);
684
+ assert(address == null or Block.address(block) == address.?);
586
685
 
587
- const entry_count = block_entry_count(block);
686
+ const entry_count = Block.entry_count(block);
588
687
  assert(entry_count > 0);
589
688
  }
689
+ };
690
+ }
691
+
692
+ fn ManifestLogBlockType(comptime Storage: type, comptime TableInfo: type) type {
693
+ return struct {
694
+ const Grid = GridType(Storage);
695
+ const BlockPtr = Grid.BlockPtr;
696
+ const BlockPtrConst = Grid.BlockPtrConst;
697
+
698
+ const block_body_size = config.block_size - @sizeOf(vsr.Header);
699
+ const entry_size = @sizeOf(Label) + @sizeOf(TableInfo);
700
+ const entry_count_max_unaligned = @divFloor(block_body_size, entry_size);
701
+ pub const entry_count_max = @divFloor(
702
+ entry_count_max_unaligned,
703
+ @alignOf(TableInfo),
704
+ ) * @alignOf(TableInfo);
590
705
 
591
- fn block_address(block: BlockPtrConst) u64 {
706
+ comptime {
707
+ assert(entry_count_max > 0);
708
+ assert((entry_count_max * @sizeOf(Label)) % @alignOf(TableInfo) == 0);
709
+ assert((entry_count_max * @sizeOf(TableInfo)) % @alignOf(TableInfo) == 0);
710
+ }
711
+
712
+ pub const Label = packed struct {
713
+ level: u7,
714
+ event: enum(u1) { insert, remove },
715
+ };
716
+
717
+ pub fn address(block: BlockPtrConst) u64 {
592
718
  const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
593
719
  assert(header.command == .block);
594
720
 
595
- const address = header.op;
596
- assert(address > 0);
597
- return address;
721
+ const block_address = header.op;
722
+ assert(block_address > 0);
723
+ return block_address;
598
724
  }
599
725
 
600
- fn block_checksum(block: BlockPtrConst) u128 {
726
+ pub fn checksum(block: BlockPtrConst) u128 {
601
727
  const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
602
728
  assert(header.command == .block);
603
729
 
604
730
  return header.checksum;
605
731
  }
606
732
 
607
- fn block_entry_count(block: BlockPtrConst) u32 {
733
+ pub fn entry_count(block: BlockPtrConst) u32 {
608
734
  const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
609
735
  assert(header.command == .block);
610
736
 
611
737
  const labels_size = entry_count_max * @sizeOf(Label);
612
738
  const tables_size = header.size - @sizeOf(vsr.Header) - labels_size;
613
739
 
614
- const entry_count = @intCast(u32, @divExact(tables_size, @sizeOf(TableInfo)));
615
- assert(entry_count > 0);
616
- assert(entry_count <= entry_count_max);
617
- return entry_count;
740
+ const entry_count_ = @intCast(u32, @divExact(tables_size, @sizeOf(TableInfo)));
741
+ assert(entry_count_ > 0);
742
+ assert(entry_count_ <= entry_count_max);
743
+ return entry_count_;
618
744
  }
619
745
 
620
- fn block_size(entry_count: u32) u32 {
621
- assert(entry_count > 0);
622
- assert(entry_count <= entry_count_max);
746
+ pub fn size(entry_count_: u32) u32 {
747
+ assert(entry_count_ > 0);
748
+ assert(entry_count_ <= entry_count_max);
623
749
 
624
750
  // Encode the smaller type first because this will be multiplied by entry_count_max.
625
751
  const labels_size = entry_count_max * @sizeOf(Label);
626
- const tables_size = entry_count * @sizeOf(TableInfo);
752
+ assert(labels_size == labels_size_max);
753
+ assert((@sizeOf(vsr.Header) + labels_size) % @alignOf(TableInfo) == 0);
754
+ const tables_size = entry_count_ * @sizeOf(TableInfo);
627
755
 
628
756
  return @sizeOf(vsr.Header) + labels_size + tables_size;
629
757
  }
630
758
 
631
759
  const labels_size_max = entry_count_max * @sizeOf(Label);
632
760
 
633
- fn labels(block: BlockPtr) *[entry_count_max]Label {
761
+ pub fn labels(block: BlockPtr) *[entry_count_max]Label {
634
762
  return mem.bytesAsSlice(
635
763
  Label,
636
764
  block[@sizeOf(vsr.Header)..][0..labels_size_max],
637
765
  )[0..entry_count_max];
638
766
  }
639
767
 
640
- fn labels_const(block: BlockPtrConst) *const [entry_count_max]Label {
768
+ pub fn labels_const(block: BlockPtrConst) *const [entry_count_max]Label {
641
769
  return mem.bytesAsSlice(
642
770
  Label,
643
771
  block[@sizeOf(vsr.Header)..][0..labels_size_max],
@@ -646,259 +774,18 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
646
774
 
647
775
  const tables_size_max = entry_count_max * @sizeOf(TableInfo);
648
776
 
649
- fn tables(block: BlockPtr) *[entry_count_max]TableInfo {
777
+ pub fn tables(block: BlockPtr) *[entry_count_max]TableInfo {
650
778
  return mem.bytesAsSlice(
651
779
  TableInfo,
652
- block[@sizeOf(vsr.Header) + entry_count_max ..][0..tables_size_max],
780
+ block[@sizeOf(vsr.Header) + labels_size_max ..][0..tables_size_max],
653
781
  )[0..entry_count_max];
654
782
  }
655
783
 
656
- fn tables_const(block: BlockPtrConst) *const [entry_count_max]TableInfo {
784
+ pub fn tables_const(block: BlockPtrConst) *const [entry_count_max]TableInfo {
657
785
  return mem.bytesAsSlice(
658
786
  TableInfo,
659
- block[@sizeOf(vsr.Header) + entry_count_max ..][0..tables_size_max],
787
+ block[@sizeOf(vsr.Header) + labels_size_max ..][0..tables_size_max],
660
788
  )[0..entry_count_max];
661
789
  }
662
790
  };
663
791
  }
664
-
665
- // TODO This is a manual runner to be replaced with a fuzz test.
666
- fn ManifestLogTestType(
667
- comptime Storage: type,
668
- comptime TableInfo: type,
669
- ) type {
670
- return struct {
671
- const ManifestLogTest = @This();
672
- const ManifestLog = ManifestLogType(Storage, TableInfo);
673
-
674
- const SuperBlock = SuperBlockType(Storage);
675
- const Grid = GridType(Storage);
676
-
677
- superblock: *SuperBlock,
678
- superblock_context: SuperBlock.Context = undefined,
679
- manifest_log: ManifestLog,
680
- pending: usize = 0,
681
-
682
- fn init(allocator: mem.Allocator, grid: *Grid) !ManifestLogTest {
683
- const tree_hash: u128 = std.math.maxInt(u128);
684
-
685
- var manifest_log = try ManifestLog.init(allocator, grid, tree_hash);
686
- errdefer manifest_log.deinit(allocator);
687
-
688
- return ManifestLogTest{
689
- .superblock = grid.superblock,
690
- .manifest_log = manifest_log,
691
- };
692
- }
693
-
694
- fn deinit(t: *ManifestLogTest, allocator: mem.Allocator) void {
695
- t.manifest_log.deinit(allocator);
696
- }
697
-
698
- fn format_superblock(t: *ManifestLogTest) void {
699
- t.pending += 1;
700
- t.superblock.format(format_superblock_callback, &t.superblock_context, .{
701
- .cluster = 10,
702
- .replica = 0,
703
- .size_max = 512 * 1024 * 1024,
704
- });
705
- }
706
-
707
- fn format_superblock_callback(context: *SuperBlock.Context) void {
708
- const t = @fieldParentPtr(ManifestLogTest, "superblock_context", context);
709
- t.pending -= 1;
710
- t.open_superblock();
711
- }
712
-
713
- fn open_superblock(t: *ManifestLogTest) void {
714
- t.pending += 1;
715
- t.superblock.open(open_superblock_callback, &t.superblock_context);
716
- }
717
-
718
- fn open_superblock_callback(context: *SuperBlock.Context) void {
719
- const t = @fieldParentPtr(ManifestLogTest, "superblock_context", context);
720
- t.pending -= 1;
721
-
722
- t.open();
723
- }
724
-
725
- fn open(t: *ManifestLogTest) void {
726
- t.pending += 1;
727
- t.manifest_log.open(open_event, open_callback);
728
- }
729
-
730
- fn open_event(manifest_log: *ManifestLog, level: u7, table: *const TableInfo) void {
731
- log.debug(
732
- "{}: open_event: level={} checksum={} address={} flags={} snapshot={}..{}",
733
- .{
734
- manifest_log.tree_hash,
735
- level,
736
- table.checksum,
737
- table.address,
738
- table.flags,
739
- table.snapshot_min,
740
- table.snapshot_max,
741
- },
742
- );
743
- }
744
-
745
- fn open_callback(manifest_log: *ManifestLog) void {
746
- const t = @fieldParentPtr(ManifestLogTest, "manifest_log", manifest_log);
747
- t.pending -= 1;
748
-
749
- t.manifest_log.insert(2, &TableInfo{
750
- .checksum = 123,
751
- .address = 7,
752
- .flags = 0,
753
- .snapshot_min = 42,
754
- .key_min = 50,
755
- .key_max = 100,
756
- });
757
-
758
- t.flush();
759
- }
760
-
761
- fn flush(t: *ManifestLogTest) void {
762
- t.pending += 1;
763
- t.manifest_log.flush(flush_callback);
764
- }
765
-
766
- fn flush_callback(manifest_log: *ManifestLog) void {
767
- const t = @fieldParentPtr(ManifestLogTest, "manifest_log", manifest_log);
768
- t.pending -= 1;
769
- t.checkpoint();
770
- }
771
-
772
- fn checkpoint(t: *ManifestLogTest) void {
773
- t.pending += 1;
774
- t.manifest_log.checkpoint(checkpoint_callback);
775
- }
776
-
777
- fn checkpoint_callback(manifest_log: *ManifestLog) void {
778
- const t = @fieldParentPtr(ManifestLogTest, "manifest_log", manifest_log);
779
- t.pending -= 1;
780
-
781
- t.manifest_log.insert(2, &TableInfo{
782
- .checksum = 123,
783
- .address = 7,
784
- .flags = 0,
785
- .snapshot_min = 42,
786
- .snapshot_max = 50,
787
- .key_min = 50,
788
- .key_max = 100,
789
- });
790
-
791
- t.manifest_log.remove(2, &TableInfo{
792
- .checksum = 123,
793
- .address = 7,
794
- .flags = 0,
795
- .snapshot_min = 42,
796
- .snapshot_max = 50,
797
- .key_min = 50,
798
- .key_max = 100,
799
- });
800
-
801
- t.checkpoint_again();
802
- }
803
-
804
- fn checkpoint_again(t: *ManifestLogTest) void {
805
- t.pending += 1;
806
- t.manifest_log.checkpoint(checkpoint_again_callback);
807
- }
808
-
809
- fn checkpoint_again_callback(manifest_log: *ManifestLog) void {
810
- const t = @fieldParentPtr(ManifestLogTest, "manifest_log", manifest_log);
811
- t.pending -= 1;
812
- t.compact();
813
- }
814
-
815
- fn compact(t: *ManifestLogTest) void {
816
- t.pending += 1;
817
- t.manifest_log.compact(compact_callback);
818
- }
819
-
820
- fn compact_callback(manifest_log: *ManifestLog) void {
821
- const t = @fieldParentPtr(ManifestLogTest, "manifest_log", manifest_log);
822
- t.pending -= 1;
823
-
824
- const tree = t.manifest_log.tree_hash;
825
- if (t.manifest_log.superblock.manifest.oldest_block_queued_for_compaction(tree)) |_| {
826
- t.compact();
827
- } else {
828
- t.checkpoint_superblock();
829
- }
830
- }
831
-
832
- fn checkpoint_superblock(t: *ManifestLogTest) void {
833
- t.pending += 1;
834
- t.superblock.checkpoint(checkpoint_superblock_callback, &t.superblock_context);
835
- }
836
-
837
- fn checkpoint_superblock_callback(context: *SuperBlock.Context) void {
838
- const t = @fieldParentPtr(ManifestLogTest, "superblock_context", context);
839
- t.pending -= 1;
840
- }
841
- };
842
- }
843
-
844
- pub fn main() !void {
845
- const testing = std.testing;
846
- const allocator = testing.allocator;
847
-
848
- testing.log_level = .debug;
849
-
850
- const os = std.os;
851
- const IO = @import("../io.zig").IO;
852
- const Storage = @import("../storage.zig").Storage;
853
- const SuperBlock = SuperBlockType(Storage);
854
- const Grid = @import("grid.zig").GridType(Storage);
855
-
856
- const dir_path = ".";
857
- const dir_fd = os.openZ(dir_path, os.O.CLOEXEC | os.O.RDONLY, 0) catch |err| {
858
- std.debug.print("failed to open directory '{s}': {}", .{ dir_path, err });
859
- return;
860
- };
861
-
862
- const size_max = 2 * 1024 * 1024 * 1024;
863
- const storage_fd = try IO.open_file(dir_fd, "test_manifest_log", size_max, true);
864
- defer std.fs.cwd().deleteFile("test_manifest_log") catch {};
865
-
866
- var io = try IO.init(128, 0);
867
- defer io.deinit();
868
-
869
- var storage = try Storage.init(&io, storage_fd);
870
- defer storage.deinit();
871
-
872
- var superblock = try SuperBlock.init(allocator, &storage);
873
- defer superblock.deinit(allocator);
874
-
875
- var grid = try Grid.init(allocator, &superblock);
876
- defer grid.deinit(allocator);
877
-
878
- const TableInfo = extern struct {
879
- checksum: u128,
880
- address: u64,
881
- flags: u64 = 0,
882
-
883
- /// The minimum snapshot that can see this table (with exclusive bounds).
884
- /// This value is set to the current snapshot tick on table creation.
885
- snapshot_min: u64,
886
-
887
- /// The maximum snapshot that can see this table (with exclusive bounds).
888
- /// This value is set to the current snapshot tick on table deletion.
889
- snapshot_max: u64 = math.maxInt(u64),
890
-
891
- key_min: u128,
892
- key_max: u128,
893
- };
894
- assert(@sizeOf(TableInfo) == 48 + 16 * 2);
895
- assert(@alignOf(TableInfo) == 16);
896
-
897
- const ManifestLogTest = ManifestLogTestType(Storage, TableInfo);
898
-
899
- var t = try ManifestLogTest.init(allocator, &grid);
900
- defer t.deinit(allocator);
901
-
902
- t.format_superblock();
903
- while (t.pending > 0) try io.run_for_ns(100);
904
- }