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.
Files changed (45) hide show
  1. package/README.md +212 -196
  2. package/dist/.client.node.sha256 +1 -1
  3. package/package.json +3 -2
  4. package/src/node.zig +1 -0
  5. package/src/tigerbeetle/scripts/benchmark.bat +9 -2
  6. package/src/tigerbeetle/scripts/benchmark.sh +1 -1
  7. package/src/tigerbeetle/scripts/fail_on_diff.sh +9 -0
  8. package/src/tigerbeetle/scripts/fuzz_loop_hash_log.sh +12 -0
  9. package/src/tigerbeetle/scripts/scripts/benchmark.bat +9 -2
  10. package/src/tigerbeetle/scripts/scripts/benchmark.sh +1 -1
  11. package/src/tigerbeetle/scripts/scripts/fail_on_diff.sh +9 -0
  12. package/src/tigerbeetle/scripts/scripts/fuzz_loop_hash_log.sh +12 -0
  13. package/src/tigerbeetle/src/benchmark.zig +253 -231
  14. package/src/tigerbeetle/src/config.zig +2 -3
  15. package/src/tigerbeetle/src/constants.zig +2 -10
  16. package/src/tigerbeetle/src/io/linux.zig +15 -6
  17. package/src/tigerbeetle/src/lsm/forest.zig +1 -0
  18. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +63 -14
  19. package/src/tigerbeetle/src/lsm/groove.zig +134 -70
  20. package/src/tigerbeetle/src/lsm/level_iterator.zig +2 -2
  21. package/src/tigerbeetle/src/lsm/manifest_level.zig +1 -0
  22. package/src/tigerbeetle/src/lsm/posted_groove.zig +7 -4
  23. package/src/tigerbeetle/src/lsm/segmented_array.zig +1 -0
  24. package/src/tigerbeetle/src/lsm/table.zig +29 -51
  25. package/src/tigerbeetle/src/lsm/table_immutable.zig +6 -17
  26. package/src/tigerbeetle/src/lsm/table_iterator.zig +2 -2
  27. package/src/tigerbeetle/src/lsm/table_mutable.zig +9 -26
  28. package/src/tigerbeetle/src/lsm/test.zig +1 -0
  29. package/src/tigerbeetle/src/lsm/tree.zig +2 -26
  30. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +7 -2
  31. package/src/tigerbeetle/src/message_bus.zig +1 -0
  32. package/src/tigerbeetle/src/simulator.zig +14 -3
  33. package/src/tigerbeetle/src/state_machine/auditor.zig +1 -0
  34. package/src/tigerbeetle/src/state_machine.zig +402 -184
  35. package/src/tigerbeetle/src/stdx.zig +9 -0
  36. package/src/tigerbeetle/src/testing/cluster.zig +1 -0
  37. package/src/tigerbeetle/src/testing/packet_simulator.zig +19 -9
  38. package/src/tigerbeetle/src/testing/state_machine.zig +1 -0
  39. package/src/tigerbeetle/src/unit_tests.zig +20 -22
  40. package/src/tigerbeetle/src/vsr/README.md +1 -1
  41. package/src/tigerbeetle/src/vsr/client.zig +4 -4
  42. package/src/tigerbeetle/src/vsr/clock.zig +2 -0
  43. package/src/tigerbeetle/src/vsr/journal.zig +2 -0
  44. package/src/tigerbeetle/src/vsr/replica.zig +481 -246
  45. 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.view != 0) return "view != 0";
356
- if (self.client != 0) {
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 > 0) {
372
- if (self.view != 0) return "view != 0";
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 != 0) return "timestamp != 0";
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
- // TODO Assert that pong responses from a replica do not echo the pinging client's ID.
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
- const majority = @divFloor(replica_count, 2) + 1;
988
- assert(majority <= replica_count);
1039
+ assert(replica_count > 0);
989
1040
 
990
1041
  assert(constants.quorum_replication_max >= 2);
991
- const quorum_replication = std.math.min(constants.quorum_replication_max, majority);
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
- const quorum_view_change = std.math.max(
995
- replica_count - quorum_replication + 1,
996
- majority,
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 >= majority);
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.