tigerbeetle-node 0.5.2 → 0.8.1

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 (61) hide show
  1. package/README.md +97 -78
  2. package/dist/benchmark.js +96 -94
  3. package/dist/benchmark.js.map +1 -1
  4. package/dist/index.d.ts +82 -82
  5. package/dist/index.js +74 -93
  6. package/dist/index.js.map +1 -1
  7. package/dist/test.js +134 -111
  8. package/dist/test.js.map +1 -1
  9. package/package.json +3 -2
  10. package/scripts/download_node_headers.sh +3 -1
  11. package/src/benchmark.ts +114 -118
  12. package/src/index.ts +102 -111
  13. package/src/node.zig +55 -63
  14. package/src/test.ts +146 -125
  15. package/src/tigerbeetle/scripts/benchmark.bat +46 -0
  16. package/src/tigerbeetle/scripts/benchmark.sh +5 -0
  17. package/src/tigerbeetle/scripts/install_zig.bat +109 -109
  18. package/src/tigerbeetle/scripts/install_zig.sh +8 -4
  19. package/src/tigerbeetle/scripts/vopr.bat +47 -47
  20. package/src/tigerbeetle/scripts/vopr.sh +2 -2
  21. package/src/tigerbeetle/src/benchmark.zig +65 -102
  22. package/src/tigerbeetle/src/cli.zig +39 -18
  23. package/src/tigerbeetle/src/config.zig +44 -25
  24. package/src/tigerbeetle/src/demo.zig +2 -15
  25. package/src/tigerbeetle/src/demo_01_create_accounts.zig +10 -10
  26. package/src/tigerbeetle/src/demo_03_create_transfers.zig +5 -3
  27. package/src/tigerbeetle/src/{demo_04_create_transfers_two_phase_commit.zig → demo_04_create_pending_transfers.zig} +18 -12
  28. package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +37 -0
  29. package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +24 -0
  30. package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +1 -1
  31. package/src/tigerbeetle/src/io/benchmark.zig +24 -49
  32. package/src/tigerbeetle/src/io/darwin.zig +175 -44
  33. package/src/tigerbeetle/src/io/linux.zig +177 -72
  34. package/src/tigerbeetle/src/io/test.zig +61 -39
  35. package/src/tigerbeetle/src/io/windows.zig +1161 -0
  36. package/src/tigerbeetle/src/io.zig +2 -0
  37. package/src/tigerbeetle/src/main.zig +31 -10
  38. package/src/tigerbeetle/src/message_bus.zig +49 -61
  39. package/src/tigerbeetle/src/message_pool.zig +66 -57
  40. package/src/tigerbeetle/src/ring_buffer.zig +55 -3
  41. package/src/tigerbeetle/src/simulator.zig +108 -12
  42. package/src/tigerbeetle/src/state_machine.zig +1813 -816
  43. package/src/tigerbeetle/src/storage.zig +0 -230
  44. package/src/tigerbeetle/src/test/cluster.zig +168 -38
  45. package/src/tigerbeetle/src/test/message_bus.zig +4 -3
  46. package/src/tigerbeetle/src/test/network.zig +13 -16
  47. package/src/tigerbeetle/src/test/packet_simulator.zig +14 -1
  48. package/src/tigerbeetle/src/test/state_checker.zig +6 -3
  49. package/src/tigerbeetle/src/test/state_machine.zig +8 -7
  50. package/src/tigerbeetle/src/test/storage.zig +99 -40
  51. package/src/tigerbeetle/src/tigerbeetle.zig +108 -101
  52. package/src/tigerbeetle/src/time.zig +58 -11
  53. package/src/tigerbeetle/src/vsr/client.zig +18 -32
  54. package/src/tigerbeetle/src/vsr/clock.zig +1 -1
  55. package/src/tigerbeetle/src/vsr/journal.zig +1388 -464
  56. package/src/tigerbeetle/src/vsr/replica.zig +1340 -576
  57. package/src/tigerbeetle/src/vsr.zig +452 -40
  58. package/src/translate.zig +10 -0
  59. package/src/tigerbeetle/src/demo_05_accept_transfers.zig +0 -23
  60. package/src/tigerbeetle/src/demo_06_reject_transfers.zig +0 -17
  61. package/src/tigerbeetle/src/format_test.zig +0 -69
@@ -20,6 +20,8 @@ const output = std.log.scoped(.state_checker);
20
20
  /// This will run much slower but will trace all logic across the cluster.
21
21
  const log_state_transitions_only = builtin.mode != .Debug;
22
22
 
23
+ const log_health = std.log.scoped(.health);
24
+
23
25
  /// You can fine tune your log levels even further (debug/info/notice/warn/err/crit/alert/emerg):
24
26
  pub const log_level: std.log.Level = if (log_state_transitions_only) .info else .debug;
25
27
 
