tigerbeetle-node 0.11.12 → 0.11.13
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 +212 -196
- package/dist/.client.node.sha256 +1 -1
- package/package.json +3 -2
- package/src/node.zig +1 -0
- package/src/tigerbeetle/scripts/benchmark.bat +9 -2
- package/src/tigerbeetle/scripts/benchmark.sh +1 -1
- package/src/tigerbeetle/scripts/fail_on_diff.sh +9 -0
- package/src/tigerbeetle/scripts/fuzz_loop_hash_log.sh +12 -0
- package/src/tigerbeetle/scripts/scripts/benchmark.bat +9 -2
- package/src/tigerbeetle/scripts/scripts/benchmark.sh +1 -1
- package/src/tigerbeetle/scripts/scripts/fail_on_diff.sh +9 -0
- package/src/tigerbeetle/scripts/scripts/fuzz_loop_hash_log.sh +12 -0
- package/src/tigerbeetle/src/benchmark.zig +253 -231
- package/src/tigerbeetle/src/config.zig +2 -3
- package/src/tigerbeetle/src/constants.zig +2 -10
- package/src/tigerbeetle/src/io/linux.zig +15 -6
- package/src/tigerbeetle/src/lsm/forest.zig +1 -0
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +63 -14
- package/src/tigerbeetle/src/lsm/groove.zig +134 -70
- package/src/tigerbeetle/src/lsm/level_iterator.zig +2 -2
- package/src/tigerbeetle/src/lsm/manifest_level.zig +1 -0
- package/src/tigerbeetle/src/lsm/posted_groove.zig +7 -4
- package/src/tigerbeetle/src/lsm/segmented_array.zig +1 -0
- package/src/tigerbeetle/src/lsm/table.zig +29 -51
- package/src/tigerbeetle/src/lsm/table_immutable.zig +6 -17
- package/src/tigerbeetle/src/lsm/table_iterator.zig +2 -2
- package/src/tigerbeetle/src/lsm/table_mutable.zig +9 -26
- package/src/tigerbeetle/src/lsm/test.zig +1 -0
- package/src/tigerbeetle/src/lsm/tree.zig +2 -26
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +7 -2
- package/src/tigerbeetle/src/message_bus.zig +1 -0
- package/src/tigerbeetle/src/simulator.zig +14 -3
- package/src/tigerbeetle/src/state_machine/auditor.zig +1 -0
- package/src/tigerbeetle/src/state_machine.zig +402 -184
- package/src/tigerbeetle/src/stdx.zig +9 -0
- package/src/tigerbeetle/src/testing/cluster.zig +1 -0
- package/src/tigerbeetle/src/testing/packet_simulator.zig +19 -9
- package/src/tigerbeetle/src/testing/state_machine.zig +1 -0
- package/src/tigerbeetle/src/unit_tests.zig +20 -22
- package/src/tigerbeetle/src/vsr/README.md +1 -1
- package/src/tigerbeetle/src/vsr/client.zig +4 -4
- package/src/tigerbeetle/src/vsr/clock.zig +2 -0
- package/src/tigerbeetle/src/vsr/journal.zig +2 -0
- package/src/tigerbeetle/src/vsr/replica.zig +481 -246
- package/src/tigerbeetle/src/vsr.zig +104 -31
|
@@ -100,6 +100,17 @@ pub const IO = struct {
|
|
|
100
100
|
try self.flush_submissions(wait_nr, timeouts, etime);
|
|
101
101
|
// We can now just peek for any CQEs without waiting and without another syscall:
|
|
102
102
|
try self.flush_completions(0, timeouts, etime);
|
|
103
|
+
|
|
104
|
+
// The SQE array is empty from flush_submissions(). Fill it up with unqueued completions.
|
|
105
|
+
// This runs before `self.completed` is flushed below to prevent new IO from reserving SQE
|
|
106
|
+
// slots and potentially starving those in `self.unqueued`.
|
|
107
|
+
// Loop over a copy to avoid an infinite loop of `enqueue()` re-adding to `self.unqueued`.
|
|
108
|
+
{
|
|
109
|
+
var copy = self.unqueued;
|
|
110
|
+
self.unqueued = .{};
|
|
111
|
+
while (copy.pop()) |completion| self.enqueue(completion);
|
|
112
|
+
}
|
|
113
|
+
|
|
103
114
|
// Run completions only after all completions have been flushed:
|
|
104
115
|
// Loop on a copy of the linked list, having reset the list first, so that any synchronous
|
|
105
116
|
// append on running a completion is executed only the next time round the event loop,
|
|
@@ -109,12 +120,10 @@ pub const IO = struct {
|
|
|
109
120
|
self.completed = .{};
|
|
110
121
|
while (copy.pop()) |completion| completion.complete();
|
|
111
122
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
while (copy.pop()) |completion| self.enqueue(completion);
|
|
117
|
-
}
|
|
123
|
+
|
|
124
|
+
// At this point, unqueued could have completions either by 1) those who didn't get an SQE
|
|
125
|
+
// during the popping of unqueued or 2) completion.complete() which start new IO. These
|
|
126
|
+
// unqueued completions will get priority to acquiring SQEs on the next flush().
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
fn flush_completions(self: *IO, wait_nr: u32, timeouts: *usize, etime: *bool) !void {
|
|
@@ -16,6 +16,7 @@ const Account = @import("../tigerbeetle.zig").Account;
|
|
|
16
16
|
const Storage = @import("../testing/storage.zig").Storage;
|
|
17
17
|
const StateMachine = @import("../state_machine.zig").StateMachineType(Storage, .{
|
|
18
18
|
.message_body_size_max = constants.message_body_size_max,
|
|
19
|
+
.lsm_batch_multiple = constants.lsm_batch_multiple,
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
const GridType = @import("grid.zig").GridType;
|
|
@@ -52,8 +53,13 @@ const Environment = struct {
|
|
|
52
53
|
.cache_entries_posted = cache_entries_max,
|
|
53
54
|
});
|
|
54
55
|
|
|
55
|
-
//
|
|
56
|
-
|
|
56
|
+
// We must call compact after every 'batch'.
|
|
57
|
+
// Every `lsm_batch_multiple` batches may put/remove `value_count_max` values per index.
|
|
58
|
+
// Every `FuzzOp.put_account` issues one remove and one put per index.
|
|
59
|
+
const puts_since_compact_max = @divTrunc(
|
|
60
|
+
Forest.groove_config.accounts_mutable.ObjectTree.Table.value_count_max,
|
|
61
|
+
2 * constants.lsm_batch_multiple,
|
|
62
|
+
);
|
|
57
63
|
|
|
58
64
|
const compacts_per_checkpoint = std.math.divCeil(
|
|
59
65
|
usize,
|
|
@@ -194,23 +200,66 @@ const Environment = struct {
|
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
fn prefetch_account(env: *Environment, id: u128) void {
|
|
197
|
-
const
|
|
198
|
-
const
|
|
203
|
+
const groove_immutable = &env.forest.grooves.accounts_immutable;
|
|
204
|
+
const groove_mutable = &env.forest.grooves.accounts_mutable;
|
|
205
|
+
|
|
206
|
+
const GrooveImmutable = @TypeOf(groove_immutable.*);
|
|
207
|
+
const GrooveMutable = @TypeOf(groove_mutable.*);
|
|
199
208
|
const Getter = struct {
|
|
209
|
+
_id: u128,
|
|
210
|
+
_groove_mutable: *GrooveMutable,
|
|
211
|
+
_groove_immutable: *GrooveImmutable,
|
|
212
|
+
|
|
200
213
|
finished: bool = false,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
214
|
+
prefetch_context_mutable: GrooveMutable.PrefetchContext = undefined,
|
|
215
|
+
prefetch_context_immutable: GrooveImmutable.PrefetchContext = undefined,
|
|
216
|
+
|
|
217
|
+
fn prefetch_start(getter: *@This()) void {
|
|
218
|
+
const groove = getter._groove_immutable;
|
|
219
|
+
groove.prefetch_setup(null);
|
|
220
|
+
groove.prefetch_enqueue(getter._id);
|
|
221
|
+
groove.prefetch(@This().prefetch_callback_immuttable, &getter.prefetch_context_immutable);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
fn prefetch_callback_immuttable(prefetch_context: *GrooveImmutable.PrefetchContext) void {
|
|
225
|
+
const getter = @fieldParentPtr(@This(), "prefetch_context_immutable", prefetch_context);
|
|
226
|
+
const groove = getter._groove_mutable;
|
|
227
|
+
groove.prefetch_setup(null);
|
|
228
|
+
|
|
229
|
+
if (getter._groove_immutable.get(getter._id)) |immut| {
|
|
230
|
+
groove.prefetch_enqueue(immut.timestamp);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
groove.prefetch(@This().prefetch_callback_mutable, &getter.prefetch_context_mutable);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fn prefetch_callback_mutable(prefetch_context: *GrooveMutable.PrefetchContext) void {
|
|
237
|
+
const getter = @fieldParentPtr(@This(), "prefetch_context_mutable", prefetch_context);
|
|
238
|
+
assert(!getter.finished);
|
|
204
239
|
getter.finished = true;
|
|
205
240
|
}
|
|
206
241
|
};
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
242
|
+
|
|
243
|
+
var getter = Getter{
|
|
244
|
+
._id = id,
|
|
245
|
+
._groove_mutable = groove_mutable,
|
|
246
|
+
._groove_immutable = groove_immutable,
|
|
247
|
+
};
|
|
248
|
+
getter.prefetch_start();
|
|
211
249
|
while (!getter.finished) env.storage.tick();
|
|
212
250
|
}
|
|
213
251
|
|
|
252
|
+
fn put_account(env: *Environment, a: *const Account) void {
|
|
253
|
+
env.forest.grooves.accounts_immutable.put(&StateMachine.AccountImmutable.from_account(a));
|
|
254
|
+
env.forest.grooves.accounts_mutable.put(&StateMachine.AccountMutable.from_account(a));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
fn get_account(env: *Environment, id: u128) ?Account {
|
|
258
|
+
const immut = env.forest.grooves.accounts_immutable.get(id) orelse return null;
|
|
259
|
+
const mut = env.forest.grooves.accounts_mutable.get(immut.timestamp).?;
|
|
260
|
+
return StateMachine.into_account(immut, mut);
|
|
261
|
+
}
|
|
262
|
+
|
|
214
263
|
fn apply(env: *Environment, fuzz_ops: []const FuzzOp) !void {
|
|
215
264
|
// The forest should behave like a simple key-value data-structure.
|
|
216
265
|
// We'll compare it to a hash map.
|
|
@@ -238,13 +287,13 @@ const Environment = struct {
|
|
|
238
287
|
.put_account => |account| {
|
|
239
288
|
// The forest requires prefetch before put.
|
|
240
289
|
env.prefetch_account(account.id);
|
|
241
|
-
env.
|
|
290
|
+
env.put_account(&account);
|
|
242
291
|
try model.put(account.id, account);
|
|
243
292
|
},
|
|
244
293
|
.get_account => |id| {
|
|
245
294
|
// Get account from lsm.
|
|
246
295
|
env.prefetch_account(id);
|
|
247
|
-
const lsm_account = env.
|
|
296
|
+
const lsm_account = env.get_account(id);
|
|
248
297
|
|
|
249
298
|
// Compare result to model.
|
|
250
299
|
const model_account = model.get(id);
|
|
@@ -254,7 +303,7 @@ const Environment = struct {
|
|
|
254
303
|
assert(std.mem.eql(
|
|
255
304
|
u8,
|
|
256
305
|
std.mem.asBytes(&model_account.?),
|
|
257
|
-
std.mem.asBytes(lsm_account.?),
|
|
306
|
+
std.mem.asBytes(&lsm_account.?),
|
|
258
307
|
));
|
|
259
308
|
}
|
|
260
309
|
},
|
|
@@ -16,8 +16,6 @@ const snapshot_latest = @import("tree.zig").snapshot_latest;
|
|
|
16
16
|
const compaction_snapshot_for_op = @import("tree.zig").compaction_snapshot_for_op;
|
|
17
17
|
|
|
18
18
|
fn ObjectTreeHelpers(comptime Object: type) type {
|
|
19
|
-
assert(@hasField(Object, "id"));
|
|
20
|
-
assert(std.meta.fieldInfo(Object, .id).field_type == u128);
|
|
21
19
|
assert(@hasField(Object, "timestamp"));
|
|
22
20
|
assert(std.meta.fieldInfo(Object, .timestamp).field_type == u64);
|
|
23
21
|
|
|
@@ -121,6 +119,7 @@ fn IndexTreeType(
|
|
|
121
119
|
comptime Storage: type,
|
|
122
120
|
comptime Field: type,
|
|
123
121
|
comptime tree_name: [:0]const u8,
|
|
122
|
+
comptime value_count_max: usize,
|
|
124
123
|
) type {
|
|
125
124
|
const Key = CompositeKey(IndexCompositeKeyType(Field));
|
|
126
125
|
const Table = TableType(
|
|
@@ -131,6 +130,7 @@ fn IndexTreeType(
|
|
|
131
130
|
Key.sentinel_key,
|
|
132
131
|
Key.tombstone,
|
|
133
132
|
Key.tombstone_from_key,
|
|
133
|
+
value_count_max,
|
|
134
134
|
.secondary_index,
|
|
135
135
|
);
|
|
136
136
|
|
|
@@ -147,6 +147,10 @@ pub fn GrooveType(
|
|
|
147
147
|
comptime Object: type,
|
|
148
148
|
/// An anonymous struct instance which contains the following:
|
|
149
149
|
///
|
|
150
|
+
/// - value_count_max: { .field = usize }:
|
|
151
|
+
/// An anonymous struct which contains, for each field of `Object`,
|
|
152
|
+
/// the maximum number of values per table for the corresponding index tree.
|
|
153
|
+
///
|
|
150
154
|
/// - ignored: [][]const u8:
|
|
151
155
|
/// An array of fields on the Object type that should not be given index trees
|
|
152
156
|
///
|
|
@@ -157,8 +161,9 @@ pub fn GrooveType(
|
|
|
157
161
|
) type {
|
|
158
162
|
@setEvalBranchQuota(64000);
|
|
159
163
|
|
|
160
|
-
|
|
161
|
-
assert(std.meta.fieldInfo(Object, .id).field_type == u128);
|
|
164
|
+
const has_id = @hasField(Object, "id");
|
|
165
|
+
if (has_id) assert(std.meta.fieldInfo(Object, .id).field_type == u128);
|
|
166
|
+
|
|
162
167
|
assert(@hasField(Object, "timestamp"));
|
|
163
168
|
assert(std.meta.fieldInfo(Object, .timestamp).field_type == u64);
|
|
164
169
|
|
|
@@ -179,7 +184,12 @@ pub fn GrooveType(
|
|
|
179
184
|
|
|
180
185
|
if (!ignored) {
|
|
181
186
|
const tree_name = @typeName(Object) ++ "." ++ field.name;
|
|
182
|
-
const IndexTree = IndexTreeType(
|
|
187
|
+
const IndexTree = IndexTreeType(
|
|
188
|
+
Storage,
|
|
189
|
+
field.field_type,
|
|
190
|
+
tree_name,
|
|
191
|
+
@field(groove_options.value_count_max, field.name),
|
|
192
|
+
);
|
|
183
193
|
index_fields = index_fields ++ [_]std.builtin.TypeInfo.StructField{
|
|
184
194
|
.{
|
|
185
195
|
.name = field.name,
|
|
@@ -219,7 +229,12 @@ pub fn GrooveType(
|
|
|
219
229
|
// Create an IndexTree for the DerivedType:
|
|
220
230
|
const tree_name = @typeName(Object) ++ "." ++ field.name;
|
|
221
231
|
const DerivedType = @typeInfo(derive_return_type).Optional.child;
|
|
222
|
-
const IndexTree = IndexTreeType(
|
|
232
|
+
const IndexTree = IndexTreeType(
|
|
233
|
+
Storage,
|
|
234
|
+
DerivedType,
|
|
235
|
+
tree_name,
|
|
236
|
+
@field(groove_options.value_count_max, field.name),
|
|
237
|
+
);
|
|
223
238
|
|
|
224
239
|
index_fields = index_fields ++ &.{
|
|
225
240
|
.{
|
|
@@ -246,7 +261,7 @@ pub fn GrooveType(
|
|
|
246
261
|
};
|
|
247
262
|
}
|
|
248
263
|
|
|
249
|
-
const
|
|
264
|
+
const _ObjectTree = blk: {
|
|
250
265
|
const Table = TableType(
|
|
251
266
|
u64, // key = timestamp
|
|
252
267
|
Object,
|
|
@@ -255,6 +270,7 @@ pub fn GrooveType(
|
|
|
255
270
|
ObjectTreeHelpers(Object).sentinel_key,
|
|
256
271
|
ObjectTreeHelpers(Object).tombstone,
|
|
257
272
|
ObjectTreeHelpers(Object).tombstone_from_key,
|
|
273
|
+
groove_options.value_count_max.timestamp,
|
|
258
274
|
.general,
|
|
259
275
|
);
|
|
260
276
|
|
|
@@ -262,7 +278,7 @@ pub fn GrooveType(
|
|
|
262
278
|
break :blk TreeType(Table, Storage, tree_name);
|
|
263
279
|
};
|
|
264
280
|
|
|
265
|
-
const
|
|
281
|
+
const _IdTree = if (!has_id) void else blk: {
|
|
266
282
|
const Table = TableType(
|
|
267
283
|
u128,
|
|
268
284
|
IdTreeValue,
|
|
@@ -271,6 +287,7 @@ pub fn GrooveType(
|
|
|
271
287
|
IdTreeValue.sentinel_key,
|
|
272
288
|
IdTreeValue.tombstone,
|
|
273
289
|
IdTreeValue.tombstone_from_key,
|
|
290
|
+
groove_options.value_count_max.id,
|
|
274
291
|
.general,
|
|
275
292
|
);
|
|
276
293
|
|
|
@@ -278,7 +295,7 @@ pub fn GrooveType(
|
|
|
278
295
|
break :blk TreeType(Table, Storage, tree_name);
|
|
279
296
|
};
|
|
280
297
|
|
|
281
|
-
const
|
|
298
|
+
const _IndexTrees = @Type(.{
|
|
282
299
|
.Struct = .{
|
|
283
300
|
.layout = .Auto,
|
|
284
301
|
.fields = index_fields,
|
|
@@ -296,22 +313,26 @@ pub fn GrooveType(
|
|
|
296
313
|
});
|
|
297
314
|
|
|
298
315
|
// Verify no hash collisions between all the trees:
|
|
299
|
-
comptime var hashes: []const u128 = &.{
|
|
316
|
+
comptime var hashes: []const u128 = &.{_ObjectTree.hash};
|
|
300
317
|
|
|
301
|
-
|
|
302
|
-
const
|
|
318
|
+
if (has_id) {
|
|
319
|
+
const hash: []const u128 = &.{_IdTree.hash};
|
|
320
|
+
assert(std.mem.indexOf(u128, hashes, hash) == null);
|
|
321
|
+
hashes = hashes ++ hash;
|
|
322
|
+
}
|
|
323
|
+
inline for (std.meta.fields(_IndexTrees)) |field| {
|
|
324
|
+
const IndexTree = @TypeOf(@field(@as(_IndexTrees, undefined), field.name));
|
|
303
325
|
const hash: []const u128 = &.{IndexTree.hash};
|
|
304
|
-
|
|
305
326
|
assert(std.mem.indexOf(u128, hashes, hash) == null);
|
|
306
327
|
hashes = hashes ++ hash;
|
|
307
328
|
}
|
|
308
329
|
|
|
309
330
|
// Verify groove index count:
|
|
310
|
-
const indexes_count_actual = std.meta.fields(
|
|
331
|
+
const indexes_count_actual = std.meta.fields(_IndexTrees).len;
|
|
311
332
|
const indexes_count_expect = std.meta.fields(Object).len -
|
|
312
333
|
groove_options.ignored.len -
|
|
313
|
-
// The id/timestamp
|
|
314
|
-
|
|
334
|
+
// The id/timestamp fields are implicitly ignored since it's the primary key for ObjectTree:
|
|
335
|
+
(1 + @boolToInt(has_id)) +
|
|
315
336
|
std.meta.fields(@TypeOf(groove_options.derived)).len;
|
|
316
337
|
|
|
317
338
|
assert(indexes_count_actual == indexes_count_expect);
|
|
@@ -372,6 +393,10 @@ pub fn GrooveType(
|
|
|
372
393
|
return struct {
|
|
373
394
|
const Groove = @This();
|
|
374
395
|
|
|
396
|
+
pub const ObjectTree = _ObjectTree;
|
|
397
|
+
pub const IdTree = _IdTree;
|
|
398
|
+
pub const IndexTrees = _IndexTrees;
|
|
399
|
+
|
|
375
400
|
const Grid = GridType(Storage);
|
|
376
401
|
|
|
377
402
|
const Callback = fn (*Groove) void;
|
|
@@ -381,24 +406,26 @@ pub fn GrooveType(
|
|
|
381
406
|
open,
|
|
382
407
|
};
|
|
383
408
|
|
|
384
|
-
const
|
|
409
|
+
const primary_field = if (has_id) "id" else "timestamp";
|
|
410
|
+
const PrimaryKey = @TypeOf(@field(@as(Object, undefined), primary_field));
|
|
411
|
+
const PrefetchIDs = std.AutoHashMapUnmanaged(PrimaryKey, void);
|
|
385
412
|
|
|
386
413
|
const PrefetchObjectsContext = struct {
|
|
387
414
|
pub fn hash(_: PrefetchObjectsContext, object: Object) u64 {
|
|
388
|
-
return std.hash.Wyhash.hash(0, mem.asBytes(
|
|
415
|
+
return std.hash.Wyhash.hash(0, mem.asBytes(&@field(object, primary_field)));
|
|
389
416
|
}
|
|
390
417
|
|
|
391
418
|
pub fn eql(_: PrefetchObjectsContext, a: Object, b: Object) bool {
|
|
392
|
-
return a
|
|
419
|
+
return @field(a, primary_field) == @field(b, primary_field);
|
|
393
420
|
}
|
|
394
421
|
};
|
|
395
422
|
const PrefetchObjectsAdapter = struct {
|
|
396
|
-
pub fn hash(_: PrefetchObjectsAdapter,
|
|
397
|
-
return std.hash.Wyhash.hash(0, mem.asBytes(&
|
|
423
|
+
pub fn hash(_: PrefetchObjectsAdapter, key: PrimaryKey) u64 {
|
|
424
|
+
return std.hash.Wyhash.hash(0, mem.asBytes(&key));
|
|
398
425
|
}
|
|
399
426
|
|
|
400
|
-
pub fn eql(_: PrefetchObjectsAdapter,
|
|
401
|
-
return
|
|
427
|
+
pub fn eql(_: PrefetchObjectsAdapter, a_key: PrimaryKey, b_object: Object) bool {
|
|
428
|
+
return a_key == @field(b_object, primary_field);
|
|
402
429
|
}
|
|
403
430
|
};
|
|
404
431
|
const PrefetchObjects = std.HashMapUnmanaged(Object, void, PrefetchObjectsContext, 70);
|
|
@@ -431,7 +458,7 @@ pub fn GrooveType(
|
|
|
431
458
|
prefetch_entries_max: u32,
|
|
432
459
|
|
|
433
460
|
tree_options_object: ObjectTree.Options,
|
|
434
|
-
tree_options_id: IdTree.Options,
|
|
461
|
+
tree_options_id: if (has_id) IdTree.Options else void,
|
|
435
462
|
tree_options_index: IndexTreeOptions,
|
|
436
463
|
};
|
|
437
464
|
|
|
@@ -450,13 +477,13 @@ pub fn GrooveType(
|
|
|
450
477
|
);
|
|
451
478
|
errdefer object_tree.deinit(allocator);
|
|
452
479
|
|
|
453
|
-
var id_tree = try IdTree.init(
|
|
480
|
+
var id_tree = if (!has_id) {} else (try IdTree.init(
|
|
454
481
|
allocator,
|
|
455
482
|
node_pool,
|
|
456
483
|
grid,
|
|
457
484
|
options.tree_options_id,
|
|
458
|
-
);
|
|
459
|
-
errdefer id_tree.deinit(allocator);
|
|
485
|
+
));
|
|
486
|
+
errdefer if (has_id) id_tree.deinit(allocator);
|
|
460
487
|
|
|
461
488
|
var index_trees_initialized: usize = 0;
|
|
462
489
|
var index_trees: IndexTrees = undefined;
|
|
@@ -507,7 +534,7 @@ pub fn GrooveType(
|
|
|
507
534
|
}
|
|
508
535
|
|
|
509
536
|
groove.objects.deinit(allocator);
|
|
510
|
-
groove.ids.deinit(allocator);
|
|
537
|
+
if (has_id) groove.ids.deinit(allocator);
|
|
511
538
|
|
|
512
539
|
groove.prefetch_ids.deinit(allocator);
|
|
513
540
|
groove.prefetch_objects.deinit(allocator);
|
|
@@ -515,8 +542,8 @@ pub fn GrooveType(
|
|
|
515
542
|
groove.* = undefined;
|
|
516
543
|
}
|
|
517
544
|
|
|
518
|
-
pub fn get(groove: *const Groove,
|
|
519
|
-
return groove.prefetch_objects.getKeyPtrAdapted(
|
|
545
|
+
pub fn get(groove: *const Groove, key: PrimaryKey) ?*const Object {
|
|
546
|
+
return groove.prefetch_objects.getKeyPtrAdapted(key, PrefetchObjectsAdapter{});
|
|
520
547
|
}
|
|
521
548
|
|
|
522
549
|
/// Must be called directly before the state machine begins queuing ids for prefetch.
|
|
@@ -526,7 +553,7 @@ pub fn GrooveType(
|
|
|
526
553
|
// output tables until the compaction is complete. (Until then, the output tables may
|
|
527
554
|
// be in the manifest but not yet on disk).
|
|
528
555
|
const snapshot_max = groove.objects.lookup_snapshot_max;
|
|
529
|
-
assert(snapshot_max == groove.ids.lookup_snapshot_max);
|
|
556
|
+
assert(!has_id or snapshot_max == groove.ids.lookup_snapshot_max);
|
|
530
557
|
|
|
531
558
|
const snapshot_target = snapshot orelse snapshot_max;
|
|
532
559
|
assert(snapshot_target <= snapshot_max);
|
|
@@ -546,8 +573,13 @@ pub fn GrooveType(
|
|
|
546
573
|
/// This must be called by the state machine for every key to be prefetched.
|
|
547
574
|
/// We tolerate duplicate IDs enqueued by the state machine.
|
|
548
575
|
/// For example, if all unique operations require the same two dependencies.
|
|
549
|
-
pub fn prefetch_enqueue(groove: *Groove,
|
|
550
|
-
if (
|
|
576
|
+
pub fn prefetch_enqueue(groove: *Groove, key: PrimaryKey) void {
|
|
577
|
+
if (!has_id) {
|
|
578
|
+
groove.prefetch_ids.putAssumeCapacity(key, {});
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (groove.ids.lookup_from_memory(groove.prefetch_snapshot.?, key)) |id_tree_value| {
|
|
551
583
|
if (id_tree_value.tombstone()) {
|
|
552
584
|
// Do nothing; an explicit ID tombstone indicates that the object was deleted.
|
|
553
585
|
} else {
|
|
@@ -556,16 +588,16 @@ pub fn GrooveType(
|
|
|
556
588
|
id_tree_value.timestamp,
|
|
557
589
|
)) |object| {
|
|
558
590
|
assert(!ObjectTreeHelpers(Object).tombstone(object));
|
|
559
|
-
assert(object.id ==
|
|
591
|
+
assert(object.id == key);
|
|
560
592
|
groove.prefetch_objects.putAssumeCapacity(object.*, {});
|
|
561
593
|
} else {
|
|
562
594
|
// The id was in the IdTree's value cache, but not in the ObjectTree's
|
|
563
595
|
// value cache.
|
|
564
|
-
groove.prefetch_ids.putAssumeCapacity(
|
|
596
|
+
groove.prefetch_ids.putAssumeCapacity(key, {});
|
|
565
597
|
}
|
|
566
598
|
}
|
|
567
599
|
} else {
|
|
568
|
-
groove.prefetch_ids.putAssumeCapacity(
|
|
600
|
+
groove.prefetch_ids.putAssumeCapacity(key, {});
|
|
569
601
|
}
|
|
570
602
|
}
|
|
571
603
|
|
|
@@ -642,7 +674,7 @@ pub fn GrooveType(
|
|
|
642
674
|
// TODO(ifreund): use a union for these to save memory, likely an extern union
|
|
643
675
|
// so that we can safetly @ptrCast() until @fieldParentPtr() is implemented
|
|
644
676
|
// for unions. See: https://github.com/ziglang/zig/issues/6611
|
|
645
|
-
lookup_id: IdTree.LookupContext = undefined,
|
|
677
|
+
lookup_id: if (has_id) IdTree.LookupContext else void = undefined,
|
|
646
678
|
lookup_object: ObjectTree.LookupContext = undefined,
|
|
647
679
|
|
|
648
680
|
fn lookup_start_next(worker: *PrefetchWorker) void {
|
|
@@ -651,6 +683,11 @@ pub fn GrooveType(
|
|
|
651
683
|
return;
|
|
652
684
|
};
|
|
653
685
|
|
|
686
|
+
if (!has_id) {
|
|
687
|
+
worker.lookup_with_timestamp(id.*);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
|
|
654
691
|
if (worker.context.groove.ids.lookup_from_memory(
|
|
655
692
|
worker.context.snapshot,
|
|
656
693
|
id.*,
|
|
@@ -699,32 +736,34 @@ pub fn GrooveType(
|
|
|
699
736
|
);
|
|
700
737
|
}
|
|
701
738
|
|
|
702
|
-
if (id_tree_value.tombstone()) {
|
|
703
|
-
worker.
|
|
739
|
+
if (!id_tree_value.tombstone()) {
|
|
740
|
+
worker.lookup_with_timestamp(id_tree_value.timestamp);
|
|
704
741
|
return;
|
|
705
742
|
}
|
|
743
|
+
}
|
|
706
744
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
id_tree_value.timestamp,
|
|
710
|
-
)) |object| {
|
|
711
|
-
// The object is not a tombstone; the ID and Object trees are in sync.
|
|
712
|
-
assert(!ObjectTreeHelpers(Object).tombstone(object));
|
|
745
|
+
worker.lookup_start_next();
|
|
746
|
+
}
|
|
713
747
|
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
748
|
+
fn lookup_with_timestamp(worker: *PrefetchWorker, timestamp: u64) void {
|
|
749
|
+
if (worker.context.groove.objects.lookup_from_memory(
|
|
750
|
+
worker.context.snapshot,
|
|
751
|
+
timestamp,
|
|
752
|
+
)) |object| {
|
|
753
|
+
// The object is not a tombstone; the ID (if any) and Object trees are in sync.
|
|
754
|
+
assert(!ObjectTreeHelpers(Object).tombstone(object));
|
|
718
755
|
|
|
719
|
-
worker.context.groove.
|
|
720
|
-
lookup_object_callback,
|
|
721
|
-
&worker.lookup_object,
|
|
722
|
-
worker.context.snapshot,
|
|
723
|
-
id_tree_value.timestamp,
|
|
724
|
-
);
|
|
725
|
-
} else {
|
|
756
|
+
worker.context.groove.prefetch_objects.putAssumeCapacityNoClobber(object.*, {});
|
|
726
757
|
worker.lookup_start_next();
|
|
758
|
+
return;
|
|
727
759
|
}
|
|
760
|
+
|
|
761
|
+
worker.context.groove.objects.lookup_from_levels(
|
|
762
|
+
lookup_object_callback,
|
|
763
|
+
&worker.lookup_object,
|
|
764
|
+
worker.context.snapshot,
|
|
765
|
+
timestamp,
|
|
766
|
+
);
|
|
728
767
|
}
|
|
729
768
|
|
|
730
769
|
fn lookup_object_callback(
|
|
@@ -733,7 +772,7 @@ pub fn GrooveType(
|
|
|
733
772
|
) void {
|
|
734
773
|
const worker = @fieldParentPtr(PrefetchWorker, "lookup_object", completion);
|
|
735
774
|
|
|
736
|
-
// The result must be non-null as we keep the ID and Object trees in sync.
|
|
775
|
+
// The result must be non-null as we keep the ID (if any) and Object trees in sync.
|
|
737
776
|
const object = result.?;
|
|
738
777
|
assert(!ObjectTreeHelpers(Object).tombstone(object));
|
|
739
778
|
|
|
@@ -743,14 +782,21 @@ pub fn GrooveType(
|
|
|
743
782
|
};
|
|
744
783
|
|
|
745
784
|
pub fn put_no_clobber(groove: *Groove, object: *const Object) void {
|
|
746
|
-
const gop = groove.prefetch_objects.getOrPutAssumeCapacityAdapted(
|
|
785
|
+
const gop = groove.prefetch_objects.getOrPutAssumeCapacityAdapted(
|
|
786
|
+
@field(object, primary_field),
|
|
787
|
+
PrefetchObjectsAdapter{},
|
|
788
|
+
);
|
|
747
789
|
assert(!gop.found_existing);
|
|
748
790
|
groove.insert(object);
|
|
749
791
|
gop.key_ptr.* = object.*;
|
|
750
792
|
}
|
|
751
793
|
|
|
752
794
|
pub fn put(groove: *Groove, object: *const Object) void {
|
|
753
|
-
const gop = groove.prefetch_objects.getOrPutAssumeCapacityAdapted(
|
|
795
|
+
const gop = groove.prefetch_objects.getOrPutAssumeCapacityAdapted(
|
|
796
|
+
@field(object, primary_field),
|
|
797
|
+
PrefetchObjectsAdapter{},
|
|
798
|
+
);
|
|
799
|
+
|
|
754
800
|
if (gop.found_existing) {
|
|
755
801
|
groove.update(gop.key_ptr, object);
|
|
756
802
|
} else {
|
|
@@ -762,7 +808,7 @@ pub fn GrooveType(
|
|
|
762
808
|
/// Insert the value into the objects tree and its fields into the index trees.
|
|
763
809
|
fn insert(groove: *Groove, object: *const Object) void {
|
|
764
810
|
groove.objects.put(object);
|
|
765
|
-
groove.ids.put(&IdTreeValue{ .id = object.id, .timestamp = object.timestamp });
|
|
811
|
+
if (has_id) groove.ids.put(&IdTreeValue{ .id = object.id, .timestamp = object.timestamp });
|
|
766
812
|
|
|
767
813
|
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
768
814
|
const Helper = IndexTreeFieldHelperType(field.name);
|
|
@@ -776,7 +822,7 @@ pub fn GrooveType(
|
|
|
776
822
|
|
|
777
823
|
/// Update the object and index trees by diff'ing the old and new values.
|
|
778
824
|
fn update(groove: *Groove, old: *const Object, new: *const Object) void {
|
|
779
|
-
assert(old
|
|
825
|
+
assert(@field(old, primary_field) == @field(new, primary_field));
|
|
780
826
|
assert(old.timestamp == new.timestamp);
|
|
781
827
|
|
|
782
828
|
// Update the object tree entry if any of the fields (even ignored) are different.
|
|
@@ -806,12 +852,14 @@ pub fn GrooveType(
|
|
|
806
852
|
}
|
|
807
853
|
}
|
|
808
854
|
|
|
809
|
-
/// Asserts that the object with the given
|
|
810
|
-
pub fn remove(groove: *Groove,
|
|
811
|
-
const object = groove.prefetch_objects.getKeyPtrAdapted(
|
|
855
|
+
/// Asserts that the object with the given PrimaryKey exists.
|
|
856
|
+
pub fn remove(groove: *Groove, key: PrimaryKey) void {
|
|
857
|
+
const object = groove.prefetch_objects.getKeyPtrAdapted(key, PrefetchObjectsAdapter{}).?;
|
|
812
858
|
|
|
813
859
|
groove.objects.remove(object);
|
|
814
|
-
|
|
860
|
+
if (has_id) {
|
|
861
|
+
groove.ids.remove(&IdTreeValue{ .id = object.id, .timestamp = object.timestamp });
|
|
862
|
+
}
|
|
815
863
|
|
|
816
864
|
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
817
865
|
const Helper = IndexTreeFieldHelperType(field.name);
|
|
@@ -824,11 +872,14 @@ pub fn GrooveType(
|
|
|
824
872
|
|
|
825
873
|
// TODO(zig) Replace this with a call to removeByPtr() after upgrading to 0.10.
|
|
826
874
|
// removeByPtr() replaces an unnecessary lookup here with some pointer arithmetic.
|
|
827
|
-
assert(groove.prefetch_objects.removeAdapted(
|
|
875
|
+
assert(groove.prefetch_objects.removeAdapted(
|
|
876
|
+
@field(object, primary_field),
|
|
877
|
+
PrefetchObjectsAdapter{},
|
|
878
|
+
));
|
|
828
879
|
}
|
|
829
880
|
|
|
830
881
|
/// Maximum number of pending sync callbacks (ObjectTree + IdTree + IndexTrees).
|
|
831
|
-
const join_pending_max =
|
|
882
|
+
const join_pending_max = 1 + @boolToInt(has_id) + std.meta.fields(IndexTrees).len;
|
|
832
883
|
|
|
833
884
|
fn JoinType(comptime join_op: JoinOp) type {
|
|
834
885
|
return struct {
|
|
@@ -897,7 +948,7 @@ pub fn GrooveType(
|
|
|
897
948
|
const Join = JoinType(.open);
|
|
898
949
|
Join.start(groove, callback);
|
|
899
950
|
|
|
900
|
-
groove.ids.open(Join.tree_callback(.ids));
|
|
951
|
+
if (has_id) groove.ids.open(Join.tree_callback(.ids));
|
|
901
952
|
groove.objects.open(Join.tree_callback(.objects));
|
|
902
953
|
|
|
903
954
|
inline for (std.meta.fields(IndexTrees)) |field| {
|
|
@@ -912,7 +963,7 @@ pub fn GrooveType(
|
|
|
912
963
|
Join.start(groove, callback);
|
|
913
964
|
|
|
914
965
|
// Compact the ObjectTree and IdTree
|
|
915
|
-
groove.ids.compact(Join.tree_callback(.ids), op);
|
|
966
|
+
if (has_id) groove.ids.compact(Join.tree_callback(.ids), op);
|
|
916
967
|
groove.objects.compact(Join.tree_callback(.objects), op);
|
|
917
968
|
|
|
918
969
|
// Compact the IndexTrees.
|
|
@@ -928,7 +979,7 @@ pub fn GrooveType(
|
|
|
928
979
|
Join.start(groove, callback);
|
|
929
980
|
|
|
930
981
|
// Checkpoint the IdTree and ObjectTree.
|
|
931
|
-
groove.ids.checkpoint(Join.tree_callback(.ids));
|
|
982
|
+
if (has_id) groove.ids.checkpoint(Join.tree_callback(.ids));
|
|
932
983
|
groove.objects.checkpoint(Join.tree_callback(.objects));
|
|
933
984
|
|
|
934
985
|
// Checkpoint the IndexTrees.
|
|
@@ -948,6 +999,19 @@ test "Groove" {
|
|
|
948
999
|
Storage,
|
|
949
1000
|
Transfer,
|
|
950
1001
|
.{
|
|
1002
|
+
// Doesn't matter for this test.
|
|
1003
|
+
.value_count_max = .{
|
|
1004
|
+
.timestamp = 1,
|
|
1005
|
+
.id = 1,
|
|
1006
|
+
.debit_account_id = 1,
|
|
1007
|
+
.credit_account_id = 1,
|
|
1008
|
+
.user_data = 1,
|
|
1009
|
+
.pending_id = 1,
|
|
1010
|
+
.timeout = 1,
|
|
1011
|
+
.ledger = 1,
|
|
1012
|
+
.code = 1,
|
|
1013
|
+
.amount = 1,
|
|
1014
|
+
},
|
|
951
1015
|
.ignored = [_][]const u8{ "reserved", "user_data", "flags" },
|
|
952
1016
|
.derived = .{},
|
|
953
1017
|
},
|
|
@@ -38,7 +38,7 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
38
38
|
table_iterator: TableIterator,
|
|
39
39
|
};
|
|
40
40
|
|
|
41
|
-
const ValuesRingBuffer = RingBuffer(Value, Table.data.
|
|
41
|
+
const ValuesRingBuffer = RingBuffer(Value, Table.data.block_value_count_max, .pointer);
|
|
42
42
|
const TablesRingBuffer = RingBuffer(TableIteratorScope, 2, .array);
|
|
43
43
|
|
|
44
44
|
grid: *Grid,
|
|
@@ -280,7 +280,7 @@ pub fn LevelIteratorType(comptime Table: type, comptime Storage: type) type {
|
|
|
280
280
|
|
|
281
281
|
fn buffered_enough_values(it: LevelIterator) bool {
|
|
282
282
|
return it.buffered_all_values() or
|
|
283
|
-
it.buffered_value_count() >= Table.data.
|
|
283
|
+
it.buffered_value_count() >= Table.data.block_value_count_max;
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
/// Returns either:
|