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.
- package/README.md +302 -101
- package/dist/index.d.ts +70 -72
- package/dist/index.js +70 -72
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
- package/scripts/download_node_headers.sh +14 -7
- package/src/index.ts +6 -10
- package/src/node.zig +6 -3
- package/src/tigerbeetle/scripts/benchmark.sh +4 -4
- package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
- package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
- package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
- package/src/tigerbeetle/scripts/install.sh +19 -4
- package/src/tigerbeetle/scripts/install_zig.bat +5 -1
- package/src/tigerbeetle/scripts/install_zig.sh +24 -14
- package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
- package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
- package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
- package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
- package/src/tigerbeetle/scripts/validate_docs.sh +17 -0
- package/src/tigerbeetle/src/benchmark.zig +29 -13
- package/src/tigerbeetle/src/c/tb_client/context.zig +248 -47
- package/src/tigerbeetle/src/c/tb_client/echo_client.zig +108 -0
- package/src/tigerbeetle/src/c/tb_client/packet.zig +2 -2
- package/src/tigerbeetle/src/c/tb_client/signal.zig +2 -4
- package/src/tigerbeetle/src/c/tb_client/thread.zig +17 -257
- package/src/tigerbeetle/src/c/tb_client.h +118 -84
- package/src/tigerbeetle/src/c/tb_client.zig +88 -23
- package/src/tigerbeetle/src/c/tb_client_header_test.zig +135 -0
- package/src/tigerbeetle/src/c/test.zig +371 -1
- package/src/tigerbeetle/src/cli.zig +37 -7
- package/src/tigerbeetle/src/config.zig +58 -17
- package/src/tigerbeetle/src/demo.zig +5 -2
- package/src/tigerbeetle/src/demo_01_create_accounts.zig +1 -1
- package/src/tigerbeetle/src/demo_03_create_transfers.zig +13 -0
- package/src/tigerbeetle/src/ewah.zig +11 -33
- package/src/tigerbeetle/src/ewah_benchmark.zig +8 -9
- package/src/tigerbeetle/src/io/linux.zig +1 -1
- package/src/tigerbeetle/src/lsm/README.md +308 -0
- package/src/tigerbeetle/src/lsm/binary_search.zig +137 -10
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +43 -0
- package/src/tigerbeetle/src/lsm/compaction.zig +376 -397
- package/src/tigerbeetle/src/lsm/composite_key.zig +2 -0
- package/src/tigerbeetle/src/lsm/eytzinger.zig +1 -1
- package/src/tigerbeetle/src/{eytzinger_benchmark.zig → lsm/eytzinger_benchmark.zig} +34 -21
- package/src/tigerbeetle/src/lsm/forest.zig +21 -447
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +414 -0
- package/src/tigerbeetle/src/lsm/grid.zig +170 -76
- package/src/tigerbeetle/src/lsm/groove.zig +197 -133
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +40 -18
- package/src/tigerbeetle/src/lsm/level_iterator.zig +28 -9
- package/src/tigerbeetle/src/lsm/manifest.zig +93 -180
- package/src/tigerbeetle/src/lsm/manifest_level.zig +161 -454
- package/src/tigerbeetle/src/lsm/manifest_log.zig +243 -356
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +665 -0
- package/src/tigerbeetle/src/lsm/node_pool.zig +4 -0
- package/src/tigerbeetle/src/lsm/posted_groove.zig +65 -76
- package/src/tigerbeetle/src/lsm/segmented_array.zig +580 -251
- package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +62 -12
- package/src/tigerbeetle/src/lsm/table.zig +115 -68
- package/src/tigerbeetle/src/lsm/table_immutable.zig +30 -23
- package/src/tigerbeetle/src/lsm/table_iterator.zig +27 -17
- package/src/tigerbeetle/src/lsm/table_mutable.zig +63 -12
- package/src/tigerbeetle/src/lsm/test.zig +61 -56
- package/src/tigerbeetle/src/lsm/tree.zig +450 -407
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +461 -0
- package/src/tigerbeetle/src/main.zig +83 -8
- package/src/tigerbeetle/src/message_bus.zig +20 -9
- package/src/tigerbeetle/src/message_pool.zig +22 -19
- package/src/tigerbeetle/src/ring_buffer.zig +7 -3
- package/src/tigerbeetle/src/simulator.zig +179 -119
- package/src/tigerbeetle/src/state_machine.zig +381 -246
- package/src/tigerbeetle/src/static_allocator.zig +65 -0
- package/src/tigerbeetle/src/storage.zig +3 -7
- package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
- package/src/tigerbeetle/src/test/accounting/workload.zig +823 -0
- package/src/tigerbeetle/src/test/cluster.zig +33 -81
- package/src/tigerbeetle/src/test/conductor.zig +366 -0
- package/src/tigerbeetle/src/test/fuzz.zig +121 -0
- package/src/tigerbeetle/src/test/id.zig +89 -0
- package/src/tigerbeetle/src/test/network.zig +45 -19
- package/src/tigerbeetle/src/test/packet_simulator.zig +40 -29
- package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
- package/src/tigerbeetle/src/test/state_checker.zig +91 -69
- package/src/tigerbeetle/src/test/state_machine.zig +11 -35
- package/src/tigerbeetle/src/test/storage.zig +470 -106
- package/src/tigerbeetle/src/test/storage_checker.zig +204 -0
- package/src/tigerbeetle/src/tigerbeetle.zig +15 -16
- package/src/tigerbeetle/src/unit_tests.zig +13 -1
- package/src/tigerbeetle/src/util.zig +97 -11
- package/src/tigerbeetle/src/vopr.zig +495 -0
- package/src/tigerbeetle/src/vsr/client.zig +21 -3
- package/src/tigerbeetle/src/vsr/journal.zig +293 -212
- package/src/tigerbeetle/src/vsr/replica.zig +1086 -515
- package/src/tigerbeetle/src/vsr/superblock.zig +382 -637
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +14 -16
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +416 -153
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +332 -0
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +349 -0
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +62 -12
- package/src/tigerbeetle/src/vsr/superblock_quorums.zig +394 -0
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +312 -0
- package/src/tigerbeetle/src/vsr.zig +94 -60
- package/src/tigerbeetle/scripts/vopr.bat +0 -48
- package/src/tigerbeetle/scripts/vopr.sh +0 -33
- package/src/tigerbeetle/src/benchmark_array_search.zig +0 -317
- package/src/tigerbeetle/src/benchmarks/perf.zig +0 -299
|
@@ -1 +1,371 @@
|
|
|
1
|
-
|
|
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
|
|
26
|
-
\\
|
|
27
|
-
\\
|
|
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
|
-
\\
|
|
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 '
|
|
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 '
|
|
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
|
|
52
|
-
.production =>
|
|
53
|
-
else =>
|
|
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
|
|
60
|
-
.production =>
|
|
61
|
-
else =>
|
|
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
|
|
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 =>
|
|
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 * (
|
|
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
|
-
|
|
98
|
-
|
|
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 *
|
|
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
|
-
|
|
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.
|