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
|
@@ -96,6 +96,9 @@ pub const Command = enum(u8) {
|
|
|
96
96
|
ping,
|
|
97
97
|
pong,
|
|
98
98
|
|
|
99
|
+
ping_client,
|
|
100
|
+
pong_client,
|
|
101
|
+
|
|
99
102
|
request,
|
|
100
103
|
prepare,
|
|
101
104
|
prepare_ok,
|
|
@@ -159,6 +162,8 @@ pub const Operation = enum(u8) {
|
|
|
159
162
|
/// We reuse the same header for both so that prepare messages from the primary can simply be
|
|
160
163
|
/// journalled as is by the backups without requiring any further modification.
|
|
161
164
|
pub const Header = extern struct {
|
|
165
|
+
const checksum_body_empty = checksum(&.{});
|
|
166
|
+
|
|
162
167
|
comptime {
|
|
163
168
|
assert(@sizeOf(Header) == 128);
|
|
164
169
|
// Assert that there is no implicit padding in the struct.
|
|
@@ -250,6 +255,7 @@ pub const Header = extern struct {
|
|
|
250
255
|
/// * A `pong` sets this to the sender's wall clock value.
|
|
251
256
|
/// * A `request_prepare` sets this to `1` when `context` is set to a checksum, and `0`
|
|
252
257
|
/// otherwise.
|
|
258
|
+
/// * A `commit` message sets this to the replica's monotonic timestamp.
|
|
253
259
|
timestamp: u64 = 0,
|
|
254
260
|
|
|
255
261
|
/// The size of the Header structure (always), plus any associated body.
|
|
@@ -312,6 +318,8 @@ pub const Header = extern struct {
|
|
|
312
318
|
.reserved => self.invalid_reserved(),
|
|
313
319
|
.ping => self.invalid_ping(),
|
|
314
320
|
.pong => self.invalid_pong(),
|
|
321
|
+
.ping_client => self.invalid_ping_client(),
|
|
322
|
+
.pong_client => self.invalid_pong_client(),
|
|
315
323
|
.request => self.invalid_request(),
|
|
316
324
|
.prepare => self.invalid_prepare(),
|
|
317
325
|
.prepare_ok => self.invalid_prepare_ok(),
|
|
@@ -348,15 +356,14 @@ pub const Header = extern struct {
|
|
|
348
356
|
fn invalid_ping(self: *const Header) ?[]const u8 {
|
|
349
357
|
assert(self.command == .ping);
|
|
350
358
|
if (self.parent != 0) return "parent != 0";
|
|
359
|
+
if (self.client != 0) return "client != 0";
|
|
351
360
|
if (self.context != 0) return "context != 0";
|
|
352
361
|
if (self.request != 0) return "request != 0";
|
|
362
|
+
if (self.view != 0) return "view != 0";
|
|
353
363
|
if (self.commit != 0) return "commit != 0";
|
|
354
364
|
if (self.timestamp != 0) return "timestamp != 0";
|
|
355
|
-
if (self.
|
|
356
|
-
if (self.
|
|
357
|
-
if (self.replica != 0) return "replica != 0";
|
|
358
|
-
if (self.op != 0) return "op != 0";
|
|
359
|
-
}
|
|
365
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
366
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
360
367
|
if (self.operation != .reserved) return "operation != .reserved";
|
|
361
368
|
return null;
|
|
362
369
|
}
|
|
@@ -367,10 +374,43 @@ pub const Header = extern struct {
|
|
|
367
374
|
if (self.client != 0) return "client != 0";
|
|
368
375
|
if (self.context != 0) return "context != 0";
|
|
369
376
|
if (self.request != 0) return "request != 0";
|
|
377
|
+
if (self.view != 0) return "view != 0";
|
|
370
378
|
if (self.commit != 0) return "commit != 0";
|
|
371
|
-
if (self.timestamp
|
|
372
|
-
|
|
373
|
-
|
|
379
|
+
if (self.timestamp == 0) return "timestamp == 0";
|
|
380
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
381
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
382
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
fn invalid_ping_client(self: *const Header) ?[]const u8 {
|
|
387
|
+
assert(self.command == .ping_client);
|
|
388
|
+
if (self.parent != 0) return "parent != 0";
|
|
389
|
+
if (self.client == 0) return "client == 0";
|
|
390
|
+
if (self.context != 0) return "context != 0";
|
|
391
|
+
if (self.request != 0) return "request != 0";
|
|
392
|
+
if (self.view != 0) return "view != 0";
|
|
393
|
+
if (self.op != 0) return "op != 0";
|
|
394
|
+
if (self.commit != 0) return "commit != 0";
|
|
395
|
+
if (self.timestamp != 0) return "timestamp != 0";
|
|
396
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
397
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
398
|
+
if (self.replica != 0) return "replica != 0";
|
|
399
|
+
if (self.operation != .reserved) return "operation != .reserved";
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
fn invalid_pong_client(self: *const Header) ?[]const u8 {
|
|
404
|
+
assert(self.command == .pong_client);
|
|
405
|
+
if (self.parent != 0) return "parent != 0";
|
|
406
|
+
if (self.client != 0) return "client != 0";
|
|
407
|
+
if (self.context != 0) return "context != 0";
|
|
408
|
+
if (self.request != 0) return "request != 0";
|
|
409
|
+
if (self.op != 0) return "op != 0";
|
|
410
|
+
if (self.commit != 0) return "commit != 0";
|
|
411
|
+
if (self.timestamp != 0) return "timestamp != 0";
|
|
412
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
413
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
374
414
|
if (self.operation != .reserved) return "operation != .reserved";
|
|
375
415
|
return null;
|
|
376
416
|
}
|
|
@@ -387,10 +427,11 @@ pub const Header = extern struct {
|
|
|
387
427
|
.root => return "operation == .root",
|
|
388
428
|
.register => {
|
|
389
429
|
// The first request a client makes must be to register with the cluster:
|
|
390
|
-
if (self.parent != 0) return "parent != 0";
|
|
391
|
-
if (self.context != 0) return "context != 0";
|
|
392
|
-
if (self.request != 0) return "request != 0";
|
|
430
|
+
if (self.parent != 0) return "register: parent != 0";
|
|
431
|
+
if (self.context != 0) return "register: context != 0";
|
|
432
|
+
if (self.request != 0) return "register: request != 0";
|
|
393
433
|
// The .register operation carries no payload:
|
|
434
|
+
if (self.checksum_body != checksum_body_empty) return "register: checksum_body != expected";
|
|
394
435
|
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
395
436
|
},
|
|
396
437
|
else => {
|
|
@@ -416,6 +457,7 @@ pub const Header = extern struct {
|
|
|
416
457
|
if (self.op != 0) return "root: op != 0";
|
|
417
458
|
if (self.commit != 0) return "root: commit != 0";
|
|
418
459
|
if (self.timestamp != 0) return "root: timestamp != 0";
|
|
460
|
+
if (self.checksum_body != checksum_body_empty) return "root: checksum_body != expected";
|
|
419
461
|
if (self.size != @sizeOf(Header)) return "root: size != @sizeOf(Header)";
|
|
420
462
|
if (self.replica != 0) return "root: replica != 0";
|
|
421
463
|
},
|
|
@@ -438,6 +480,7 @@ pub const Header = extern struct {
|
|
|
438
480
|
|
|
439
481
|
fn invalid_prepare_ok(self: *const Header) ?[]const u8 {
|
|
440
482
|
assert(self.command == .prepare_ok);
|
|
483
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
441
484
|
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
442
485
|
switch (self.operation) {
|
|
443
486
|
.reserved => return "operation == .reserved",
|
|
@@ -456,6 +499,7 @@ pub const Header = extern struct {
|
|
|
456
499
|
if (self.client == 0) return "client == 0";
|
|
457
500
|
if (self.op == 0) return "op == 0";
|
|
458
501
|
if (self.op <= self.commit) return "op <= commit";
|
|
502
|
+
if (self.timestamp == 0) return "timestamp == 0";
|
|
459
503
|
if (self.operation == .register) {
|
|
460
504
|
if (self.request != 0) return "request != 0";
|
|
461
505
|
} else {
|
|
@@ -491,7 +535,9 @@ pub const Header = extern struct {
|
|
|
491
535
|
if (self.client != 0) return "client != 0";
|
|
492
536
|
if (self.request != 0) return "request != 0";
|
|
493
537
|
if (self.op != 0) return "op != 0";
|
|
494
|
-
if (self.timestamp
|
|
538
|
+
if (self.timestamp == 0) return "timestamp == 0";
|
|
539
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
540
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
495
541
|
if (self.operation != .reserved) return "operation != .reserved";
|
|
496
542
|
return null;
|
|
497
543
|
}
|
|
@@ -505,6 +551,8 @@ pub const Header = extern struct {
|
|
|
505
551
|
if (self.op != 0) return "op != 0";
|
|
506
552
|
if (self.commit != 0) return "commit != 0";
|
|
507
553
|
if (self.timestamp != 0) return "timestamp != 0";
|
|
554
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
555
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
508
556
|
if (self.operation != .reserved) return "operation != .reserved";
|
|
509
557
|
return null;
|
|
510
558
|
}
|
|
@@ -539,6 +587,8 @@ pub const Header = extern struct {
|
|
|
539
587
|
if (self.op != 0) return "op != 0";
|
|
540
588
|
if (self.commit != 0) return "commit != 0";
|
|
541
589
|
if (self.timestamp != 0) return "timestamp != 0";
|
|
590
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
591
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
542
592
|
if (self.operation != .reserved) return "operation != .reserved";
|
|
543
593
|
return null;
|
|
544
594
|
}
|
|
@@ -549,8 +599,10 @@ pub const Header = extern struct {
|
|
|
549
599
|
if (self.client != 0) return "client != 0";
|
|
550
600
|
if (self.context != 0) return "context != 0";
|
|
551
601
|
if (self.request != 0) return "request != 0";
|
|
552
|
-
if (self.timestamp != 0) return "timestamp != 0";
|
|
553
602
|
if (self.commit > self.op) return "op_min > op_max";
|
|
603
|
+
if (self.timestamp != 0) return "timestamp != 0";
|
|
604
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
605
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
554
606
|
if (self.operation != .reserved) return "operation != .reserved";
|
|
555
607
|
return null;
|
|
556
608
|
}
|
|
@@ -561,6 +613,8 @@ pub const Header = extern struct {
|
|
|
561
613
|
if (self.client != 0) return "client != 0";
|
|
562
614
|
if (self.request != 0) return "request != 0";
|
|
563
615
|
if (self.commit != 0) return "commit != 0";
|
|
616
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
617
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
564
618
|
switch (self.timestamp) {
|
|
565
619
|
0 => if (self.context != 0) return "context != 0",
|
|
566
620
|
1 => {}, // context is a checksum, which may be 0.
|
|
@@ -589,6 +643,8 @@ pub const Header = extern struct {
|
|
|
589
643
|
if (self.request != 0) return "request != 0";
|
|
590
644
|
if (self.commit != 0) return "commit != 0";
|
|
591
645
|
if (self.timestamp != 0) return "timestamp != 0";
|
|
646
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
647
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
592
648
|
if (self.operation != .reserved) return "operation != .reserved";
|
|
593
649
|
return null;
|
|
594
650
|
}
|
|
@@ -601,6 +657,8 @@ pub const Header = extern struct {
|
|
|
601
657
|
if (self.op != 0) return "op != 0";
|
|
602
658
|
if (self.commit != 0) return "commit != 0";
|
|
603
659
|
if (self.timestamp != 0) return "timestamp != 0";
|
|
660
|
+
if (self.checksum_body != checksum_body_empty) return "checksum_body != expected";
|
|
661
|
+
if (self.size != @sizeOf(Header)) return "size != @sizeOf(Header)";
|
|
604
662
|
if (self.operation != .reserved) return "operation != .reserved";
|
|
605
663
|
return null;
|
|
606
664
|
}
|
|
@@ -619,15 +677,7 @@ pub const Header = extern struct {
|
|
|
619
677
|
},
|
|
620
678
|
.prepare => return .unknown,
|
|
621
679
|
// These messages identify the peer as either a replica or a client:
|
|
622
|
-
|
|
623
|
-
.ping, .pong => {
|
|
624
|
-
if (self.client > 0) {
|
|
625
|
-
assert(self.replica == 0);
|
|
626
|
-
return .client;
|
|
627
|
-
} else {
|
|
628
|
-
return .replica;
|
|
629
|
-
}
|
|
630
|
-
},
|
|
680
|
+
.ping_client => return .client,
|
|
631
681
|
// All other messages identify the peer as a replica:
|
|
632
682
|
else => return .replica,
|
|
633
683
|
}
|
|
@@ -687,7 +737,7 @@ pub const Timeout = struct {
|
|
|
687
737
|
|
|
688
738
|
/// It's important to check that when fired() is acted on that the timeout is stopped/started,
|
|
689
739
|
/// otherwise further ticks around the event loop may trigger a thundering herd of messages.
|
|
690
|
-
pub fn fired(self: *Timeout) bool {
|
|
740
|
+
pub fn fired(self: *const Timeout) bool {
|
|
691
741
|
if (self.ticking and self.ticks >= self.after) {
|
|
692
742
|
log.debug("{}: {s} fired", .{ self.id, self.name });
|
|
693
743
|
if (self.ticks > self.after) {
|
|
@@ -975,6 +1025,8 @@ pub fn sector_ceil(offset: u64) u64 {
|
|
|
975
1025
|
}
|
|
976
1026
|
|
|
977
1027
|
pub fn checksum(source: []const u8) u128 {
|
|
1028
|
+
@setEvalBranchQuota(4000);
|
|
1029
|
+
|
|
978
1030
|
var target: [32]u8 = undefined;
|
|
979
1031
|
std.crypto.hash.Blake3.hash(source, target[0..], .{});
|
|
980
1032
|
return @bitCast(u128, target[0..@sizeOf(u128)].*);
|
|
@@ -984,22 +1036,30 @@ pub fn quorums(replica_count: u8) struct {
|
|
|
984
1036
|
replication: u8,
|
|
985
1037
|
view_change: u8,
|
|
986
1038
|
} {
|
|
987
|
-
|
|
988
|
-
assert(majority <= replica_count);
|
|
1039
|
+
assert(replica_count > 0);
|
|
989
1040
|
|
|
990
1041
|
assert(constants.quorum_replication_max >= 2);
|
|
991
|
-
|
|
1042
|
+
// For replica_count=2, set quorum_replication=2 even though =1 would intersect.
|
|
1043
|
+
// This improves durability of small clusters.
|
|
1044
|
+
const quorum_replication = if (replica_count == 2) 2 else std.math.min(
|
|
1045
|
+
constants.quorum_replication_max,
|
|
1046
|
+
stdx.div_ceil(replica_count, 2),
|
|
1047
|
+
);
|
|
1048
|
+
assert(quorum_replication <= replica_count);
|
|
992
1049
|
assert(quorum_replication >= 2 or quorum_replication == replica_count);
|
|
993
1050
|
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1051
|
+
// For replica_count=2, set quorum_view_change=2 even though =1 would intersect.
|
|
1052
|
+
// This avoids special cases for a single-replica view-change in Replica.
|
|
1053
|
+
const quorum_view_change =
|
|
1054
|
+
if (replica_count == 2) 2 else replica_count - quorum_replication + 1;
|
|
998
1055
|
// The view change quorum may be more expensive to make the replication quorum cheaper.
|
|
999
1056
|
// The insight is that the replication phase is by far more common than the view change.
|
|
1000
1057
|
// This trade-off allows us to optimize for the common case.
|
|
1001
1058
|
// See the comments in `constants.zig` for further explanation.
|
|
1002
|
-
assert(quorum_view_change
|
|
1059
|
+
assert(quorum_view_change <= replica_count);
|
|
1060
|
+
assert(quorum_view_change >= 2 or quorum_view_change == replica_count);
|
|
1061
|
+
assert(quorum_view_change >= @divFloor(replica_count, 2) + 1);
|
|
1062
|
+
assert(quorum_view_change + quorum_replication > replica_count);
|
|
1003
1063
|
|
|
1004
1064
|
return .{
|
|
1005
1065
|
.replication = quorum_replication,
|
|
@@ -1007,6 +1067,19 @@ pub fn quorums(replica_count: u8) struct {
|
|
|
1007
1067
|
};
|
|
1008
1068
|
}
|
|
1009
1069
|
|
|
1070
|
+
test "quorums" {
|
|
1071
|
+
if (constants.quorum_replication_max != 3) return error.SkipZigTest;
|
|
1072
|
+
|
|
1073
|
+
const expect_replication = [_]u8{ 1, 2, 2, 2, 3, 3, 3, 3 };
|
|
1074
|
+
const expect_view_change = [_]u8{ 1, 2, 2, 3, 3, 4, 5, 6 };
|
|
1075
|
+
|
|
1076
|
+
for (expect_replication[0..]) |_, i| {
|
|
1077
|
+
const actual = quorums(@intCast(u8, i) + 1);
|
|
1078
|
+
try std.testing.expectEqual(actual.replication, expect_replication[i]);
|
|
1079
|
+
try std.testing.expectEqual(actual.view_change, expect_view_change[i]);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1010
1083
|
pub const Headers = struct {
|
|
1011
1084
|
pub const Array = std.BoundedArray(Header, constants.view_change_headers_max);
|
|
1012
1085
|
/// The SuperBlock's persisted VSR headers.
|