tigerbeetle-node 0.10.0 → 0.11.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 (109) hide show
  1. package/README.md +302 -101
  2. package/dist/index.d.ts +70 -72
  3. package/dist/index.js +70 -72
  4. package/dist/index.js.map +1 -1
  5. package/package.json +9 -8
  6. package/scripts/download_node_headers.sh +14 -7
  7. package/src/index.ts +6 -10
  8. package/src/node.zig +6 -3
  9. package/src/tigerbeetle/scripts/benchmark.sh +4 -4
  10. package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
  11. package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
  12. package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
  13. package/src/tigerbeetle/scripts/install.sh +19 -4
  14. package/src/tigerbeetle/scripts/install_zig.bat +5 -1
  15. package/src/tigerbeetle/scripts/install_zig.sh +24 -14
  16. package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
  17. package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
  18. package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
  19. package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
  20. package/src/tigerbeetle/scripts/validate_docs.sh +17 -0
  21. package/src/tigerbeetle/src/benchmark.zig +29 -13
  22. package/src/tigerbeetle/src/c/tb_client/context.zig +248 -47
  23. package/src/tigerbeetle/src/c/tb_client/echo_client.zig +108 -0
  24. package/src/tigerbeetle/src/c/tb_client/packet.zig +2 -2
  25. package/src/tigerbeetle/src/c/tb_client/signal.zig +2 -4
  26. package/src/tigerbeetle/src/c/tb_client/thread.zig +17 -257
  27. package/src/tigerbeetle/src/c/tb_client.h +118 -84
  28. package/src/tigerbeetle/src/c/tb_client.zig +88 -23
  29. package/src/tigerbeetle/src/c/tb_client_header_test.zig +135 -0
  30. package/src/tigerbeetle/src/c/test.zig +371 -1
  31. package/src/tigerbeetle/src/cli.zig +37 -7
  32. package/src/tigerbeetle/src/config.zig +58 -17
  33. package/src/tigerbeetle/src/demo.zig +5 -2
  34. package/src/tigerbeetle/src/demo_01_create_accounts.zig +1 -1
  35. package/src/tigerbeetle/src/demo_03_create_transfers.zig +13 -0
  36. package/src/tigerbeetle/src/ewah.zig +11 -33
  37. package/src/tigerbeetle/src/ewah_benchmark.zig +8 -9
  38. package/src/tigerbeetle/src/io/linux.zig +1 -1
  39. package/src/tigerbeetle/src/lsm/README.md +308 -0
  40. package/src/tigerbeetle/src/lsm/binary_search.zig +137 -10
  41. package/src/tigerbeetle/src/lsm/bloom_filter.zig +43 -0
  42. package/src/tigerbeetle/src/lsm/compaction.zig +376 -397
  43. package/src/tigerbeetle/src/lsm/composite_key.zig +2 -0
  44. package/src/tigerbeetle/src/lsm/eytzinger.zig +1 -1
  45. package/src/tigerbeetle/src/{eytzinger_benchmark.zig → lsm/eytzinger_benchmark.zig} +34 -21
  46. package/src/tigerbeetle/src/lsm/forest.zig +21 -447
  47. package/src/tigerbeetle/src/lsm/forest_fuzz.zig +414 -0
  48. package/src/tigerbeetle/src/lsm/grid.zig +170 -76
  49. package/src/tigerbeetle/src/lsm/groove.zig +197 -133
  50. package/src/tigerbeetle/src/lsm/k_way_merge.zig +40 -18
  51. package/src/tigerbeetle/src/lsm/level_iterator.zig +28 -9
  52. package/src/tigerbeetle/src/lsm/manifest.zig +93 -180
  53. package/src/tigerbeetle/src/lsm/manifest_level.zig +161 -454
  54. package/src/tigerbeetle/src/lsm/manifest_log.zig +243 -356
  55. package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +665 -0
  56. package/src/tigerbeetle/src/lsm/node_pool.zig +4 -0
  57. package/src/tigerbeetle/src/lsm/posted_groove.zig +65 -76
  58. package/src/tigerbeetle/src/lsm/segmented_array.zig +580 -251
  59. package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
  60. package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
  61. package/src/tigerbeetle/src/lsm/set_associative_cache.zig +62 -12
  62. package/src/tigerbeetle/src/lsm/table.zig +115 -68
  63. package/src/tigerbeetle/src/lsm/table_immutable.zig +30 -23
  64. package/src/tigerbeetle/src/lsm/table_iterator.zig +27 -17
  65. package/src/tigerbeetle/src/lsm/table_mutable.zig +63 -12
  66. package/src/tigerbeetle/src/lsm/test.zig +61 -56
  67. package/src/tigerbeetle/src/lsm/tree.zig +450 -407
  68. package/src/tigerbeetle/src/lsm/tree_fuzz.zig +461 -0
  69. package/src/tigerbeetle/src/main.zig +83 -8
  70. package/src/tigerbeetle/src/message_bus.zig +20 -9
  71. package/src/tigerbeetle/src/message_pool.zig +22 -19
  72. package/src/tigerbeetle/src/ring_buffer.zig +7 -3
  73. package/src/tigerbeetle/src/simulator.zig +179 -119
  74. package/src/tigerbeetle/src/state_machine.zig +381 -246
  75. package/src/tigerbeetle/src/static_allocator.zig +65 -0
  76. package/src/tigerbeetle/src/storage.zig +3 -7
  77. package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
  78. package/src/tigerbeetle/src/test/accounting/workload.zig +823 -0
  79. package/src/tigerbeetle/src/test/cluster.zig +33 -81
  80. package/src/tigerbeetle/src/test/conductor.zig +366 -0
  81. package/src/tigerbeetle/src/test/fuzz.zig +121 -0
  82. package/src/tigerbeetle/src/test/id.zig +89 -0
  83. package/src/tigerbeetle/src/test/network.zig +45 -19
  84. package/src/tigerbeetle/src/test/packet_simulator.zig +40 -29
  85. package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
  86. package/src/tigerbeetle/src/test/state_checker.zig +91 -69
  87. package/src/tigerbeetle/src/test/state_machine.zig +11 -35
  88. package/src/tigerbeetle/src/test/storage.zig +470 -106
  89. package/src/tigerbeetle/src/test/storage_checker.zig +204 -0
  90. package/src/tigerbeetle/src/tigerbeetle.zig +15 -16
  91. package/src/tigerbeetle/src/unit_tests.zig +13 -1
  92. package/src/tigerbeetle/src/util.zig +97 -11
  93. package/src/tigerbeetle/src/vopr.zig +495 -0
  94. package/src/tigerbeetle/src/vsr/client.zig +21 -3
  95. package/src/tigerbeetle/src/vsr/journal.zig +293 -212
  96. package/src/tigerbeetle/src/vsr/replica.zig +1086 -515
  97. package/src/tigerbeetle/src/vsr/superblock.zig +382 -637
  98. package/src/tigerbeetle/src/vsr/superblock_client_table.zig +14 -16
  99. package/src/tigerbeetle/src/vsr/superblock_free_set.zig +416 -153
  100. package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +332 -0
  101. package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +349 -0
  102. package/src/tigerbeetle/src/vsr/superblock_manifest.zig +62 -12
  103. package/src/tigerbeetle/src/vsr/superblock_quorums.zig +394 -0
  104. package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +312 -0
  105. package/src/tigerbeetle/src/vsr.zig +94 -60
  106. package/src/tigerbeetle/scripts/vopr.bat +0 -48
  107. package/src/tigerbeetle/scripts/vopr.sh +0 -33
  108. package/src/tigerbeetle/src/benchmark_array_search.zig +0 -317
  109. package/src/tigerbeetle/src/benchmarks/perf.zig +0 -299
