tigerbeetle-node 0.11.5 → 0.11.7
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/dist/.client.node.sha256 +1 -1
- package/dist/index.d.ts +41 -42
- package/dist/index.js +41 -42
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +0 -1
- package/src/tigerbeetle/scripts/benchmark.bat +7 -3
- package/src/tigerbeetle/scripts/benchmark.sh +2 -3
- package/src/tigerbeetle/scripts/install.bat +7 -0
- package/src/tigerbeetle/scripts/install.sh +2 -3
- package/src/tigerbeetle/src/benchmark.zig +3 -3
- package/src/tigerbeetle/src/config.zig +24 -3
- package/src/tigerbeetle/src/constants.zig +8 -5
- package/src/tigerbeetle/src/ewah.zig +6 -5
- package/src/tigerbeetle/src/ewah_fuzz.zig +1 -1
- package/src/tigerbeetle/src/io/darwin.zig +19 -0
- package/src/tigerbeetle/src/io/linux.zig +8 -0
- package/src/tigerbeetle/src/io/windows.zig +20 -2
- package/src/tigerbeetle/src/iops.zig +7 -1
- package/src/tigerbeetle/src/lsm/compaction.zig +27 -72
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +10 -11
- package/src/tigerbeetle/src/lsm/grid.zig +267 -267
- package/src/tigerbeetle/src/lsm/groove.zig +3 -0
- package/src/tigerbeetle/src/lsm/level_iterator.zig +18 -1
- package/src/tigerbeetle/src/lsm/manifest.zig +29 -1
- package/src/tigerbeetle/src/lsm/manifest_level.zig +1 -0
- package/src/tigerbeetle/src/lsm/manifest_log.zig +5 -5
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +19 -11
- package/src/tigerbeetle/src/lsm/merge_iterator.zig +106 -0
- package/src/tigerbeetle/src/lsm/posted_groove.zig +1 -0
- package/src/tigerbeetle/src/lsm/segmented_array.zig +1 -0
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +26 -70
- package/src/tigerbeetle/src/lsm/table.zig +56 -0
- package/src/tigerbeetle/src/lsm/table_iterator.zig +29 -2
- package/src/tigerbeetle/src/lsm/table_mutable.zig +49 -15
- package/src/tigerbeetle/src/lsm/test.zig +10 -7
- package/src/tigerbeetle/src/lsm/tree.zig +27 -6
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +302 -263
- package/src/tigerbeetle/src/message_pool.zig +2 -1
- package/src/tigerbeetle/src/simulator.zig +22 -84
- package/src/tigerbeetle/src/{test/accounting → state_machine}/auditor.zig +8 -8
- package/src/tigerbeetle/src/{test/accounting → state_machine}/workload.zig +108 -48
- package/src/tigerbeetle/src/state_machine.zig +20 -14
- package/src/tigerbeetle/src/storage.zig +58 -6
- package/src/tigerbeetle/src/test/cluster.zig +14 -11
- package/src/tigerbeetle/src/test/conductor.zig +2 -3
- package/src/tigerbeetle/src/test/id.zig +10 -0
- package/src/tigerbeetle/src/test/state_checker.zig +1 -1
- package/src/tigerbeetle/src/test/state_machine.zig +151 -46
- package/src/tigerbeetle/src/test/storage.zig +22 -1
- package/src/tigerbeetle/src/tigerbeetle.zig +0 -1
- package/src/tigerbeetle/src/tracer.zig +50 -28
- package/src/tigerbeetle/src/unit_tests.zig +11 -6
- package/src/tigerbeetle/src/vopr.zig +4 -4
- package/src/tigerbeetle/src/vsr/client.zig +5 -5
- package/src/tigerbeetle/src/vsr/clock.zig +2 -2
- package/src/tigerbeetle/src/vsr/journal.zig +647 -537
- package/src/tigerbeetle/src/vsr/replica.zig +333 -333
- package/src/tigerbeetle/src/vsr/replica_format.zig +7 -4
- package/src/tigerbeetle/src/vsr/superblock.zig +87 -39
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +114 -93
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +1 -1
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +11 -8
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +3 -3
- package/src/tigerbeetle/src/vsr.zig +60 -13
- package/src/tigerbeetle/src/c/tb_client/context.zig +0 -304
- package/src/tigerbeetle/src/c/tb_client/echo_client.zig +0 -108
- package/src/tigerbeetle/src/c/tb_client/packet.zig +0 -80
- package/src/tigerbeetle/src/c/tb_client/signal.zig +0 -286
- package/src/tigerbeetle/src/c/tb_client/thread.zig +0 -88
- package/src/tigerbeetle/src/c/tb_client.h +0 -221
- package/src/tigerbeetle/src/c/tb_client.zig +0 -177
- package/src/tigerbeetle/src/c/tb_client_header.zig +0 -218
- package/src/tigerbeetle/src/c/tb_client_header_test.zig +0 -135
- package/src/tigerbeetle/src/c/test.zig +0 -371
- package/src/tigerbeetle/src/cli.zig +0 -375
- package/src/tigerbeetle/src/main.zig +0 -245
|
@@ -131,6 +131,7 @@ fn IndexTreeType(
|
|
|
131
131
|
Key.sentinel_key,
|
|
132
132
|
Key.tombstone,
|
|
133
133
|
Key.tombstone_from_key,
|
|
134
|
+
.secondary_index,
|
|
134
135
|
);
|
|
135
136
|
|
|
136
137
|
return TreeType(Table, Storage, tree_name);
|
|
@@ -254,6 +255,7 @@ pub fn GrooveType(
|
|
|
254
255
|
ObjectTreeHelpers(Object).sentinel_key,
|
|
255
256
|
ObjectTreeHelpers(Object).tombstone,
|
|
256
257
|
ObjectTreeHelpers(Object).tombstone_from_key,
|
|
258
|
+
.general,
|
|
257
259
|
);
|
|
258
260
|
|
|
259
261
|
const tree_name = @typeName(Object);
|
|
@@ -269,6 +271,7 @@ pub fn GrooveType(
|
|
|
269
271
|
IdTreeValue.sentinel_key,
|
|
270
272
|
IdTreeValue.tombstone,
|
|
271
273
|
IdTreeValue.tombstone_from_key,
|
|
274
|
+
.general,
|
|
272
275
|
);
|
|
273
276
|
|
|
274
277
|
const tree_name = @typeName(Object) ++ ".id";
|
|
@@ -68,6 +68,9 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
68
68
|
values: ValuesRingBuffer,
|
|
69
69
|
tables: TablesRingBuffer,
|
|
70
70
|
|
|
71
|
+
// Used for verifying key order when constants.verify == true.
|
|
72
|
+
key_prev: ?Key,
|
|
73
|
+
|
|
71
74
|
pub fn init(allocator: mem.Allocator) !LevelIterator {
|
|
72
75
|
var values = try ValuesRingBuffer.init(allocator);
|
|
73
76
|
errdefer values.deinit(allocator);
|
|
@@ -96,6 +99,7 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
96
99
|
.{ .table_iterator = table_b },
|
|
97
100
|
},
|
|
98
101
|
},
|
|
102
|
+
.key_prev = null,
|
|
99
103
|
};
|
|
100
104
|
}
|
|
101
105
|
|
|
@@ -138,6 +142,7 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
138
142
|
.direction = context.direction,
|
|
139
143
|
.values = .{ .buffer = it.values.buffer },
|
|
140
144
|
.tables = .{ .buffer = it.tables.buffer },
|
|
145
|
+
.key_prev = null,
|
|
141
146
|
};
|
|
142
147
|
|
|
143
148
|
assert(it.key_exclusive == null);
|
|
@@ -281,7 +286,7 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
281
286
|
/// Returns either:
|
|
282
287
|
/// - the next Key, if available.
|
|
283
288
|
/// - error.Empty when there are no values remaining to iterate.
|
|
284
|
-
/// - error.Drained when the iterator isn't empty, but the values
|
|
289
|
+
/// - error.Drained when the iterator isn't empty, but the values
|
|
285
290
|
/// still need to be buffered into memory via tick().
|
|
286
291
|
pub fn peek(it: LevelIterator) error{ Empty, Drained }!Key {
|
|
287
292
|
if (it.values.head_ptr_const()) |value| return key_from_value(value);
|
|
@@ -299,6 +304,18 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
299
304
|
|
|
300
305
|
/// This may only be called after peek() returns a Key (and not Empty or Drained)
|
|
301
306
|
pub fn pop(it: *LevelIterator) Value {
|
|
307
|
+
const value = it.pop_internal();
|
|
308
|
+
|
|
309
|
+
if (constants.verify) {
|
|
310
|
+
const key = Table.key_from_value(&value);
|
|
311
|
+
if (it.key_prev) |k| assert(Table.compare_keys(k, key) == .lt);
|
|
312
|
+
it.key_prev = key;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
fn pop_internal(it: *LevelIterator) Value {
|
|
302
319
|
if (it.values.pop()) |value| return value;
|
|
303
320
|
|
|
304
321
|
const table_iterator = &it.tables.head_ptr().?.table_iterator;
|
|
@@ -531,7 +531,7 @@ pub fn ManifestType(comptime Table: type, comptime Storage: type) type {
|
|
|
531
531
|
.ascending,
|
|
532
532
|
KeyRange{ .key_min = range.key_min, .key_max = range.key_max },
|
|
533
533
|
);
|
|
534
|
-
if (it.next()
|
|
534
|
+
if (it.next() != null) {
|
|
535
535
|
// If the range is being compacted into the last level then this is unreachable,
|
|
536
536
|
// as the last level has no subsequent levels and must always drop tombstones.
|
|
537
537
|
assert(level_b != constants.lsm_levels - 1);
|
|
@@ -585,5 +585,33 @@ pub fn ManifestType(comptime Table: type, comptime Storage: type) type {
|
|
|
585
585
|
manifest.checkpoint_callback = null;
|
|
586
586
|
callback(manifest);
|
|
587
587
|
}
|
|
588
|
+
|
|
589
|
+
pub fn verify(manifest: *Manifest, snapshot: u64) void {
|
|
590
|
+
for (manifest.levels) |*level| {
|
|
591
|
+
var key_max_prev: ?Key = null;
|
|
592
|
+
var table_info_iter = level.iterator(
|
|
593
|
+
.visible,
|
|
594
|
+
&.{snapshot},
|
|
595
|
+
.ascending,
|
|
596
|
+
null,
|
|
597
|
+
);
|
|
598
|
+
while (table_info_iter.next()) |table_info| {
|
|
599
|
+
if (key_max_prev) |k| {
|
|
600
|
+
assert(compare_keys(k, table_info.key_min) == .lt);
|
|
601
|
+
}
|
|
602
|
+
// We could have key_min == key_max if there is only one value.
|
|
603
|
+
assert(compare_keys(table_info.key_min, table_info.key_max) != .gt);
|
|
604
|
+
key_max_prev = table_info.key_max;
|
|
605
|
+
|
|
606
|
+
Table.verify(
|
|
607
|
+
Storage,
|
|
608
|
+
manifest.manifest_log.grid.superblock.storage,
|
|
609
|
+
table_info.address,
|
|
610
|
+
table_info.key_min,
|
|
611
|
+
table_info.key_max,
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
588
616
|
};
|
|
589
617
|
}
|
|
@@ -404,14 +404,14 @@ pub fn ManifestLogType(comptime Storage: type, comptime TableInfo: type) type {
|
|
|
404
404
|
return;
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
-
const block = manifest_log.blocks.
|
|
408
|
-
verify_block(block
|
|
407
|
+
const block = manifest_log.blocks.head_ptr().?;
|
|
408
|
+
verify_block(block.*, null, null);
|
|
409
409
|
|
|
410
|
-
const header = mem.bytesAsValue(vsr.Header, block[0..@sizeOf(vsr.Header)]);
|
|
411
|
-
const address = Block.address(block);
|
|
410
|
+
const header = mem.bytesAsValue(vsr.Header, block.*[0..@sizeOf(vsr.Header)]);
|
|
411
|
+
const address = Block.address(block.*);
|
|
412
412
|
assert(address > 0);
|
|
413
413
|
|
|
414
|
-
const entry_count = Block.entry_count(block);
|
|
414
|
+
const entry_count = Block.entry_count(block.*);
|
|
415
415
|
|
|
416
416
|
if (manifest_log.blocks_closed == 1 and manifest_log.blocks.count == 1) {
|
|
417
417
|
// This might be the last block of a checkpoint, which can be a partial block.
|
|
@@ -30,8 +30,6 @@ const fuzz = @import("../test/fuzz.zig");
|
|
|
30
30
|
|
|
31
31
|
pub const tigerbeetle_config = @import("../config.zig").configs.test_min;
|
|
32
32
|
|
|
33
|
-
const storage_size_max = data_file_size_min + constants.block_size * 1024;
|
|
34
|
-
|
|
35
33
|
const entries_max_block = ManifestLog.Block.entry_count_max;
|
|
36
34
|
const entries_max_buffered = entries_max_block *
|
|
37
35
|
std.meta.fieldInfo(ManifestLog, .blocks).field_type.count_max;
|
|
@@ -66,20 +64,28 @@ fn run_fuzz(
|
|
|
66
64
|
.write_latency_mean = 1 + random.uintLessThan(u64, 40),
|
|
67
65
|
};
|
|
68
66
|
|
|
69
|
-
var storage = try Storage.init(allocator, storage_size_max, storage_options);
|
|
67
|
+
var storage = try Storage.init(allocator, constants.storage_size_max, storage_options);
|
|
70
68
|
defer storage.deinit(allocator);
|
|
71
69
|
|
|
72
|
-
var storage_verify = try Storage.init(allocator, storage_size_max, storage_options);
|
|
70
|
+
var storage_verify = try Storage.init(allocator, constants.storage_size_max, storage_options);
|
|
73
71
|
defer storage_verify.deinit(allocator);
|
|
74
72
|
|
|
75
73
|
// The MessagePool is shared by both superblocks because they will not use it.
|
|
76
74
|
var message_pool = try MessagePool.init(allocator, .replica);
|
|
77
75
|
defer message_pool.deinit(allocator);
|
|
78
76
|
|
|
79
|
-
var superblock = try SuperBlock.init(allocator,
|
|
77
|
+
var superblock = try SuperBlock.init(allocator, .{
|
|
78
|
+
.storage = &storage,
|
|
79
|
+
.storage_size_limit = constants.storage_size_max,
|
|
80
|
+
.message_pool = &message_pool,
|
|
81
|
+
});
|
|
80
82
|
defer superblock.deinit(allocator);
|
|
81
83
|
|
|
82
|
-
var superblock_verify = try SuperBlock.init(allocator,
|
|
84
|
+
var superblock_verify = try SuperBlock.init(allocator, .{
|
|
85
|
+
.storage = &storage_verify,
|
|
86
|
+
.storage_size_limit = constants.storage_size_max,
|
|
87
|
+
.message_pool = &message_pool,
|
|
88
|
+
});
|
|
83
89
|
defer superblock_verify.deinit(allocator);
|
|
84
90
|
|
|
85
91
|
var grid = try Grid.init(allocator, &superblock);
|
|
@@ -313,7 +319,7 @@ const Environment = struct {
|
|
|
313
319
|
|
|
314
320
|
fn wait(env: *Environment, manifest_log: *ManifestLog) void {
|
|
315
321
|
while (env.pending > 0) {
|
|
316
|
-
manifest_log.grid.tick();
|
|
322
|
+
// manifest_log.grid.tick();
|
|
317
323
|
manifest_log.superblock.storage.tick();
|
|
318
324
|
}
|
|
319
325
|
}
|
|
@@ -324,7 +330,6 @@ const Environment = struct {
|
|
|
324
330
|
env.manifest_log.superblock.format(format_superblock_callback, &env.superblock_context, .{
|
|
325
331
|
.cluster = 0,
|
|
326
332
|
.replica = 0,
|
|
327
|
-
.size_max = storage_size_max,
|
|
328
333
|
});
|
|
329
334
|
}
|
|
330
335
|
|
|
@@ -453,8 +458,11 @@ const Environment = struct {
|
|
|
453
458
|
test_superblock.deinit(env.allocator);
|
|
454
459
|
test_superblock.* = try SuperBlock.init(
|
|
455
460
|
env.allocator,
|
|
456
|
-
|
|
457
|
-
|
|
461
|
+
.{
|
|
462
|
+
.storage = test_storage,
|
|
463
|
+
.storage_size_limit = constants.storage_size_max,
|
|
464
|
+
.message_pool = env.manifest_log.superblock.client_table.message_pool,
|
|
465
|
+
},
|
|
458
466
|
);
|
|
459
467
|
|
|
460
468
|
test_grid.deinit(env.allocator);
|
|
@@ -627,7 +635,7 @@ fn verify_manifest_compaction_set(
|
|
|
627
635
|
var compact_blocks_checked: u32 = 0;
|
|
628
636
|
|
|
629
637
|
// This test doesn't include any actual table blocks, so all blocks are manifest blocks.
|
|
630
|
-
var blocks = superblock.free_set.blocks.iterator(.{ .kind = .
|
|
638
|
+
var blocks = superblock.free_set.blocks.iterator(.{ .kind = .set });
|
|
631
639
|
while (blocks.next()) |block_index| {
|
|
632
640
|
const block_address = block_index + 1;
|
|
633
641
|
const block = superblock.storage.grid_block(block_address);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const assert = std.debug.assert;
|
|
3
|
+
|
|
4
|
+
const util = @import("../util.zig");
|
|
5
|
+
|
|
6
|
+
const k_max = 2;
|
|
7
|
+
|
|
8
|
+
pub fn MergeIteratorType(
|
|
9
|
+
comptime Table: type,
|
|
10
|
+
comptime IteratorA: type,
|
|
11
|
+
comptime IteratorB: type,
|
|
12
|
+
) type {
|
|
13
|
+
return struct {
|
|
14
|
+
const Self = @This();
|
|
15
|
+
|
|
16
|
+
iterator_a: *IteratorA,
|
|
17
|
+
iterator_b: *IteratorB,
|
|
18
|
+
|
|
19
|
+
empty_a: bool,
|
|
20
|
+
empty_b: bool,
|
|
21
|
+
|
|
22
|
+
previous_key_popped: ?Table.Key,
|
|
23
|
+
|
|
24
|
+
pub fn init(
|
|
25
|
+
iterator_a: *IteratorA,
|
|
26
|
+
iterator_b: *IteratorB,
|
|
27
|
+
) Self {
|
|
28
|
+
return Self{
|
|
29
|
+
.iterator_a = iterator_a,
|
|
30
|
+
.iterator_b = iterator_b,
|
|
31
|
+
.empty_a = false,
|
|
32
|
+
.empty_b = false,
|
|
33
|
+
.previous_key_popped = null,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Returns `true` once both `iterator_a` and `iterator_b` have raised `error.Empty`.
|
|
38
|
+
pub fn empty(it: Self) bool {
|
|
39
|
+
return it.empty_a and it.empty_b;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Returns `null` if either `iterator_a` or `iterator_b` return `error.Empty` or `error.Drained`.
|
|
43
|
+
/// Check `it.empty()` to disambiguate.
|
|
44
|
+
pub fn pop(it: *Self) ?Table.Value {
|
|
45
|
+
while (true) {
|
|
46
|
+
if (it.empty_a and it.empty_b) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (it.empty_a) {
|
|
51
|
+
_ = it.iterator_b.peek() catch |error_b| switch (error_b) {
|
|
52
|
+
error.Drained => return null,
|
|
53
|
+
error.Empty => {
|
|
54
|
+
it.empty_b = true;
|
|
55
|
+
continue;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
return it.iterator_b.pop();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (it.empty_b) {
|
|
62
|
+
_ = it.iterator_a.peek() catch |error_a| switch (error_a) {
|
|
63
|
+
error.Drained => return null,
|
|
64
|
+
error.Empty => {
|
|
65
|
+
it.empty_a = true;
|
|
66
|
+
continue;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
return it.iterator_a.pop();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const key_a = it.iterator_a.peek() catch |error_a| switch (error_a) {
|
|
73
|
+
error.Drained => return null,
|
|
74
|
+
error.Empty => {
|
|
75
|
+
it.empty_a = true;
|
|
76
|
+
continue;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
const key_b = it.iterator_b.peek() catch |error_b| switch (error_b) {
|
|
80
|
+
error.Drained => return null,
|
|
81
|
+
error.Empty => {
|
|
82
|
+
it.empty_b = true;
|
|
83
|
+
continue;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
switch (Table.compare_keys(key_a, key_b)) {
|
|
88
|
+
.lt => return it.iterator_a.pop(),
|
|
89
|
+
.gt => return it.iterator_b.pop(),
|
|
90
|
+
.eq => {
|
|
91
|
+
const value_a = it.iterator_a.pop();
|
|
92
|
+
const value_b = it.iterator_b.pop();
|
|
93
|
+
switch (Table.usage) {
|
|
94
|
+
.general => return value_a,
|
|
95
|
+
.secondary_index => {
|
|
96
|
+
// In secondary indexes, puts and removes alternate and can be safely cancelled out.
|
|
97
|
+
assert(Table.tombstone(&value_a) != Table.tombstone(&value_b));
|
|
98
|
+
continue;
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -188,21 +188,26 @@ pub fn SetAssociativeCache(
|
|
|
188
188
|
mem.set(u64, self.clocks.words, 0);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
/// Returns whether an entry with the given key is cached,
|
|
191
|
+
/// Returns whether an entry with the given key is cached,
|
|
192
192
|
/// without modifying the entry's counter.
|
|
193
193
|
pub fn exists(self: *Self, key: Key) bool {
|
|
194
194
|
const set = self.associate(key);
|
|
195
195
|
return self.search(set, key) != null;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
pub fn
|
|
198
|
+
pub fn get_index(self: *Self, key: Key) ?usize {
|
|
199
199
|
const set = self.associate(key);
|
|
200
200
|
const way = self.search(set, key) orelse return null;
|
|
201
201
|
|
|
202
202
|
const count = self.counts.get(set.offset + way);
|
|
203
203
|
self.counts.set(set.offset + way, count +| 1);
|
|
204
204
|
|
|
205
|
-
return
|
|
205
|
+
return set.offset + way;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
pub fn get(self: *Self, key: Key) ?*align(value_alignment) Value {
|
|
209
|
+
const index = self.get_index(key) orelse return null;
|
|
210
|
+
return @alignCast(value_alignment, &self.values[index]);
|
|
206
211
|
}
|
|
207
212
|
|
|
208
213
|
/// Remove a key from the set associative cache if present.
|
|
@@ -211,6 +216,7 @@ pub fn SetAssociativeCache(
|
|
|
211
216
|
const way = self.search(set, key) orelse return;
|
|
212
217
|
|
|
213
218
|
self.counts.set(set.offset + way, 0);
|
|
219
|
+
set.values[way] = undefined;
|
|
214
220
|
}
|
|
215
221
|
|
|
216
222
|
/// Hint that the key is less likely to be accessed in the future, without actually removing
|
|
@@ -248,39 +254,21 @@ pub fn SetAssociativeCache(
|
|
|
248
254
|
return @ptrCast(*const Ways, &result).*;
|
|
249
255
|
}
|
|
250
256
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
struct {
|
|
255
|
-
inline fn locked(_: void, _: *const Value) bool {
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
}.locked,
|
|
259
|
-
{},
|
|
260
|
-
key,
|
|
261
|
-
);
|
|
257
|
+
/// Insert a value, evicting an older entry if needed.
|
|
258
|
+
pub fn insert(self: *Self, value: *const Value) void {
|
|
259
|
+
_ = self.insert_index(value);
|
|
262
260
|
}
|
|
263
261
|
|
|
264
|
-
///
|
|
265
|
-
///
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
pub fn insert_preserve_locked(
|
|
269
|
-
self: *Self,
|
|
270
|
-
comptime Context: type,
|
|
271
|
-
comptime locked: fn (
|
|
272
|
-
Context,
|
|
273
|
-
*align(value_alignment) const Value,
|
|
274
|
-
) callconv(.Inline) bool,
|
|
275
|
-
context: Context,
|
|
276
|
-
key: Key,
|
|
277
|
-
) *align(value_alignment) Value {
|
|
262
|
+
/// Insert a value, evicting an older entry if needed.
|
|
263
|
+
/// Return the index at which the value was inserted.
|
|
264
|
+
pub fn insert_index(self: *Self, value: *const Value) usize {
|
|
265
|
+
const key = key_from_value(value);
|
|
278
266
|
const set = self.associate(key);
|
|
279
267
|
if (self.search(set, key)) |way| {
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
268
|
+
// Overwrite the old entry for this key.
|
|
269
|
+
self.counts.set(set.offset + way, 1);
|
|
270
|
+
set.values[way] = value.*;
|
|
271
|
+
return set.offset + way;
|
|
284
272
|
}
|
|
285
273
|
|
|
286
274
|
const clock_index = @divExact(set.offset, layout.ways);
|
|
@@ -299,11 +287,6 @@ pub fn SetAssociativeCache(
|
|
|
299
287
|
safety_count += 1;
|
|
300
288
|
way +%= 1;
|
|
301
289
|
}) {
|
|
302
|
-
// We pass a value pointer to the callback here so that a cache miss
|
|
303
|
-
// can be avoided if the caller is able to determine if the value is
|
|
304
|
-
// locked by comparing pointers directly.
|
|
305
|
-
if (locked(context, @alignCast(value_alignment, &set.values[way]))) continue;
|
|
306
|
-
|
|
307
290
|
var count = self.counts.get(set.offset + way);
|
|
308
291
|
if (count == 0) break; // Way is already free.
|
|
309
292
|
|
|
@@ -316,10 +299,11 @@ pub fn SetAssociativeCache(
|
|
|
316
299
|
assert(self.counts.get(set.offset + way) == 0);
|
|
317
300
|
|
|
318
301
|
set.tags[way] = set.tag;
|
|
302
|
+
set.values[way] = value.*;
|
|
319
303
|
self.counts.set(set.offset + way, 1);
|
|
320
304
|
self.clocks.set(clock_index, way +% 1);
|
|
321
305
|
|
|
322
|
-
return
|
|
306
|
+
return set.offset + way;
|
|
323
307
|
}
|
|
324
308
|
|
|
325
309
|
const Set = struct {
|
|
@@ -430,7 +414,7 @@ fn set_associative_cache_test(
|
|
|
430
414
|
try expectEqual(i, sac.clocks.get(0));
|
|
431
415
|
|
|
432
416
|
const key = i * sac.sets;
|
|
433
|
-
sac.insert(key)
|
|
417
|
+
sac.insert(&key);
|
|
434
418
|
try expect(sac.counts.get(i) == 1);
|
|
435
419
|
try expectEqual(key, sac.get(key).?.*);
|
|
436
420
|
try expect(sac.counts.get(i) == 2);
|
|
@@ -443,7 +427,7 @@ fn set_associative_cache_test(
|
|
|
443
427
|
// Insert another element into the first set, causing key 0 to be evicted.
|
|
444
428
|
{
|
|
445
429
|
const key = layout.ways * sac.sets;
|
|
446
|
-
sac.insert(key)
|
|
430
|
+
sac.insert(&key);
|
|
447
431
|
try expect(sac.counts.get(0) == 1);
|
|
448
432
|
try expectEqual(key, sac.get(key).?.*);
|
|
449
433
|
try expect(sac.counts.get(0) == 2);
|
|
@@ -460,34 +444,6 @@ fn set_associative_cache_test(
|
|
|
460
444
|
|
|
461
445
|
if (log) sac.associate(0).inspect(sac);
|
|
462
446
|
|
|
463
|
-
// Lock all other slots, causing key layout.ways * sac.sets to be evicted despite having the
|
|
464
|
-
// highest count.
|
|
465
|
-
{
|
|
466
|
-
{
|
|
467
|
-
assert(sac.counts.get(0) == 2);
|
|
468
|
-
var i: usize = 1;
|
|
469
|
-
while (i < layout.ways) : (i += 1) assert(sac.counts.get(i) == 1);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const key = (layout.ways + 1) * sac.sets;
|
|
473
|
-
const expect_evicted = layout.ways * sac.sets;
|
|
474
|
-
|
|
475
|
-
sac.insert_preserve_locked(
|
|
476
|
-
u64,
|
|
477
|
-
struct {
|
|
478
|
-
inline fn locked(only_unlocked: u64, value: *const Value) bool {
|
|
479
|
-
return value.* != only_unlocked;
|
|
480
|
-
}
|
|
481
|
-
}.locked,
|
|
482
|
-
expect_evicted,
|
|
483
|
-
key,
|
|
484
|
-
).* = key;
|
|
485
|
-
|
|
486
|
-
try expectEqual(@as(?*Value, null), sac.get(expect_evicted));
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (log) sac.associate(0).inspect(sac);
|
|
490
|
-
|
|
491
447
|
// Ensure removal works.
|
|
492
448
|
{
|
|
493
449
|
const key = 5 * sac.sets;
|
|
@@ -512,7 +468,7 @@ fn set_associative_cache_test(
|
|
|
512
468
|
try expectEqual(i, sac.clocks.get(0));
|
|
513
469
|
|
|
514
470
|
const key = i * sac.sets;
|
|
515
|
-
sac.insert(key)
|
|
471
|
+
sac.insert(&key);
|
|
516
472
|
try expect(sac.counts.get(i) == 1);
|
|
517
473
|
var j: usize = 2;
|
|
518
474
|
while (j <= math.maxInt(SAC.Count)) : (j += 1) {
|
|
@@ -530,7 +486,7 @@ fn set_associative_cache_test(
|
|
|
530
486
|
// Insert another element into the first set, causing key 0 to be evicted.
|
|
531
487
|
{
|
|
532
488
|
const key = layout.ways * sac.sets;
|
|
533
|
-
sac.insert(key)
|
|
489
|
+
sac.insert(&key);
|
|
534
490
|
try expect(sac.counts.get(0) == 1);
|
|
535
491
|
try expectEqual(key, sac.get(key).?.*);
|
|
536
492
|
try expect(sac.counts.get(0) == 2);
|
|
@@ -16,6 +16,17 @@ const snapshot_latest = @import("tree.zig").snapshot_latest;
|
|
|
16
16
|
const BlockType = @import("grid.zig").BlockType;
|
|
17
17
|
const TableInfoType = @import("manifest.zig").TableInfoType;
|
|
18
18
|
|
|
19
|
+
pub const TableUsage = enum {
|
|
20
|
+
/// General purpose table.
|
|
21
|
+
general,
|
|
22
|
+
/// If your usage fits this pattern:
|
|
23
|
+
/// * Only put keys which are not present.
|
|
24
|
+
/// * Only remove keys which are present.
|
|
25
|
+
/// * TableKey == TableValue (modulo padding, eg CompositeKey)
|
|
26
|
+
/// Then we can unlock additional optimizations.
|
|
27
|
+
secondary_index,
|
|
28
|
+
};
|
|
29
|
+
|
|
19
30
|
/// A table is a set of blocks:
|
|
20
31
|
///
|
|
21
32
|
/// * Index block (exactly 1)
|
|
@@ -69,6 +80,7 @@ pub fn TableType(
|
|
|
69
80
|
comptime table_tombstone: fn (*const TableValue) callconv(.Inline) bool,
|
|
70
81
|
/// Returns a tombstone value representation for a key.
|
|
71
82
|
comptime table_tombstone_from_key: fn (TableKey) callconv(.Inline) TableValue,
|
|
83
|
+
comptime usage: TableUsage,
|
|
72
84
|
) type {
|
|
73
85
|
return struct {
|
|
74
86
|
const Table = @This();
|
|
@@ -81,6 +93,7 @@ pub fn TableType(
|
|
|
81
93
|
pub const sentinel_key = table_sentinel_key;
|
|
82
94
|
pub const tombstone = table_tombstone;
|
|
83
95
|
pub const tombstone_from_key = table_tombstone_from_key;
|
|
96
|
+
pub const usage = usage;
|
|
84
97
|
|
|
85
98
|
// Export hashmap context for Key and Value
|
|
86
99
|
pub const HashMapContextValue = struct {
|
|
@@ -616,6 +629,11 @@ pub fn TableType(
|
|
|
616
629
|
assert(compare_keys(builder.key_min, builder.key_max) == .lt);
|
|
617
630
|
}
|
|
618
631
|
|
|
632
|
+
if (current > 0) {
|
|
633
|
+
const key_max_prev = index_data_keys(builder.index_block)[current - 1];
|
|
634
|
+
assert(compare_keys(key_max_prev, key_from_value(&values[0])) == .lt);
|
|
635
|
+
}
|
|
636
|
+
|
|
619
637
|
builder.data_block_count += 1;
|
|
620
638
|
builder.value = 0;
|
|
621
639
|
|
|
@@ -966,6 +984,43 @@ pub fn TableType(
|
|
|
966
984
|
|
|
967
985
|
return null;
|
|
968
986
|
}
|
|
987
|
+
|
|
988
|
+
pub fn verify(
|
|
989
|
+
comptime Storage: type,
|
|
990
|
+
storage: *Storage,
|
|
991
|
+
index_address: u64,
|
|
992
|
+
key_min: ?Key,
|
|
993
|
+
key_max: ?Key,
|
|
994
|
+
) void {
|
|
995
|
+
if (Storage != @import("../test/storage.zig").Storage)
|
|
996
|
+
// Too complicated to do async verification
|
|
997
|
+
return;
|
|
998
|
+
|
|
999
|
+
const index_block = storage.grid_block(index_address);
|
|
1000
|
+
const addresses = index_data_addresses(index_block);
|
|
1001
|
+
const data_blocks_used = index_data_blocks_used(index_block);
|
|
1002
|
+
var data_block_index: usize = 0;
|
|
1003
|
+
while (data_block_index < data_blocks_used) : (data_block_index += 1) {
|
|
1004
|
+
const address = addresses[data_block_index];
|
|
1005
|
+
const data_block = storage.grid_block(address);
|
|
1006
|
+
const values = data_block_values_used(data_block);
|
|
1007
|
+
if (values.len > 0) {
|
|
1008
|
+
if (data_block_index == 0) {
|
|
1009
|
+
assert(key_min == null or
|
|
1010
|
+
compare_keys(key_min.?, key_from_value(&values[0])) == .eq);
|
|
1011
|
+
}
|
|
1012
|
+
if (data_block_index == data_blocks_used - 1) {
|
|
1013
|
+
assert(key_max == null or
|
|
1014
|
+
compare_keys(key_from_value(&values[values.len - 1]), key_max.?) == .eq);
|
|
1015
|
+
}
|
|
1016
|
+
var a = &values[0];
|
|
1017
|
+
for (values[1..]) |*b| {
|
|
1018
|
+
assert(compare_keys(key_from_value(a), key_from_value(b)) == .lt);
|
|
1019
|
+
a = b;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
969
1024
|
};
|
|
970
1025
|
}
|
|
971
1026
|
|
|
@@ -980,6 +1035,7 @@ test "Table" {
|
|
|
980
1035
|
Key.sentinel_key,
|
|
981
1036
|
Key.tombstone,
|
|
982
1037
|
Key.tombstone_from_key,
|
|
1038
|
+
.general,
|
|
983
1039
|
);
|
|
984
1040
|
|
|
985
1041
|
_ = Table;
|