@@ -64,7 +66,6 @@ pub fn main() !void {
64
66
  const node_count = replica_count + client_count;
65
67
 
66
68
  const ticks_max = 100_000_000;
67
- const transitions_max = config.journal_size_max / config.message_size_max;
68
69
  const request_probability = 1 + random.uintLessThan(u8, 99);
69
70
  const idle_on_probability = random.uintLessThan(u8, 20);
70
71
  const idle_off_probability = 10 + random.uintLessThan(u8, 10);
@@ -84,7 +85,7 @@ pub fn main() !void {
84
85
  .one_way_delay_mean = 3 + random.uintLessThan(u16, 10),
85
86
  .one_way_delay_min = random.uintLessThan(u16, 3),
86
87
  .packet_loss_probability = random.uintLessThan(u8, 30),
87
- .path_maximum_capacity = 20 + random.uintLessThan(u8, 20),
88
+ .path_maximum_capacity = 2 + random.uintLessThan(u8, 19),
88
89
  .path_clog_duration_mean = random.uintLessThan(u16, 500),
89
90
  .path_clog_probability = random.uintLessThan(u8, 2),
90
91
  .packet_replay_probability = random.uintLessThan(u8, 50),
@@ -101,10 +102,16 @@ pub fn main() !void {
101
102
  .read_latency_min = random.uintLessThan(u16, 3),
102
103
  .read_latency_mean = 3 + random.uintLessThan(u16, 10),
103
104
  .write_latency_min = random.uintLessThan(u16, 3),
104
- .write_latency_mean = 3 + random.uintLessThan(u16, 10),
105
+ .write_latency_mean = 3 + random.uintLessThan(u16, 100),
105
106
  .read_fault_probability = random.uintLessThan(u8, 10),
106
107
  .write_fault_probability = random.uintLessThan(u8, 10),
107
108
  },
109
+ .health_options = .{
110
+ .crash_probability = 0.0001,
111
+ .crash_stability = random.uintLessThan(u32, 1_000),
112
+ .restart_probability = 0.01,
113
+ .restart_stability = random.uintLessThan(u32, 1_000),
114
+ },
108
115
  });
109
116
  defer cluster.destroy();
110
117
 
