tigerbeetle-node 0.8.0 → 0.10.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 +47 -47
- package/dist/benchmark.js +15 -15
- package/dist/benchmark.js.map +1 -1
- package/dist/index.d.ts +66 -61
- package/dist/index.js +66 -61
- package/dist/index.js.map +1 -1
- package/dist/test.js +1 -1
- package/dist/test.js.map +1 -1
- package/package.json +14 -16
- package/scripts/download_node_headers.sh +3 -1
- package/src/index.ts +5 -0
- package/src/node.zig +18 -19
- package/src/tigerbeetle/scripts/benchmark.bat +47 -46
- package/src/tigerbeetle/scripts/benchmark.sh +25 -10
- package/src/tigerbeetle/scripts/install.sh +2 -1
- package/src/tigerbeetle/scripts/install_zig.bat +109 -109
- package/src/tigerbeetle/scripts/install_zig.sh +18 -18
- package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +12 -3
- package/src/tigerbeetle/scripts/vopr.bat +47 -47
- package/src/tigerbeetle/scripts/vopr.sh +5 -5
- package/src/tigerbeetle/src/benchmark.zig +17 -9
- package/src/tigerbeetle/src/benchmark_array_search.zig +317 -0
- package/src/tigerbeetle/src/benchmarks/perf.zig +299 -0
- package/src/tigerbeetle/src/c/tb_client/context.zig +103 -0
- package/src/tigerbeetle/src/c/tb_client/packet.zig +80 -0
- package/src/tigerbeetle/src/c/tb_client/signal.zig +288 -0
- package/src/tigerbeetle/src/c/tb_client/thread.zig +329 -0
- package/src/tigerbeetle/src/c/tb_client.h +201 -0
- package/src/tigerbeetle/src/c/tb_client.zig +101 -0
- package/src/tigerbeetle/src/c/test.zig +1 -0
- package/src/tigerbeetle/src/cli.zig +142 -83
- package/src/tigerbeetle/src/config.zig +136 -23
- package/src/tigerbeetle/src/demo.zig +12 -8
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +3 -3
- package/src/tigerbeetle/src/demo_04_create_pending_transfers.zig +10 -10
- package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +7 -7
- package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +3 -3
- package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +1 -1
- package/src/tigerbeetle/src/ewah.zig +318 -0
- package/src/tigerbeetle/src/ewah_benchmark.zig +121 -0
- package/src/tigerbeetle/src/eytzinger_benchmark.zig +317 -0
- package/src/tigerbeetle/src/fifo.zig +17 -1
- package/src/tigerbeetle/src/io/darwin.zig +12 -10
- package/src/tigerbeetle/src/io/linux.zig +25 -9
- package/src/tigerbeetle/src/io/windows.zig +13 -9
- package/src/tigerbeetle/src/iops.zig +101 -0
- package/src/tigerbeetle/src/lsm/binary_search.zig +214 -0
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +82 -0
- package/src/tigerbeetle/src/lsm/compaction.zig +603 -0
- package/src/tigerbeetle/src/lsm/composite_key.zig +75 -0
- package/src/tigerbeetle/src/lsm/direction.zig +11 -0
- package/src/tigerbeetle/src/lsm/eytzinger.zig +587 -0
- package/src/tigerbeetle/src/lsm/forest.zig +630 -0
- package/src/tigerbeetle/src/lsm/grid.zig +473 -0
- package/src/tigerbeetle/src/lsm/groove.zig +939 -0
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +452 -0
- package/src/tigerbeetle/src/lsm/level_iterator.zig +296 -0
- package/src/tigerbeetle/src/lsm/manifest.zig +680 -0
- package/src/tigerbeetle/src/lsm/manifest_level.zig +1169 -0
- package/src/tigerbeetle/src/lsm/manifest_log.zig +904 -0
- package/src/tigerbeetle/src/lsm/node_pool.zig +231 -0
- package/src/tigerbeetle/src/lsm/posted_groove.zig +399 -0
- package/src/tigerbeetle/src/lsm/segmented_array.zig +998 -0
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +844 -0
- package/src/tigerbeetle/src/lsm/table.zig +932 -0
- package/src/tigerbeetle/src/lsm/table_immutable.zig +196 -0
- package/src/tigerbeetle/src/lsm/table_iterator.zig +295 -0
- package/src/tigerbeetle/src/lsm/table_mutable.zig +123 -0
- package/src/tigerbeetle/src/lsm/test.zig +429 -0
- package/src/tigerbeetle/src/lsm/tree.zig +1085 -0
- package/src/tigerbeetle/src/main.zig +121 -95
- package/src/tigerbeetle/src/message_bus.zig +49 -48
- package/src/tigerbeetle/src/message_pool.zig +19 -3
- package/src/tigerbeetle/src/ring_buffer.zig +172 -31
- package/src/tigerbeetle/src/simulator.zig +171 -43
- package/src/tigerbeetle/src/state_machine.zig +1026 -599
- package/src/tigerbeetle/src/storage.zig +46 -16
- package/src/tigerbeetle/src/test/cluster.zig +257 -78
- package/src/tigerbeetle/src/test/message_bus.zig +15 -24
- package/src/tigerbeetle/src/test/network.zig +26 -17
- package/src/tigerbeetle/src/test/packet_simulator.zig +14 -1
- package/src/tigerbeetle/src/test/state_checker.zig +10 -6
- package/src/tigerbeetle/src/test/state_machine.zig +159 -68
- package/src/tigerbeetle/src/test/storage.zig +137 -49
- package/src/tigerbeetle/src/tigerbeetle.zig +5 -0
- package/src/tigerbeetle/src/unit_tests.zig +8 -0
- package/src/tigerbeetle/src/util.zig +51 -0
- package/src/tigerbeetle/src/vsr/client.zig +21 -7
- package/src/tigerbeetle/src/vsr/journal.zig +1429 -514
- package/src/tigerbeetle/src/vsr/replica.zig +1855 -550
- package/src/tigerbeetle/src/vsr/superblock.zig +1743 -0
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +258 -0
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +644 -0
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +546 -0
- package/src/tigerbeetle/src/vsr.zig +134 -52
- package/.yarn/releases/yarn-berry.cjs +0 -55
- package/.yarnrc.yml +0 -1
- package/scripts/postinstall.sh +0 -6
- package/yarn.lock +0 -42
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
//! Maintains an on-disk manifest log of the latest TableInfo's in an LSM tree's in-memory manifest.
|
|
2
|
+
//!
|
|
3
|
+
//! Invariants:
|
|
4
|
+
//!
|
|
5
|
+
//! * Checkpointing the manifest log must flush all buffered log blocks.
|
|
6
|
+
//!
|
|
7
|
+
//! * Opening the manifest log must emit only the latest TableInfo's to be inserted.
|
|
8
|
+
//!
|
|
9
|
+
//! * Opening the manifest log after a crash must result in exactly the same `compaction_set` in
|
|
10
|
+
//! `SuperBlock.Manifest` as before the crash assuming that the crash was exactly at a checkpoint.
|
|
11
|
+
//!
|
|
12
|
+
//! * The latest version of a table must never be dropped from the log through a compaction, unless
|
|
13
|
+
//! the table was removed.
|
|
14
|
+
//!
|
|
15
|
+
//! * Removes that are recorded in a log block must also queue that log block for compaction.
|
|
16
|
+
//!
|
|
17
|
+
//! * Compaction must compact partially full blocks, even where it must rewrite all entries to the
|
|
18
|
+
//! tail end of the log.
|
|
19
|
+
//!
|
|
20
|
+
//! * If a remove is dropped from the log, then all prior inserts must already have been dropped.
|
|
21
|
+
|
|
22
|
+
const std = @import("std");
|
|
23
|
+
const assert = std.debug.assert;
|
|
24
|
+
const math = std.math;
|
|
25
|
+
const mem = std.mem;
|
|
26
|
+
|
|
27
|
+
const log = std.log.scoped(.manifest_log);
|
|
28
|
+
|
|
29
|
+
const config = @import("../config.zig");
|
|
30
|
+
const vsr = @import("../vsr.zig");
|
|
31
|
+
|
|
32
|
+
const SuperBlockType = vsr.SuperBlockType;
|
|
33
|
+
const GridType = @import("grid.zig").GridType;
|
|
34
|
+
const RingBuffer = @import("../ring_buffer.zig").RingBuffer;
|
|
35
|
+
|
|
36
|
+
pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
|
|
37
|
+
return struct {
|
|
38
|
+
const ManifestLog = @This();
|
|
39
|
+
|
|
40
|
+
const SuperBlock = SuperBlockType(Storage);
|
|
41
|
+
const Grid = GridType(Storage);
|
|
42
|
+
|
|
43
|
+
const BlockPtr = *align(config.sector_size) [config.block_size]u8;
|
|
44
|
+
const BlockPtrConst = *align(config.sector_size) const [config.block_size]u8;
|
|
45
|
+
|
|
46
|
+
pub const Callback = fn (manifest_log: *ManifestLog) void;
|
|
47
|
+
|
|
48
|
+
pub const OpenEvent = fn (
|
|
49
|
+
manifest_log: *ManifestLog,
|
|
50
|
+
level: u7,
|
|
51
|
+
table: *const TableInfo,
|
|
52
|
+
) void;
|
|
53
|
+
|
|
54
|
+
pub const Label = packed struct {
|
|
55
|
+
level: u7,
|
|
56
|
+
event: enum(u1) { insert, remove },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const alignment = 16;
|
|
60
|
+
|
|
61
|
+
comptime {
|
|
62
|
+
// Bit 7 is reserved to indicate whether the event is an insert or remove.
|
|
63
|
+
assert(config.lsm_levels <= math.maxInt(u7) + 1);
|
|
64
|
+
|
|
65
|
+
assert(@sizeOf(Label) == @sizeOf(u8));
|
|
66
|
+
|
|
67
|
+
// All TableInfo's should already be 16-byte aligned because of the leading checksum.
|
|
68
|
+
assert(@alignOf(TableInfo) == alignment);
|
|
69
|
+
|
|
70
|
+
// For keys { 8, 16, 24, 32 } all TableInfo's should be a multiple of the alignment.
|
|
71
|
+
// However, we still store Label ahead of TableInfo to save space on the network.
|
|
72
|
+
// This means we store fewer entries per manifest block, to gain less padding,
|
|
73
|
+
// 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.
|
|
75
|
+
assert(@sizeOf(TableInfo) % alignment == 0);
|
|
76
|
+
}
|
|
77
|
+
|
|
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;
|
|
82
|
+
|
|
83
|
+
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);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
superblock: *SuperBlock,
|
|
90
|
+
grid: *Grid,
|
|
91
|
+
tree_hash: u128,
|
|
92
|
+
|
|
93
|
+
/// The head block is used to accumulate a full block, to be written at the next flush.
|
|
94
|
+
/// 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),
|
|
97
|
+
|
|
98
|
+
/// The number of blocks that have been appended to, filled up, and then closed.
|
|
99
|
+
blocks_closed: u8 = 0,
|
|
100
|
+
|
|
101
|
+
/// The number of entries in the open block.
|
|
102
|
+
entry_count: u32 = 0,
|
|
103
|
+
|
|
104
|
+
opened: bool = false,
|
|
105
|
+
open_event: OpenEvent = undefined,
|
|
106
|
+
open_iterator: SuperBlock.Manifest.IteratorReverse = undefined,
|
|
107
|
+
|
|
108
|
+
reading: bool = false,
|
|
109
|
+
read: Grid.Read = undefined,
|
|
110
|
+
read_callback: Callback = undefined,
|
|
111
|
+
read_block_reference: ?SuperBlock.Manifest.BlockReference = null,
|
|
112
|
+
|
|
113
|
+
writing: bool = false,
|
|
114
|
+
write: Grid.Write = undefined,
|
|
115
|
+
write_callback: Callback = undefined,
|
|
116
|
+
|
|
117
|
+
pub fn init(allocator: mem.Allocator, grid: *Grid, tree_hash: u128) !ManifestLog {
|
|
118
|
+
// TODO RingBuffer for .pointer should be extended to take care of alignment:
|
|
119
|
+
|
|
120
|
+
const a = try allocator.alignedAlloc(u8, config.sector_size, config.block_size);
|
|
121
|
+
errdefer allocator.free(a);
|
|
122
|
+
|
|
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);
|
|
128
|
+
|
|
129
|
+
return ManifestLog{
|
|
130
|
+
.superblock = grid.superblock,
|
|
131
|
+
.grid = grid,
|
|
132
|
+
.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
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
pub fn deinit(manifest_log: *ManifestLog, allocator: mem.Allocator) void {
|
|
144
|
+
for (manifest_log.blocks.buffer) |block| allocator.free(block);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Opens the manifest log.
|
|
148
|
+
/// Reads the manifest blocks in reverse order and passes extent table inserts to event().
|
|
149
|
+
/// Therefore, only the latest version of a table will be emitted by event() for insertion
|
|
150
|
+
/// into the in-memory manifest. Older versions of a table in older manifest blocks will not
|
|
151
|
+
/// be emitted, as an optimization to not replay all table mutations.
|
|
152
|
+
/// SuperBlock.Manifest.tables is used to track the latest version of a table.
|
|
153
|
+
pub fn open(manifest_log: *ManifestLog, event: OpenEvent, callback: Callback) void {
|
|
154
|
+
assert(!manifest_log.opened);
|
|
155
|
+
assert(!manifest_log.reading);
|
|
156
|
+
assert(!manifest_log.writing);
|
|
157
|
+
|
|
158
|
+
manifest_log.open_event = event;
|
|
159
|
+
manifest_log.open_iterator = manifest_log.superblock.manifest.iterator_reverse(
|
|
160
|
+
manifest_log.tree_hash,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
manifest_log.reading = true;
|
|
164
|
+
manifest_log.read_callback = callback;
|
|
165
|
+
|
|
166
|
+
manifest_log.open_read_block();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn open_read_block(manifest_log: *ManifestLog) void {
|
|
170
|
+
assert(!manifest_log.opened);
|
|
171
|
+
assert(manifest_log.reading);
|
|
172
|
+
assert(!manifest_log.writing);
|
|
173
|
+
|
|
174
|
+
manifest_log.read_block_reference = manifest_log.open_iterator.next();
|
|
175
|
+
|
|
176
|
+
if (manifest_log.read_block_reference) |block| {
|
|
177
|
+
assert(block.tree == manifest_log.tree_hash);
|
|
178
|
+
assert(block.address > 0);
|
|
179
|
+
|
|
180
|
+
manifest_log.grid.read_block(
|
|
181
|
+
open_read_block_callback,
|
|
182
|
+
&manifest_log.read,
|
|
183
|
+
block.address,
|
|
184
|
+
block.checksum,
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
manifest_log.opened = true;
|
|
188
|
+
manifest_log.open_event = undefined;
|
|
189
|
+
manifest_log.open_iterator = undefined;
|
|
190
|
+
|
|
191
|
+
const callback = manifest_log.read_callback;
|
|
192
|
+
manifest_log.reading = false;
|
|
193
|
+
manifest_log.read_callback = undefined;
|
|
194
|
+
assert(manifest_log.read_block_reference == null);
|
|
195
|
+
|
|
196
|
+
callback(manifest_log);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
fn open_read_block_callback(read: *Grid.Read, block: Grid.BlockPtrConst) void {
|
|
201
|
+
const manifest_log = @fieldParentPtr(ManifestLog, "read", read);
|
|
202
|
+
assert(!manifest_log.opened);
|
|
203
|
+
assert(manifest_log.reading);
|
|
204
|
+
assert(!manifest_log.writing);
|
|
205
|
+
|
|
206
|
+
const block_reference = manifest_log.read_block_reference.?;
|
|
207
|
+
verify_block(block, block_reference.checksum, block_reference.address);
|
|
208
|
+
|
|
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];
|
|
212
|
+
|
|
213
|
+
const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
|
|
214
|
+
|
|
215
|
+
var entry = entry_count;
|
|
216
|
+
while (entry > 0) {
|
|
217
|
+
entry -= 1;
|
|
218
|
+
|
|
219
|
+
const label = labels_used[entry];
|
|
220
|
+
const table = &tables_used[entry];
|
|
221
|
+
|
|
222
|
+
if (manifest.insert_table_extent(table.address, block_reference.address, entry)) {
|
|
223
|
+
switch (label.event) {
|
|
224
|
+
.insert => manifest_log.open_event(manifest_log, label.level, table),
|
|
225
|
+
.remove => manifest.queue_for_compaction(block_reference.address),
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
manifest.queue_for_compaction(block_reference.address);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
log.debug("{}: opened: checksum={} address={} entries={}", .{
|
|
233
|
+
manifest_log.tree_hash,
|
|
234
|
+
block_reference.checksum,
|
|
235
|
+
block_reference.address,
|
|
236
|
+
entry_count,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
manifest_log.open_read_block();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Appends an insert, an update, or a direct move of a table to a level.
|
|
243
|
+
/// A move is only recorded as an insert, there is no remove from the previous level, since
|
|
244
|
+
/// this is safer (no potential to get the event order wrong) and reduces fragmentation.
|
|
245
|
+
pub fn insert(manifest_log: *ManifestLog, level: u7, table: *const TableInfo) void {
|
|
246
|
+
manifest_log.append(.{ .level = level, .event = .insert }, table);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/// Appends the removal of a table from a level.
|
|
250
|
+
/// The table must have previously been inserted to the manifest log.
|
|
251
|
+
pub fn remove(manifest_log: *ManifestLog, level: u7, table: *const TableInfo) void {
|
|
252
|
+
manifest_log.append(.{ .level = level, .event = .remove }, table);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fn append(manifest_log: *ManifestLog, label: Label, table: *const TableInfo) void {
|
|
256
|
+
assert(manifest_log.opened);
|
|
257
|
+
assert(label.level < config.lsm_levels);
|
|
258
|
+
assert(table.address > 0);
|
|
259
|
+
assert(table.snapshot_min > 0);
|
|
260
|
+
assert(table.snapshot_max > table.snapshot_min);
|
|
261
|
+
|
|
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();
|
|
267
|
+
manifest_log.acquire_block();
|
|
268
|
+
} else if (manifest_log.entry_count > 0) {
|
|
269
|
+
assert(manifest_log.blocks.count > 0);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
assert(manifest_log.entry_count < entry_count_max);
|
|
273
|
+
assert(manifest_log.blocks.count - manifest_log.blocks_closed == 1);
|
|
274
|
+
|
|
275
|
+
log.debug(
|
|
276
|
+
"{}: {s}: level={} checksum={} address={} flags={} snapshot={}..{}",
|
|
277
|
+
.{
|
|
278
|
+
manifest_log.tree_hash,
|
|
279
|
+
@tagName(label.event),
|
|
280
|
+
label.level,
|
|
281
|
+
table.checksum,
|
|
282
|
+
table.address,
|
|
283
|
+
table.flags,
|
|
284
|
+
table.snapshot_min,
|
|
285
|
+
table.snapshot_max,
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const block: BlockPtr = manifest_log.blocks.tail().?;
|
|
290
|
+
const entry = manifest_log.entry_count;
|
|
291
|
+
labels(block)[entry] = label;
|
|
292
|
+
tables(block)[entry] = table.*;
|
|
293
|
+
|
|
294
|
+
const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
|
|
295
|
+
const address = block_address(block);
|
|
296
|
+
if (manifest.update_table_extent(table.address, address, entry)) |previous_block| {
|
|
297
|
+
manifest.queue_for_compaction(previous_block);
|
|
298
|
+
if (label.event == .remove) manifest.queue_for_compaction(address);
|
|
299
|
+
} else {
|
|
300
|
+
// A remove must remove a insert, which implies that it must update the extent.
|
|
301
|
+
assert(label.event != .remove);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
manifest_log.entry_count += 1;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
pub fn flush(manifest_log: *ManifestLog, callback: Callback) void {
|
|
308
|
+
assert(manifest_log.opened);
|
|
309
|
+
assert(!manifest_log.reading);
|
|
310
|
+
assert(!manifest_log.writing);
|
|
311
|
+
|
|
312
|
+
manifest_log.writing = true;
|
|
313
|
+
manifest_log.write_callback = callback;
|
|
314
|
+
|
|
315
|
+
log.debug("{}: flush: writing {} block(s)", .{
|
|
316
|
+
manifest_log.tree_hash,
|
|
317
|
+
manifest_log.blocks_closed,
|
|
318
|
+
});
|
|
319
|
+
manifest_log.write_block();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
fn write_block(manifest_log: *ManifestLog) void {
|
|
323
|
+
assert(manifest_log.opened);
|
|
324
|
+
assert(manifest_log.writing);
|
|
325
|
+
assert(manifest_log.blocks_closed <= manifest_log.blocks.count);
|
|
326
|
+
|
|
327
|
+
if (manifest_log.blocks_closed == 0) {
|
|
328
|
+
if (manifest_log.blocks.count == 0) {
|
|
329
|
+
assert(manifest_log.entry_count == 0);
|
|
330
|
+
} else {
|
|
331
|
+
assert(manifest_log.blocks.count == 1);
|
|
332
|
+
assert(manifest_log.entry_count < entry_count_max);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const callback = manifest_log.write_callback;
|
|
336
|
+
manifest_log.write_callback = undefined;
|
|
337
|
+
manifest_log.writing = false;
|
|
338
|
+
|
|
339
|
+
callback(manifest_log);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const block = manifest_log.blocks.head().?;
|
|
344
|
+
verify_block(block, null, null);
|
|
345
|
+
|
|
346
|
+
const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
|
|
347
|
+
const address = block_address(block);
|
|
348
|
+
assert(address > 0);
|
|
349
|
+
|
|
350
|
+
const entry_count = block_entry_count(block);
|
|
351
|
+
|
|
352
|
+
if (manifest_log.blocks_closed == 1 and manifest_log.blocks.count == 1) {
|
|
353
|
+
assert(entry_count > 0);
|
|
354
|
+
} else {
|
|
355
|
+
assert(entry_count == entry_count_max);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
log.debug("{}: write_block: checksum={} address={} entries={}", .{
|
|
359
|
+
manifest_log.tree_hash,
|
|
360
|
+
header.checksum,
|
|
361
|
+
address,
|
|
362
|
+
entry_count,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
manifest_log.grid.write_block(
|
|
366
|
+
write_block_callback,
|
|
367
|
+
&manifest_log.write,
|
|
368
|
+
block,
|
|
369
|
+
address,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
fn write_block_callback(write: *Grid.Write) void {
|
|
374
|
+
const manifest_log = @fieldParentPtr(ManifestLog, "write", write);
|
|
375
|
+
assert(manifest_log.opened);
|
|
376
|
+
assert(manifest_log.writing);
|
|
377
|
+
|
|
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
|
+
manifest_log.blocks_closed -= 1;
|
|
391
|
+
manifest_log.blocks.advance_head();
|
|
392
|
+
assert(manifest_log.blocks_closed <= manifest_log.blocks.count);
|
|
393
|
+
|
|
394
|
+
manifest_log.write_block();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
pub fn compact(manifest_log: *ManifestLog, callback: Callback) void {
|
|
398
|
+
assert(!manifest_log.reading);
|
|
399
|
+
manifest_log.read_callback = callback;
|
|
400
|
+
manifest_log.flush(flush_callback);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fn flush_callback(manifest_log: *ManifestLog) void {
|
|
404
|
+
const callback = manifest_log.read_callback;
|
|
405
|
+
manifest_log.read_callback = undefined;
|
|
406
|
+
|
|
407
|
+
assert(manifest_log.opened);
|
|
408
|
+
assert(!manifest_log.reading);
|
|
409
|
+
assert(!manifest_log.writing);
|
|
410
|
+
|
|
411
|
+
const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
|
|
412
|
+
|
|
413
|
+
if (manifest.oldest_block_queued_for_compaction(manifest_log.tree_hash)) |block| {
|
|
414
|
+
assert(block.tree == manifest_log.tree_hash);
|
|
415
|
+
assert(block.address > 0);
|
|
416
|
+
|
|
417
|
+
manifest_log.reading = true;
|
|
418
|
+
manifest_log.read_callback = callback;
|
|
419
|
+
manifest_log.read_block_reference = block;
|
|
420
|
+
|
|
421
|
+
manifest_log.grid.read_block(
|
|
422
|
+
compact_callback,
|
|
423
|
+
&manifest_log.read,
|
|
424
|
+
block.address,
|
|
425
|
+
block.checksum,
|
|
426
|
+
);
|
|
427
|
+
} else {
|
|
428
|
+
callback(manifest_log);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
fn compact_callback(read: *Grid.Read, block: BlockPtrConst) void {
|
|
433
|
+
const manifest_log = @fieldParentPtr(ManifestLog, "read", read);
|
|
434
|
+
assert(manifest_log.opened);
|
|
435
|
+
assert(manifest_log.reading);
|
|
436
|
+
assert(!manifest_log.writing);
|
|
437
|
+
|
|
438
|
+
const block_reference = manifest_log.read_block_reference.?;
|
|
439
|
+
verify_block(block, block_reference.checksum, block_reference.address);
|
|
440
|
+
|
|
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];
|
|
444
|
+
|
|
445
|
+
const manifest: *SuperBlock.Manifest = &manifest_log.superblock.manifest;
|
|
446
|
+
assert(manifest.tables.count() > 0);
|
|
447
|
+
|
|
448
|
+
var frees: u32 = 0;
|
|
449
|
+
var entry: u32 = 0;
|
|
450
|
+
while (entry < entry_count) : (entry += 1) {
|
|
451
|
+
const label = labels_used[entry];
|
|
452
|
+
const table = &tables_used[entry];
|
|
453
|
+
|
|
454
|
+
// Remove the extent if the table is the latest version.
|
|
455
|
+
// We must iterate entries in forward order to drop the extent here.
|
|
456
|
+
// Otherwise, stale versions earlier in the block may reappear.
|
|
457
|
+
if (manifest.remove_table_extent(table.address, block_reference.address, entry)) {
|
|
458
|
+
switch (label.event) {
|
|
459
|
+
// Append the table, updating the table extent:
|
|
460
|
+
.insert => manifest_log.append(label, table),
|
|
461
|
+
// Since we compact oldest blocks first, we know that we have already
|
|
462
|
+
// compacted all inserts that were eclipsed by this remove, so this remove
|
|
463
|
+
// can now be safely dropped.
|
|
464
|
+
.remove => frees += 1,
|
|
465
|
+
}
|
|
466
|
+
} else {
|
|
467
|
+
// The table is not the latest version and can dropped.
|
|
468
|
+
frees += 1;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
log.debug("{}: compacted: checksum={} address={} frees={}/{}", .{
|
|
473
|
+
manifest_log.tree_hash,
|
|
474
|
+
block_reference.checksum,
|
|
475
|
+
block_reference.address,
|
|
476
|
+
frees,
|
|
477
|
+
entry_count,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Blocks may be compacted if they contain frees, or are not completely full.
|
|
481
|
+
// For example, a partial block may be flushed as part of a checkpoint.
|
|
482
|
+
assert(frees > 0 or entry_count < entry_count_max);
|
|
483
|
+
|
|
484
|
+
assert(manifest.queued_for_compaction(block_reference.address));
|
|
485
|
+
manifest.remove(
|
|
486
|
+
manifest_log.tree_hash,
|
|
487
|
+
block_reference.checksum,
|
|
488
|
+
block_reference.address,
|
|
489
|
+
);
|
|
490
|
+
assert(!manifest.queued_for_compaction(block_reference.address));
|
|
491
|
+
|
|
492
|
+
manifest_log.superblock.free_set.release_at_checkpoint(block_reference.address);
|
|
493
|
+
|
|
494
|
+
const callback = manifest_log.read_callback;
|
|
495
|
+
manifest_log.reading = false;
|
|
496
|
+
manifest_log.read_callback = undefined;
|
|
497
|
+
manifest_log.read_block_reference = null;
|
|
498
|
+
|
|
499
|
+
callback(manifest_log);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
pub fn checkpoint(manifest_log: *ManifestLog, callback: Callback) void {
|
|
503
|
+
assert(manifest_log.opened);
|
|
504
|
+
assert(!manifest_log.reading);
|
|
505
|
+
assert(!manifest_log.writing);
|
|
506
|
+
|
|
507
|
+
manifest_log.writing = true;
|
|
508
|
+
manifest_log.write_callback = callback;
|
|
509
|
+
|
|
510
|
+
if (manifest_log.entry_count > 0) {
|
|
511
|
+
manifest_log.close_block();
|
|
512
|
+
assert(manifest_log.entry_count == 0);
|
|
513
|
+
assert(manifest_log.blocks_closed > 0);
|
|
514
|
+
assert(manifest_log.blocks_closed == manifest_log.blocks.count);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
log.debug("checkpoint: writing {} block(s)", .{manifest_log.blocks_closed});
|
|
518
|
+
manifest_log.write_block();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
fn acquire_block(manifest_log: *ManifestLog) void {
|
|
522
|
+
assert(manifest_log.entry_count == 0);
|
|
523
|
+
assert(!manifest_log.blocks.full());
|
|
524
|
+
|
|
525
|
+
manifest_log.blocks.advance_tail();
|
|
526
|
+
|
|
527
|
+
const block: BlockPtr = manifest_log.blocks.tail().?;
|
|
528
|
+
|
|
529
|
+
const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
|
|
530
|
+
header.* = .{
|
|
531
|
+
.cluster = manifest_log.superblock.working.cluster,
|
|
532
|
+
.op = manifest_log.superblock.free_set.acquire().?,
|
|
533
|
+
.size = undefined,
|
|
534
|
+
.command = .block,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
fn close_block(manifest_log: *ManifestLog) void {
|
|
539
|
+
const block: BlockPtr = manifest_log.blocks.tail().?;
|
|
540
|
+
|
|
541
|
+
const entry_count = manifest_log.entry_count;
|
|
542
|
+
assert(entry_count > 0);
|
|
543
|
+
assert(entry_count <= entry_count_max);
|
|
544
|
+
|
|
545
|
+
const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
|
|
546
|
+
assert(header.cluster == manifest_log.superblock.working.cluster);
|
|
547
|
+
assert(header.op > 0);
|
|
548
|
+
header.size = block_size(entry_count);
|
|
549
|
+
assert(header.command == .block);
|
|
550
|
+
|
|
551
|
+
// Zero unused labels:
|
|
552
|
+
mem.set(u8, mem.sliceAsBytes(labels(block)[entry_count..]), 0);
|
|
553
|
+
|
|
554
|
+
// Zero unused tables, and padding:
|
|
555
|
+
mem.set(u8, block[header.size..], 0);
|
|
556
|
+
|
|
557
|
+
header.set_checksum_body(block[@sizeOf(vsr.Header)..header.size]);
|
|
558
|
+
header.set_checksum();
|
|
559
|
+
|
|
560
|
+
verify_block(block, null, null);
|
|
561
|
+
assert(block_entry_count(block) == entry_count);
|
|
562
|
+
|
|
563
|
+
log.debug("{}: close_block: checksum={} address={} entries={}", .{
|
|
564
|
+
manifest_log.tree_hash,
|
|
565
|
+
header.checksum,
|
|
566
|
+
block_address(block),
|
|
567
|
+
entry_count,
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
manifest_log.blocks_closed += 1;
|
|
571
|
+
manifest_log.entry_count = 0;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
fn verify_block(block: BlockPtrConst, checksum: ?u128, address: ?u64) void {
|
|
575
|
+
const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
|
|
576
|
+
|
|
577
|
+
if (config.verify) {
|
|
578
|
+
assert(header.valid_checksum());
|
|
579
|
+
assert(header.valid_checksum_body(block[@sizeOf(vsr.Header)..header.size]));
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
assert(checksum == null or header.checksum == checksum.?);
|
|
583
|
+
|
|
584
|
+
assert(block_address(block) > 0);
|
|
585
|
+
assert(address == null or block_address(block) == address.?);
|
|
586
|
+
|
|
587
|
+
const entry_count = block_entry_count(block);
|
|
588
|
+
assert(entry_count > 0);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
fn block_address(block: BlockPtrConst) u64 {
|
|
592
|
+
const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
|
|
593
|
+
assert(header.command == .block);
|
|
594
|
+
|
|
595
|
+
const address = header.op;
|
|
596
|
+
assert(address > 0);
|
|
597
|
+
return address;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
fn block_checksum(block: BlockPtrConst) u128 {
|
|
601
|
+
const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
|
|
602
|
+
assert(header.command == .block);
|
|
603
|
+
|
|
604
|
+
return header.checksum;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
fn block_entry_count(block: BlockPtrConst) u32 {
|
|
608
|
+
const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
|
|
609
|
+
assert(header.command == .block);
|
|
610
|
+
|
|
611
|
+
const labels_size = entry_count_max * @sizeOf(Label);
|
|
612
|
+
const tables_size = header.size - @sizeOf(vsr.Header) - labels_size;
|
|
613
|
+
|
|
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;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
fn block_size(entry_count: u32) u32 {
|
|
621
|
+
assert(entry_count > 0);
|
|
622
|
+
assert(entry_count <= entry_count_max);
|
|
623
|
+
|
|
624
|
+
// Encode the smaller type first because this will be multiplied by entry_count_max.
|
|
625
|
+
const labels_size = entry_count_max * @sizeOf(Label);
|
|
626
|
+
const tables_size = entry_count * @sizeOf(TableInfo);
|
|
627
|
+
|
|
628
|
+
return @sizeOf(vsr.Header) + labels_size + tables_size;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const labels_size_max = entry_count_max * @sizeOf(Label);
|
|
632
|
+
|
|
633
|
+
fn labels(block: BlockPtr) *[entry_count_max]Label {
|
|
634
|
+
return mem.bytesAsSlice(
|
|
635
|
+
Label,
|
|
636
|
+
block[@sizeOf(vsr.Header)..][0..labels_size_max],
|
|
637
|
+
)[0..entry_count_max];
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
fn labels_const(block: BlockPtrConst) *const [entry_count_max]Label {
|
|
641
|
+
return mem.bytesAsSlice(
|
|
642
|
+
Label,
|
|
643
|
+
block[@sizeOf(vsr.Header)..][0..labels_size_max],
|
|
644
|
+
)[0..entry_count_max];
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const tables_size_max = entry_count_max * @sizeOf(TableInfo);
|
|
648
|
+
|
|
649
|
+
fn tables(block: BlockPtr) *[entry_count_max]TableInfo {
|
|
650
|
+
return mem.bytesAsSlice(
|
|
651
|
+
TableInfo,
|
|
652
|
+
block[@sizeOf(vsr.Header) + entry_count_max ..][0..tables_size_max],
|
|
653
|
+
)[0..entry_count_max];
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
fn tables_const(block: BlockPtrConst) *const [entry_count_max]TableInfo {
|
|
657
|
+
return mem.bytesAsSlice(
|
|
658
|
+
TableInfo,
|
|
659
|
+
block[@sizeOf(vsr.Header) + entry_count_max ..][0..tables_size_max],
|
|
660
|
+
)[0..entry_count_max];
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
}
|
|
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
|
+
}
|