@@ -1 +1,371 @@
1
- // TODO
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+
4
+ const testing = std.testing;
5
+
6
+ const c = @cImport(@cInclude("tb_client.h"));
7
+
8
+ const util = @import("../util.zig");
9
+ const config = @import("../config.zig");
10
+ const Packet = @import("tb_client/packet.zig").Packet;
11
+
12
+ const Mutex = std.Thread.Mutex;
13
+ const Condition = std.Thread.Condition;
14
+
15
+ fn RequestContextType(comptime request_size_max: comptime_int) type {
16
+ return struct {
17
+ const Self = @This();
18
+
19
+ completion: *Completion,
20
+ sent_data: [request_size_max]u8 = undefined,
21
+ sent_data_size: u32,
22
+ packet: *Packet = undefined,
23
+ reply: ?struct {
24
+ tb_context: usize,
25
+ tb_client: c.tb_client_t,
26
+ tb_packet: *c.tb_packet_t,
27
+ result: ?[request_size_max]u8,
28
+ result_len: u32,
29
+ } = null,
30
+
31
+ pub fn on_complete(
32
+ tb_context: usize,
33
+ tb_client: c.tb_client_t,
34
+ tb_packet: [*c]c.tb_packet_t,
35
+ result_ptr: [*c]const u8,
36
+ result_len: u32,
37
+ ) callconv(.C) void {
38
+ var self = @ptrCast(*Self, @alignCast(@alignOf(*Self), tb_packet.*.user_data.?));
39
+ defer self.completion.complete();
40
+
41
+ self.reply = .{
42
+ .tb_context = tb_context,
43
+ .tb_client = tb_client,
44
+ .tb_packet = tb_packet,
45
+ .result = if (result_ptr != null and result_len > 0) blk: {
46
+ // Copy the message's body to the context buffer:
47
+ assert(result_len <= request_size_max);
48
+ var writable: [request_size_max]u8 = undefined;
49
+ const readable = @ptrCast([*]const u8, result_ptr.?);
50
+ util.copy_disjoint(.inexact, u8, &writable, readable[0..result_len]);
51
+ break :blk writable;
52
+ } else null,
53
+ .result_len = result_len,
54
+ };
55
+ }
56
+ };
57
+ }
58
+
59
+ // Notifies the main thread when all pending requests are completed.
60
+ const Completion = struct {
61
+ const Self = @This();
62
+
63
+ pending: usize,
64
+ mutex: Mutex = .{},
65
+ cond: Condition = .{},
66
+
67
+ pub fn complete(self: *Self) void {
68
+ self.mutex.lock();
69
+ defer self.mutex.unlock();
70
+
71
+ assert(self.pending > 0);
72
+ self.pending -= 1;
73
+ self.cond.signal();
74
+ }
75
+
76
+ pub fn wait_pending(self: *Self) void {
77
+ self.mutex.lock();
78
+ defer self.mutex.unlock();
79
+
80
+ while (self.pending > 0)
81
+ self.cond.wait(&self.mutex);
82
+ }
83
+ };
84
+
85
+ // When initialized with tb_client_init_echo, the c_client uses a test context that echoes
86
+ // the data back without creating an actual client or connecting to a cluster.
87
+ //
88
+ // This same test should be implemented by all the target programming languages, asserting that:
89
+ // 1. the c_client api was initialized correctly.
90
+ // 2. the application can submit messages and receive replies through the completion callback.
91
+ // 3. the data marshaling is correct, and exactly the same data sent was received back.
92
+ test "c_client echo" {
93
+ // Using the create_accounts operation for this test.
94
+ const RequestContext = RequestContextType(config.message_body_size_max);
95
+ const create_accounts_operation: u8 = c.TB_OP_CREATE_ACCOUNTS;
96
+ const event_size = @sizeOf(c.tb_account_t);
97
+ const event_request_max = @divFloor(config.message_body_size_max, event_size);
98
+
99
+ // Initializing an echo client for testing purposes.
100
+ // We ensure that the retry mechanism is being tested
101
+ // by allowing more simultaneous packets than "client_request_queue_max".
102
+ var tb_client: c.tb_client_t = undefined;
103
+ var tb_packet_list: c.tb_packet_list_t = undefined;
104
+ const cluster_id = 0;
105
+ const address = "3000";
106
+ const packets_count: u32 = config.client_request_queue_max * 2;
107
+ const tb_context: usize = 42;
108
+ const result = c.tb_client_init_echo(
109
+ &tb_client,
110
+ &tb_packet_list,
111
+ cluster_id,
112
+ address,
113
+ @intCast(u32, address.len),
114
+ packets_count,
115
+ tb_context,
116
+ RequestContext.on_complete,
117
+ );
118
+
119
+ try testing.expectEqual(@as(c_uint, c.TB_STATUS_SUCCESS), result);
120
+ defer c.tb_client_deinit(tb_client);
121
+
122
+ var packet_list = @bitCast(Packet.List, tb_packet_list);
123
+ var prng = std.rand.DefaultPrng.init(tb_context);
124
+
125
+ var requests: []RequestContext = try testing.allocator.alloc(RequestContext, packets_count);
126
+ defer testing.allocator.free(requests);
127
+
128
+ // Repeating the same test multiple times to stress the
129
+ // cycle of message exhaustion followed by completions.
130
+ const repetitions_max = 100;
131
+ var repetition: u32 = 0;
132
+ while (repetition < repetitions_max) : (repetition += 1) {
133
+ var completion = Completion{ .pending = packets_count };
134
+
135
+ // Submitting some random data to be echoed back:
136
+ for (requests) |*request| {
137
+ request.* = .{
138
+ .completion = &completion,
139
+ .sent_data_size = prng.random().intRangeAtMost(u32, 1, event_request_max) * event_size,
140
+ };
141
+ prng.random().bytes(request.sent_data[0..request.sent_data_size]);
142
+
143
+ request.packet = blk: {
144
+ var packet = packet_list.pop().?;
145
+ packet.operation = create_accounts_operation;
146
+ packet.user_data = request;
147
+ packet.data = &request.sent_data;
148
+ packet.data_size = request.sent_data_size;
149
+ packet.next = null;
150
+ packet.status = .ok;
151
+ break :blk packet;
152
+ };
153
+
154
+ var list = @bitCast(c.tb_packet_list_t, Packet.List.from(request.packet));
155
+ c.tb_client_submit(tb_client, &list);
156
+ }
157
+
158
+ // Waiting until the c_client thread has processed all submitted requests:
159
+ completion.wait_pending();
160
+
161
+ // Checking if the received echo matches the data we sent:
162
+ for (requests) |*request| {
163
+ defer packet_list.push(Packet.List.from(request.packet));
164
+
165
+ try testing.expect(request.reply != null);
166
+ try testing.expectEqual(tb_context, request.reply.?.tb_context);
167
+ try testing.expectEqual(tb_client, request.reply.?.tb_client);
168
+ try testing.expectEqual(c.TB_PACKET_OK, @enumToInt(request.packet.status));
169
+ try testing.expectEqual(@ptrToInt(request.packet), @ptrToInt(request.reply.?.tb_packet));
170
+ try testing.expect(request.reply.?.result != null);
171
+ try testing.expectEqual(request.sent_data_size, request.reply.?.result_len);
172
+
173
+ const sent_data = request.sent_data[0..request.sent_data_size];
174
+ const reply = request.reply.?.result.?[0..request.reply.?.result_len];
175
+ try testing.expectEqualSlices(u8, sent_data, reply);
176
+ }
177
+ }
178
+ }
179
+
180
+ // Asserts the validation rules associated with the "TB_STATUS" enum.
181
+ test "c_client tb_status" {
182
+ const assert_status = struct {
183
+ pub fn action(
184
+ packets_count: u32,
185
+ addresses: []const u8,
186
+ expected_status: c_uint,
187
+ ) !void {
188
+ var tb_client: c.tb_client_t = undefined;
189
+ var tb_packet_list: c.tb_packet_list_t = undefined;
190
+ const cluster_id = 0;
191
+ const tb_context: usize = 0;
192
+ const result = c.tb_client_init_echo(
193
+ &tb_client,
194
+ &tb_packet_list,
195
+ cluster_id,
196
+ addresses.ptr,
197
+ @intCast(u32, addresses.len),
198
+ packets_count,
199
+ tb_context,
200
+ RequestContextType(0).on_complete,
201
+ );
202
+ defer if (result == c.TB_STATUS_SUCCESS) c.tb_client_deinit(tb_client);
203
+
204
+ try testing.expectEqual(expected_status, result);
205
+ }
206
+ }.action;
207
+
208
+ // Valid addresses and packets count should return TB_STATUS_SUCCESS:
209
+ try assert_status(0, "3000", c.TB_STATUS_SUCCESS);
210
+ try assert_status(1, "3000", c.TB_STATUS_SUCCESS);
211
+ try assert_status(32, "127.0.0.1", c.TB_STATUS_SUCCESS);
212
+ try assert_status(128, "127.0.0.1:3000", c.TB_STATUS_SUCCESS);
213
+ try assert_status(512, "3000,3001,3002", c.TB_STATUS_SUCCESS);
214
+ try assert_status(1024, "127.0.0.1,127.0.0.2,172.0.0.3", c.TB_STATUS_SUCCESS);
215
+ try assert_status(4096, "127.0.0.1:3000,127.0.0.1:3002,127.0.0.1:3003", c.TB_STATUS_SUCCESS);
216
+
217
+ // Invalid or empty address should return "TB_STATUS_ADDRESS_INVALID":
218
+ try assert_status(1, "invalid", c.TB_STATUS_ADDRESS_INVALID);
219
+ try assert_status(1, "", c.TB_STATUS_ADDRESS_INVALID);
220
+
221
+ // More addresses thant "replicas_max" should return "TB_STATUS_ADDRESS_LIMIT_EXCEEDED":
222
+ try assert_status(
223
+ 1,
224
+ ("3000," ** config.replicas_max) ++ "3001",
225
+ c.TB_STATUS_ADDRESS_LIMIT_EXCEEDED,
226
+ );
227
+
228
+ // Packets count greater than 4096 should return "TB_STATUS_INVALID_PACKETS_COUNT":
229
+ try assert_status(4097, "3000", c.TB_STATUS_PACKETS_COUNT_INVALID);
230
+ try assert_status(std.math.maxInt(u32), "3000", c.TB_STATUS_PACKETS_COUNT_INVALID);
231
+
232
+ // All other status are not testable.
233
+ }
234
+
235
+ // Asserts the validation rules associated with the "TB_PACKET_STATUS" enum.
236
+ test "c_client tb_packet_status" {
237
+ const RequestContext = RequestContextType(config.message_body_size_max);
238
+
239
+ var tb_client: c.tb_client_t = undefined;
240
+ var tb_packet_list: c.tb_packet_list_t = undefined;
241
+ const cluster_id = 0;
242
+ const address = "3000";
243
+ const packets_count = 1;
244
+ const tb_context: usize = 42;
245
+ const result = c.tb_client_init_echo(
246
+ &tb_client,
247
+ &tb_packet_list,
248
+ cluster_id,
249
+ address,
250
+ @intCast(u32, address.len),
251
+ packets_count,
252
+ tb_context,
253
+ RequestContext.on_complete,
254
+ );
255
+
256
+ try testing.expectEqual(@as(c_uint, c.TB_STATUS_SUCCESS), result);
257
+ defer c.tb_client_deinit(tb_client);
258
+
259
+ const assert_result = struct {
260
+ // Asserts if the packet's status matches the expected status
261
+ // for a given operation and request_size.
262
+ pub fn action(
263
+ client: c.tb_client_t,
264
+ packet_list: *Packet.List,
265
+ operation: u8,
266
+ request_size: u32,
267
+ tb_packet_status_expected: c_int,
268
+ ) !void {
269
+ var completion = Completion{ .pending = 1 };
270
+ var request = RequestContext{
271
+ .completion = &completion,
272
+ .sent_data_size = request_size,
273
+ };
274
+
275
+ request.packet = blk: {
276
+ var packet = packet_list.pop().?;
277
+ packet.operation = operation;
278
+ packet.user_data = &request;
279
+ packet.data = &request.sent_data;
280
+ packet.data_size = request_size;
281
+ packet.next = null;
282
+ packet.status = .ok;
283
+ break :blk packet;
284
+ };
285
+ defer packet_list.push(Packet.List.from(request.packet));
286
+
287
+ var list = @bitCast(c.tb_packet_list_t, Packet.List.from(request.packet));
288
+ c.tb_client_submit(client, &list);
289
+
290
+ completion.wait_pending();
291
+
292
+ try testing.expect(request.reply != null);
293
+ try testing.expectEqual(tb_context, request.reply.?.tb_context);
294
+ try testing.expectEqual(client, request.reply.?.tb_client);
295
+ try testing.expectEqual(@ptrToInt(request.packet), @ptrToInt(request.reply.?.tb_packet));
296
+ try testing.expectEqual(tb_packet_status_expected, @enumToInt(request.packet.status));
297
+ }
298
+ }.action;
299
+
300
+ var packet_list = @ptrCast(*Packet.List, &tb_packet_list);
301
+
302
+ // Messages larger than config.message_body_size_max should return "too_much_data":
303
+ try assert_result(
304
+ tb_client,
305
+ packet_list,
306
+ c.TB_OP_CREATE_TRANSFERS,
307
+ config.message_body_size_max + @sizeOf(c.tb_transfer_t),
308
+ c.TB_PACKET_TOO_MUCH_DATA,
309
+ );
310
+
311
+ // All reserved and unknown operations should return "invalid_operation":
312
+ try assert_result(
313
+ tb_client,
314
+ packet_list,
315
+ 0,
316
+ @sizeOf(u128),
317
+ c.TB_PACKET_INVALID_OPERATION,
318
+ );
319
+ try assert_result(
320
+ tb_client,
321
+ packet_list,
322
+ 1,
323
+ @sizeOf(u128),
324
+ c.TB_PACKET_INVALID_OPERATION,
325
+ );
326
+ try assert_result(
327
+ tb_client,
328
+ packet_list,
329
+ 2,
330
+ @sizeOf(u128),
331
+ c.TB_PACKET_INVALID_OPERATION,
332
+ );
333
+ try assert_result(
334
+ tb_client,
335
+ packet_list,
336
+ 99,
337
+ @sizeOf(u128),
338
+ c.TB_PACKET_INVALID_OPERATION,
339
+ );
340
+
341
+ // Messages length 0 or not a multiple of the event size
342
+ // should return "invalid_data_size":
343
+ try assert_result(
344
+ tb_client,
345
+ packet_list,
346
+ c.TB_OP_CREATE_ACCOUNTS,
347
+ 0,
348
+ c.TB_PACKET_INVALID_DATA_SIZE,
349
+ );
350
+ try assert_result(
351
+ tb_client,
352
+ packet_list,
353
+ c.TB_OP_CREATE_TRANSFERS,
354
+ @sizeOf(c.tb_transfer_t) - 1,
355
+ c.TB_PACKET_INVALID_DATA_SIZE,
356
+ );
357
+ try assert_result(
358
+ tb_client,
359
+ packet_list,
360
+ c.TB_OP_LOOKUP_TRANSFERS,
361
+ @sizeOf(u128) + 1,
362
+ c.TB_PACKET_INVALID_DATA_SIZE,
363
+ );
364
+ try assert_result(
365
+ tb_client,
366
+ packet_list,
367
+ c.TB_OP_LOOKUP_ACCOUNTS,
368
+ @sizeOf(u128) * 2.5,
369
+ c.TB_PACKET_INVALID_DATA_SIZE,
370
+ );
371
+ }
@@ -20,13 +20,17 @@ const usage = fmt.comptimePrint(
20
20
  \\
21
21
  \\ tigerbeetle start --addresses=<addresses> <path>
22
22
  \\
23
+ \\ tigerbeetle version [--version]
24
+ \\
23
25
  \\Commands:
24
26
  \\
25
- \\ format Create a TigerBeetle replica data file at <path>.
26
- \\ The --cluster and --replica arguments are required.
27
- \\ Each TigerBeetle replica must have its own data file.
27
+ \\ format Create a TigerBeetle replica data file at <path>.
28
+ \\ The --cluster and --replica arguments are required.
29
+ \\ Each TigerBeetle replica must have its own data file.
30
+ \\
31
+ \\ start Run a TigerBeetle replica from the data file at <path>.
28
32
  \\
29
- \\ start Run a TigerBeetle replica from the data file at <path>.
33
+ \\ version Print the TigerBeetle build version and the compile-time config values.
30
34
  \\
31
35
  \\Options:
32
36
  \\
@@ -46,6 +50,10 @@ const usage = fmt.comptimePrint(
46
50
  \\ Either the IPv4 address or port number (but not both) may be omitted,
47
51
  \\ in which case a default of {[default_address]s} or {[default_port]d}
48
52
  \\ will be used.
53
+ \\ "addresses[i]" corresponds to replica "i".
54
+ \\
55
+ \\ --verbose
56
+ \\ Print compile-time configuration along with the build version.
49
57
  \\
50
58
  \\Examples:
51
59
  \\
@@ -59,6 +67,8 @@ const usage = fmt.comptimePrint(
59
67
  \\
60
68
  \\ tigerbeetle start --addresses=192.168.0.1,192.168.0.2,192.168.0.3 7_0.tigerbeetle
61
69
  \\
70
+ \\ tigerbeetle version --verbose
71
+ \\
62
72
  , .{
63
73
  .default_address = config.address,
64
74
  .default_port = config.port,
@@ -77,11 +87,15 @@ pub const Command = union(enum) {
77
87
  memory: u64,
78
88
  path: [:0]const u8,
79
89
  },
90
+ version: struct {
91
+ verbose: bool,
92
+ },
80
93
 
81
94
  pub fn deinit(command: Command, allocator: std.mem.Allocator) void {
82
95
  var args_allocated = switch (command) {
83
96
  .format => |cmd| cmd.args_allocated,
84
97
  .start => |cmd| cmd.args_allocated,
98
+ .version => return,
85
99
  };
86
100
 
87
101
  for (args_allocated.items) |arg| allocator.free(arg);
@@ -97,6 +111,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
97
111
  var replica: ?[]const u8 = null;
98
112
  var addresses: ?[]const u8 = null;
99
113
  var memory: ?[]const u8 = null;
114
+ var verbose: ?bool = null;
100
115
 
101
116
  var args = try std.process.argsWithAllocator(allocator);
102
117
  defer args.deinit();
@@ -110,7 +125,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
110
125
  assert(did_skip);
111
126
 
112
127
  const raw_command = try (args.next(allocator) orelse
113
- fatal("no command provided, expected 'start' or 'format'", .{}));
128
+ fatal("no command provided, expected 'start', 'format', or 'version'", .{}));
114
129
  defer allocator.free(raw_command);
115
130
 
116
131
  if (mem.eql(u8, raw_command, "-h") or mem.eql(u8, raw_command, "--help")) {
@@ -118,7 +133,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
118
133
  os.exit(0);
119
134
  }
120
135
  const command = meta.stringToEnum(meta.Tag(Command), raw_command) orelse
121
- fatal("unknown command '{s}', expected 'start' or 'format'", .{raw_command});
136
+ fatal("unknown command '{s}', expected 'start', 'format', or 'version'", .{raw_command});
122
137
 
123
138
  while (args.next(allocator)) |parsed_arg| {
124
139
  const arg = try parsed_arg;
@@ -132,6 +147,8 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
132
147
  addresses = parse_flag("--addresses", arg);
133
148
  } else if (mem.startsWith(u8, arg, "--memory")) {
134
149
  memory = parse_flag("--memory", arg);
150
+ } else if (mem.eql(u8, arg, "--verbose")) {
151
+ verbose = true;
135
152
  } else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
136
153
  std.io.getStdOut().writeAll(usage) catch os.exit(1);
137
154
  os.exit(0);
@@ -145,9 +162,21 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
145
162
  }
146
163
 
147
164
  switch (command) {
165
+ .version => {
166
+ if (addresses != null) fatal("--addresses: supported only by 'start' command", .{});
167
+ if (memory != null) fatal("--memory: supported only by 'start' command", .{});
168
+ if (cluster != null) fatal("--cluster: supported only by 'format' command", .{});
169
+ if (replica != null) fatal("--replica: supported only by 'format' command", .{});
170
+ if (path != null) fatal("unexpected path", .{});
171
+
172
+ return Command{
173
+ .version = .{ .verbose = verbose orelse false },
174
+ };
175
+ },
148
176
  .format => {
149
177
  if (addresses != null) fatal("--addresses: supported only by 'start' command", .{});
150
178
  if (memory != null) fatal("--memory: supported only by 'start' command", .{});
179
+ if (verbose != null) fatal("--verbose: supported only by 'version' command", .{});
151
180
 
152
181
  return Command{
153
182
  .format = .{
@@ -161,6 +190,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
161
190
  .start => {
162
191
  if (cluster != null) fatal("--cluster: supported only by 'format' command", .{});
163
192
  if (replica != null) fatal("--replica: supported only by 'format' command", .{});
193
+ if (verbose != null) fatal("--verbose: supported only by 'version' command", .{});
164
194
 
165
195
  return Command{
166
196
  .start = .{
@@ -207,7 +237,7 @@ fn parse_cluster(raw_cluster: []const u8) u32 {
207
237
 
208
238
  /// Parse and allocate the addresses returning a slice into that array.
209
239
  fn parse_addresses(allocator: std.mem.Allocator, raw_addresses: []const u8) []net.Address {
210
- return vsr.parse_addresses(allocator, raw_addresses) catch |err| switch (err) {
240
+ return vsr.parse_addresses(allocator, raw_addresses, config.replicas_max) catch |err| switch (err) {
211
241
  error.AddressHasTrailingComma => fatal("--addresses: invalid trailing comma", .{}),
212
242
  error.AddressLimitExceeded => {
213
243
  fatal("--addresses: too many addresses, at most {d} are allowed", .{
@@ -1,5 +1,7 @@
1
1
  const std = @import("std");
2
2
  const assert = std.debug.assert;
3
+ const tigerbeetle = @import("tigerbeetle.zig");
4
+ const vsr = @import("vsr.zig");
3
5
 
4
6
  const Environment = enum {
5
7
  development,
@@ -9,8 +11,7 @@ const Environment = enum {
9
11
 
10
12
  /// Whether development or production:
11
13
  pub const deployment_environment: Environment =
12
- if (@hasDecl(@import("root"), "deployment_environment")) @import("root").deployment_environment
13
- else .development;
14
+ if (@hasDecl(@import("root"), "deployment_environment")) @import("root").deployment_environment else .development;
14
15
 
15
16
  /// The maximum log level in increasing order of verbosity (emergency=0, debug=3):
16
17
  pub const log_level = 2;
@@ -48,22 +49,22 @@ pub const memory_size_max_default = 1024 * 1024 * 1024;
48
49
 
49
50
  /// The maximum number of accounts to store in memory:
50
51
  /// This impacts the amount of memory allocated at initialization by the server.
51
- pub const accounts_max = switch (deployment_environment) {
52
- .production => 1_000_000,
53
- else => 100_000,
52
+ pub const cache_accounts_max = switch (deployment_environment) {
53
+ .production => 64 * 1024,
54
+ else => 8 * 1024,
54
55
  };
55
56
 
56
57
  /// The maximum number of transfers to store in memory:
57
58
  /// This impacts the amount of memory allocated at initialization by the server.
58
59
  /// We allocate more capacity than the number of transfers for a safe hash table load factor.
59
- pub const transfers_max = switch (deployment_environment) {
60
- .production => 100_000_000,
61
- else => 1_000_000,
60
+ pub const cache_transfers_max = switch (deployment_environment) {
61
+ .production => 1024 * 1024,
62
+ else => 64 * 1024,
62
63
  };
63
64
 
64
65
  /// The maximum number of two-phase transfers to store in memory:
65
66
  /// This impacts the amount of memory allocated at initialization by the server.
66
- pub const transfers_pending_max = transfers_max;
67
+ pub const cache_transfers_pending_max = cache_transfers_max;
67
68
 
68
69
  /// The maximum number of batch entries in the journal file:
69
70
  /// A batch entry may contain many transfers, so this is not a limit on the number of transfers.
@@ -71,7 +72,7 @@ pub const transfers_pending_max = transfers_max;
71
72
  /// These header copies enable us to disentangle corruption from crashes and recover accordingly.
72
73
  pub const journal_slot_count = switch (deployment_environment) {
73
74
  .production => 1024,
74
- else => 128,
75
+ else => 1024,
75
76
  };
76
77
 
77
78
  /// The maximum size of the journal file:
@@ -80,7 +81,7 @@ pub const journal_slot_count = switch (deployment_environment) {
80
81
  /// This enables static allocation of disk space so that appends cannot fail with ENOSPC.
81
82
  /// This also enables us to detect filesystem inode corruption that would change the journal size.
82
83
  // TODO remove this; just allocate a part of the total storage for the journal
83
- pub const journal_size_max = journal_slot_count * (128 + message_size_max);
84
+ pub const journal_size_max = journal_slot_count * (@sizeOf(vsr.Header) + message_size_max);
84
85
 
85
86
  /// The maximum number of connections that can be held open by the server at any time:
86
87
  pub const connections_max = replicas_max + clients_max;
@@ -94,11 +95,22 @@ pub const connections_max = replicas_max + clients_max;
94
95
  /// For a 1 Gbps NIC = 125 MiB/s throughput: 2 MiB / 125 * 1000ms = 16ms for the next request.
95
96
  /// This impacts the amount of memory allocated at initialization by the server.
96
97
  pub const message_size_max = switch (deployment_environment) {
97
- // Use a small message size during the simulator for improved performance.
98
- .simulation => sector_size,
99
- else => 1 * 1024 * 1024
98
+ .simulation => message_size_max_min,
99
+ else => 1 * 1024 * 1024,
100
100
  };
101
101
 
102
+ pub const message_body_size_max = message_size_max - @sizeOf(vsr.Header);
103
+
104
+ /// The smallest possible message_size_max (for use in the simulator to improve performance).
105
+ /// The message body must have room for pipeline_max headers in the DVC.
106
+ const message_size_max_min = std.math.max(
107
+ sector_size,
108
+ std.mem.alignForward(
109
+ @sizeOf(vsr.Header) + pipeline_max * @sizeOf(vsr.Header),
110
+ sector_size,
111
+ ),
112
+ );
113
+
102
114
  /// The maximum number of Viewstamped Replication prepare messages that can be inflight at a time.
103
115
  /// This is immutable once assigned per cluster, as replicas need to know how many operations might
104
116
  /// possibly be uncommitted during a view change, and this must be constant for all replicas.
@@ -201,7 +213,6 @@ pub const io_depth_write = 8;
201
213
 
202
214
  /// The number of redundant copies of the superblock in the superblock storage zone.
203
215
  /// This must be either { 4, 6, 8 }, i.e. an even number, for more efficient flexible quorums.
204
- /// This is further multiplied by two to support copy-on-write across copy sets.
205
216
  ///
206
217
  /// The superblock contains local state for the replica and therefore cannot be replicated remotely.
207
218
  /// Loss of the superblock would represent loss of the replica and so it must be protected.
@@ -227,6 +238,9 @@ pub const superblock_copies = 4;
227
238
  pub const size_max = 16 * 1024 * 1024 * 1024 * 1024;
228
239
 
229
240
  /// The unit of read/write access to LSM manifest and LSM table blocks in the block storage zone.
241
+ ///
242
+ /// - A lower block size increases the memory overhead of table metadata, due to smaller/more tables.
243
+ /// - A higher block size increases space amplification due to partially-filled blocks.
230
244
  pub const block_size = 64 * 1024;
231
245
 
232
246
  pub const block_count_max = @divExact(16 * 1024 * 1024 * 1024 * 1024, block_size);
@@ -235,13 +249,22 @@ pub const block_count_max = @divExact(16 * 1024 * 1024 * 1024 * 1024, block_size
235
249
  pub const lsm_trees = 30;
236
250
 
237
251
  /// The number of levels in an LSM tree.
252
+ /// A higher number of levels increases read amplification, as well as total storage capacity.
238
253
  pub const lsm_levels = 7;
239
254
 
255
+ /// The number of tables at level i (0 ≤ i < lsm_levels) is `pow(lsm_growth_factor, i+1)`.
256
+ /// A higher growth factor increases write amplification (by increasing the number of tables in
257
+ /// level B that overlap a table in level A in a compaction), but decreases read amplification (by
258
+ /// reducing the height of the tree and thus the number of levels that must be probed). Since read
259
+ /// amplification can be optimized more easily (with filters and caching), we target a growth
260
+ /// factor of 8 for lower write amplification rather than the more typical growth factor of 10.
240
261
  pub const lsm_growth_factor = 8;
241
262
 
242
263
  /// The maximum key size for an LSM tree in bytes.
243
264
  pub const lsm_key_size_max = 32;
244
265
 
266
+ /// The maximum cumulative size of a table — computed as the sum of the size of the index block,
267
+ /// filter blocks, and data blocks.
245
268
  pub const lsm_table_size_max = 64 * 1024 * 1024;
246
269
 
247
270
  /// Size of nodes used by the LSM tree manifest implementation.
@@ -305,10 +328,10 @@ pub const clock_synchronization_window_max_ms = 20000;
305
328
  pub const verify = true;
306
329
 
307
330
  // TODO Move these to a separate "internal computed constants" file.
308
- pub const journal_size_headers = journal_slot_count * 128; // 128 == @sizeOf(Header)
331
+ pub const journal_size_headers = journal_slot_count * @sizeOf(vsr.Header);
309
332
  pub const journal_size_prepares = journal_slot_count * message_size_max;
310
333
 
311
- // TODO Move these into a separate `config_valid.zig` which we import here:
334
+ // TODO Move these into a separate `config_valid.zig` which we import here:
312
335
  comptime {
313
336
  // vsr.parse_address assumes that config.address/config.port are valid.
314
337
  _ = std.net.Address.parseIp4(address, 0) catch unreachable;
@@ -339,10 +362,28 @@ comptime {
339
362
 
340
363
  // The WAL format requires messages to be a multiple of the sector size.
341
364
  assert(message_size_max % sector_size == 0);
365
+ assert(message_size_max >= @sizeOf(vsr.Header));
342
366
  assert(message_size_max >= sector_size);
343
367
 
368
+ assert(superblock_copies % 2 == 0);
369
+ assert(superblock_copies >= 4);
370
+ assert(superblock_copies <= 8);
371
+
372
+ // ManifestLog serializes the level as a u7.
373
+ assert(lsm_levels > 0);
374
+ assert(lsm_levels <= std.math.maxInt(u7));
375
+
376
+ assert(block_size % sector_size == 0);
377
+ assert(lsm_table_size_max % sector_size == 0);
378
+ assert(lsm_table_size_max % block_size == 0);
379
+
344
380
  // The LSM tree uses half-measures to balance compaction.
345
381
  assert(lsm_batch_multiple % 2 == 0);
382
+
383
+ // SetAssociativeCache requires a power-of-two cardinality.
384
+ assert(std.math.isPowerOfTwo(cache_accounts_max));
385
+ assert(std.math.isPowerOfTwo(cache_transfers_max));
386
+ assert(std.math.isPowerOfTwo(cache_transfers_pending_max));
346
387
  }
347
388
 
348
389
  pub const is_32_bit = @sizeOf(usize) == 4; // TODO Return a compile error if we are not 32-bit.