@@ -143,6 +150,10 @@ pub fn main() !void {
143
150
  \\ write_latency_mean={}
144
151
  \\ read_fault_probability={}%
145
152
  \\ write_fault_probability={}%
153
+ \\ crash_probability={d}%
154
+ \\ crash_stability={} ticks
155
+ \\ restart_probability={d}%
156
+ \\ restart_stability={} ticks
146
157
  \\
147
158
  , .{
148
159
  seed,
@@ -169,26 +180,105 @@ pub fn main() !void {
169
180
  cluster.options.storage_options.write_latency_mean,
170
181
  cluster.options.storage_options.read_fault_probability,
171
182
  cluster.options.storage_options.write_fault_probability,
183
+ cluster.options.health_options.crash_probability * 100,
184
+ cluster.options.health_options.crash_stability,
185
+ cluster.options.health_options.restart_probability * 100,
186
+ cluster.options.health_options.restart_stability,
172
187
  });
173
188
 
174
189
  var requests_sent: u64 = 0;
175
190
  var idle = false;
176
191
 
192
+ // The minimum number of healthy replicas required for a crashed replica to be able to recover.
193
+ const replica_normal_min = replicas: {
194
+ if (replica_count == 1) {
195
+ // A cluster of 1 can crash safely (as long as there is no disk corruption) since it
196
+ // does not run the recovery protocol.
197
+ break :replicas 0;
198
+ } else {
199
+ break :replicas cluster.replicas[0].quorum_view_change;
200
+ }
201
+ };
202
+
203
+ // Disable most faults at startup, so that the replicas don't get stuck in recovery mode.
204
+ for (cluster.storages) |*storage, i| {
205
+ storage.faulty = replica_normal_min <= i;
206
+ }
207
+
208
+ // TODO When storage is supported, run more transitions than fit in the journal.
209
+ const transitions_max = config.journal_slot_count / 2;
177
210
  var tick: u64 = 0;
178
211
  while (tick < ticks_max) : (tick += 1) {
179
- for (cluster.storages) |*storage| storage.tick();
212
+ const health_options = &cluster.options.health_options;
213
+ // The maximum number of replicas that can crash, with the cluster still able to recover.
214
+ var crashes = cluster.replica_normal_count() -| replica_normal_min;
215
+
216
+ for (cluster.storages) |*storage, replica| {
217
+ if (cluster.replicas[replica].journal.recovered) {
218
+
219
+ // TODO Remove this workaround when VSR recovery protocol is disabled.
220
+ // When only the minimum number of replicas are healthy (no more crashes allowed),
221
+ // disable storage faults on all healthy replicas.
222
+ //
223
+ // This is a workaround to avoid the deadlock that occurs when (for example) in a
224
+ // cluster of 3 replicas, one is down, another has a corrupt prepare, and the last does
225
+ // not have the prepare. The two healthy replicas can never complete a view change,
226
+ // because two replicas are not enough to nack, and the unhealthy replica cannot
227
+ // complete the VSR recovery protocol either.
228
+ if (cluster.health[replica] == .up and crashes == 0) {
229
+ storage.faulty = false;
230
+ } else {
231
+ // When a journal recovers for the first time, enable its storage faults.
232
+ // Future crashes will recover in the presence of faults.
233
+ storage.faulty = true;
234
+ }
235
+ }
236
+ storage.tick();
237
+ }
180
238
 
181
- for (cluster.replicas) |*replica, i| {
182
- replica.tick();
183
- cluster.state_checker.check_state(@intCast(u8, i));
239
+ for (cluster.replicas) |*replica| {
240
+ switch (cluster.health[replica.replica]) {
241
+ .up => |*ticks| {
242
+ ticks.* -|= 1;
243
+ replica.tick();
244
+ cluster.state_checker.check_state(replica.replica);
245
+
246
+ if (ticks.* != 0) continue;
247
+ if (crashes == 0) continue;
248
+ if (cluster.storages[replica.replica].writes.count() == 0) {
249
+ if (!chance_f64(random, health_options.crash_probability)) continue;
250
+ } else {
251
+ if (!chance_f64(random, health_options.crash_probability * 10.0)) continue;
252
+ }
253
+
254
+ if (!try cluster.crash_replica(replica.replica)) continue;
255
+ log_health.debug("crash replica={}", .{replica.replica});
256
+ crashes -= 1;
257
+ },
258
+ .down => |*ticks| {
259
+ ticks.* -|= 1;
260
+ // Keep ticking the time so that it won't have diverged too far to synchronize
261
+ // when the replica restarts.
262
+ replica.clock.time.tick();
263
+ assert(replica.status == .recovering);
264
+ if (ticks.* == 0 and chance_f64(random, health_options.restart_probability)) {
265
+ cluster.health[replica.replica] = .{ .up = health_options.restart_stability };
266
+ log_health.debug("restart replica={}", .{replica.replica});
267
+ }
268
+ },
269
+ }
184
270
  }
185
271
 
186
- cluster.network.packet_simulator.tick();
272
+ cluster.network.packet_simulator.tick(cluster.health);
187
273
 
188
274
  for (cluster.clients) |*client| client.tick();
189
275
 
190
276
  if (cluster.state_checker.transitions == transitions_max) {
191
- if (cluster.state_checker.convergence()) break;
277
+ if (cluster.state_checker.convergence() and
278
+ cluster.replica_up_count() == replica_count)
279
+ {
280
+ break;
281
+ }
192
282
  continue;
193
283
  } else {
194
284
  assert(cluster.state_checker.transitions < transitions_max);
@@ -213,7 +303,7 @@ pub fn main() !void {
213
303
 
214
304
  assert(cluster.state_checker.convergence());
215
305
 
216
- output.info("\n PASSED", .{});
306
+ output.info("\n PASSED ({} ticks)", .{tick});
217
307
  }
218
308
 
219
309
  /// Returns true, `p` percent of the time, else false.
@@ -222,6 +312,12 @@ fn chance(random: std.rand.Random, p: u8) bool {
222
312
  return random.uintLessThan(u8, 100) < p;
223
313
  }
224
314
 
315
+ /// Returns true, `p` percent of the time, else false.
316
+ fn chance_f64(random: std.rand.Random, p: f64) bool {
317
+ assert(p <= 100.0);
318
+ return random.float(f64) < p;
319
+ }
320
+
225
321
  /// Returns the next argument for the simulator or null (if none available)
226
322
  fn args_next(args: *std.process.ArgIterator, allocator: std.mem.Allocator) ?[:0]const u8 {
227
323
  const err_or_bytes = args.next(allocator) orelse return null;
@@ -244,7 +340,7 @@ fn send_request(random: std.rand.Random) bool {
244
340
  if (client.request_queue.full()) return false;
245
341
  if (checker_request_queue.full()) return false;
246
342
 
247
- const message = client.get_message() orelse return false;
343
+ const message = client.get_message();
248
344
  defer client.unref(message);
249
345
 
250
346
  const body_size_max = config.message_size_max - @sizeOf(Header);
@@ -265,7 +361,7 @@ fn send_request(random: std.rand.Random) bool {
265
361
  // While hashing the client ID with the request body prevents input collisions across clients,
266
362
  // it's still possible for the same client to generate the same body, and therefore input hash.
267
363
  const client_input = StateMachine.hash(client.id, body);
268
- checker_request_queue.push(client_input) catch unreachable;
364
+ checker_request_queue.push_assume_capacity(client_input);
269
365
  std.log.scoped(.test_client).debug("client {} sending input={x}", .{
270
366
  client_index,
271
367
  client_input,