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.
- package/README.md +97 -78
- package/dist/benchmark.js +96 -94
- package/dist/benchmark.js.map +1 -1
- package/dist/index.d.ts +82 -82
- package/dist/index.js +74 -93
- package/dist/index.js.map +1 -1
- package/dist/test.js +134 -111
- package/dist/test.js.map +1 -1
- package/package.json +3 -2
- package/scripts/download_node_headers.sh +3 -1
- package/src/benchmark.ts +114 -118
- package/src/index.ts +102 -111
- package/src/node.zig +55 -63
- package/src/test.ts +146 -125
- package/src/tigerbeetle/scripts/benchmark.bat +46 -0
- package/src/tigerbeetle/scripts/benchmark.sh +5 -0
- package/src/tigerbeetle/scripts/install_zig.bat +109 -109
- package/src/tigerbeetle/scripts/install_zig.sh +8 -4
- package/src/tigerbeetle/scripts/vopr.bat +47 -47
- package/src/tigerbeetle/scripts/vopr.sh +2 -2
- package/src/tigerbeetle/src/benchmark.zig +65 -102
- package/src/tigerbeetle/src/cli.zig +39 -18
- package/src/tigerbeetle/src/config.zig +44 -25
- package/src/tigerbeetle/src/demo.zig +2 -15
- package/src/tigerbeetle/src/demo_01_create_accounts.zig +10 -10
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +5 -3
- package/src/tigerbeetle/src/{demo_04_create_transfers_two_phase_commit.zig → demo_04_create_pending_transfers.zig} +18 -12
- package/src/tigerbeetle/src/demo_05_post_pending_transfers.zig +37 -0
- package/src/tigerbeetle/src/demo_06_void_pending_transfers.zig +24 -0
- package/src/tigerbeetle/src/demo_07_lookup_transfers.zig +1 -1
- package/src/tigerbeetle/src/io/benchmark.zig +24 -49
- package/src/tigerbeetle/src/io/darwin.zig +175 -44
- package/src/tigerbeetle/src/io/linux.zig +177 -72
- package/src/tigerbeetle/src/io/test.zig +61 -39
- package/src/tigerbeetle/src/io/windows.zig +1161 -0
- package/src/tigerbeetle/src/io.zig +2 -0
- package/src/tigerbeetle/src/main.zig +31 -10
- package/src/tigerbeetle/src/message_bus.zig +49 -61
- package/src/tigerbeetle/src/message_pool.zig +66 -57
- package/src/tigerbeetle/src/ring_buffer.zig +55 -3
- package/src/tigerbeetle/src/simulator.zig +108 -12
- package/src/tigerbeetle/src/state_machine.zig +1813 -816
- package/src/tigerbeetle/src/storage.zig +0 -230
- package/src/tigerbeetle/src/test/cluster.zig +168 -38
- package/src/tigerbeetle/src/test/message_bus.zig +4 -3
- package/src/tigerbeetle/src/test/network.zig +13 -16
- package/src/tigerbeetle/src/test/packet_simulator.zig +14 -1
- package/src/tigerbeetle/src/test/state_checker.zig +6 -3
- package/src/tigerbeetle/src/test/state_machine.zig +8 -7
- package/src/tigerbeetle/src/test/storage.zig +99 -40
- package/src/tigerbeetle/src/tigerbeetle.zig +108 -101
- package/src/tigerbeetle/src/time.zig +58 -11
- package/src/tigerbeetle/src/vsr/client.zig +18 -32
- package/src/tigerbeetle/src/vsr/clock.zig +1 -1
- package/src/tigerbeetle/src/vsr/journal.zig +1388 -464
- package/src/tigerbeetle/src/vsr/replica.zig +1340 -576
- package/src/tigerbeetle/src/vsr.zig +452 -40
- package/src/translate.zig +10 -0
- package/src/tigerbeetle/src/demo_05_accept_transfers.zig +0 -23
- package/src/tigerbeetle/src/demo_06_reject_transfers.zig +0 -17
- 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 =
|
|
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,
|
|
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
|
-
|
|
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
|
|
182
|
-
replica.
|
|
183
|
-
|
|
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()
|
|
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()
|
|
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.
|
|
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,
|