tigerbeetle-node 0.11.5 → 0.11.6
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/dist/.client.node.sha256 +1 -1
- package/dist/index.d.ts +41 -42
- package/dist/index.js +41 -42
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +0 -1
- package/src/tigerbeetle/scripts/benchmark.bat +6 -1
- package/src/tigerbeetle/scripts/benchmark.sh +1 -1
- package/src/tigerbeetle/src/c/tb_client.h +42 -43
- package/src/tigerbeetle/src/cli.zig +32 -8
- package/src/tigerbeetle/src/config.zig +24 -3
- package/src/tigerbeetle/src/constants.zig +8 -5
- package/src/tigerbeetle/src/lsm/compaction.zig +9 -43
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +2 -7
- package/src/tigerbeetle/src/lsm/groove.zig +3 -0
- package/src/tigerbeetle/src/lsm/manifest_level.zig +1 -0
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +17 -9
- package/src/tigerbeetle/src/lsm/merge_iterator.zig +106 -0
- package/src/tigerbeetle/src/lsm/posted_groove.zig +1 -0
- package/src/tigerbeetle/src/lsm/segmented_array.zig +1 -0
- package/src/tigerbeetle/src/lsm/table.zig +14 -0
- package/src/tigerbeetle/src/lsm/table_iterator.zig +2 -2
- package/src/tigerbeetle/src/lsm/table_mutable.zig +49 -15
- package/src/tigerbeetle/src/lsm/test.zig +8 -4
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +302 -263
- package/src/tigerbeetle/src/main.zig +22 -25
- package/src/tigerbeetle/src/message_pool.zig +2 -1
- package/src/tigerbeetle/src/simulator.zig +22 -79
- package/src/tigerbeetle/src/{test/accounting → state_machine}/auditor.zig +8 -8
- package/src/tigerbeetle/src/{test/accounting → state_machine}/workload.zig +108 -48
- package/src/tigerbeetle/src/state_machine.zig +20 -14
- package/src/tigerbeetle/src/test/cluster.zig +11 -11
- package/src/tigerbeetle/src/test/conductor.zig +2 -3
- package/src/tigerbeetle/src/test/id.zig +10 -0
- package/src/tigerbeetle/src/test/state_machine.zig +151 -46
- package/src/tigerbeetle/src/tigerbeetle.zig +0 -1
- package/src/tigerbeetle/src/unit_tests.zig +2 -2
- package/src/tigerbeetle/src/vsr/client.zig +5 -5
- package/src/tigerbeetle/src/vsr/clock.zig +2 -2
- package/src/tigerbeetle/src/vsr/journal.zig +537 -487
- package/src/tigerbeetle/src/vsr/replica.zig +324 -314
- package/src/tigerbeetle/src/vsr/replica_format.zig +7 -4
- package/src/tigerbeetle/src/vsr/superblock.zig +76 -31
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +10 -5
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +3 -3
- package/src/tigerbeetle/src/vsr.zig +5 -5
|
@@ -45,18 +45,7 @@ pub fn main() !void {
|
|
|
45
45
|
|
|
46
46
|
switch (parse_args) {
|
|
47
47
|
.format => |*args| try Command.format(allocator, args.cluster, args.replica, args.path),
|
|
48
|
-
.start => |*args| try Command.start(
|
|
49
|
-
&arena,
|
|
50
|
-
args.addresses,
|
|
51
|
-
.{
|
|
52
|
-
// TODO Tune lsm_forest_node_count better.
|
|
53
|
-
.lsm_forest_node_count = 4096,
|
|
54
|
-
.cache_entries_accounts = args.cache_accounts,
|
|
55
|
-
.cache_entries_transfers = args.cache_transfers,
|
|
56
|
-
.cache_entries_posted = args.cache_transfers_posted,
|
|
57
|
-
},
|
|
58
|
-
args.path,
|
|
59
|
-
),
|
|
48
|
+
.start => |*args| try Command.start(&arena, args),
|
|
60
49
|
.version => |*args| try Command.version(allocator, args.verbose),
|
|
61
50
|
}
|
|
62
51
|
}
|
|
@@ -113,43 +102,51 @@ const Command = struct {
|
|
|
113
102
|
|
|
114
103
|
var superblock = try SuperBlock.init(
|
|
115
104
|
allocator,
|
|
116
|
-
|
|
117
|
-
|
|
105
|
+
.{
|
|
106
|
+
.storage = &command.storage,
|
|
107
|
+
.storage_size_limit = data_file_size_min,
|
|
108
|
+
.message_pool = &command.message_pool,
|
|
109
|
+
},
|
|
118
110
|
);
|
|
119
111
|
defer superblock.deinit(allocator);
|
|
120
112
|
|
|
121
113
|
try vsr.format(Storage, allocator, cluster, replica, &command.storage, &superblock);
|
|
122
114
|
}
|
|
123
115
|
|
|
124
|
-
pub fn start(
|
|
125
|
-
arena: *std.heap.ArenaAllocator,
|
|
126
|
-
addresses: []std.net.Address,
|
|
127
|
-
options: StateMachine.Options,
|
|
128
|
-
path: [:0]const u8,
|
|
129
|
-
) !void {
|
|
116
|
+
pub fn start(arena: *std.heap.ArenaAllocator, args: *const cli.Command.Start) !void {
|
|
130
117
|
var tracer_allocator = if (constants.tracer_backend == .tracy)
|
|
131
118
|
tracer.TracerAllocator.init(arena.allocator())
|
|
132
119
|
else
|
|
133
120
|
arena;
|
|
134
121
|
|
|
122
|
+
// TODO Panic if the data file's size is larger that args.storage_size_limit.
|
|
123
|
+
// (Here or in Replica.open()?).
|
|
124
|
+
|
|
135
125
|
const allocator = tracer_allocator.allocator();
|
|
136
126
|
|
|
137
127
|
try tracer.init(allocator);
|
|
138
128
|
defer tracer.deinit(allocator);
|
|
139
129
|
|
|
140
130
|
var command: Command = undefined;
|
|
141
|
-
try command.init(allocator, path, false);
|
|
131
|
+
try command.init(allocator, args.path, false);
|
|
142
132
|
defer command.deinit(allocator);
|
|
143
133
|
|
|
144
134
|
var replica: Replica = undefined;
|
|
145
135
|
replica.open(allocator, .{
|
|
146
|
-
.replica_count = @intCast(u8, addresses.len),
|
|
136
|
+
.replica_count = @intCast(u8, args.addresses.len),
|
|
137
|
+
.storage_size_limit = args.storage_size_limit,
|
|
147
138
|
.storage = &command.storage,
|
|
148
139
|
.message_pool = &command.message_pool,
|
|
149
140
|
.time = .{},
|
|
150
|
-
.state_machine_options =
|
|
141
|
+
.state_machine_options = .{
|
|
142
|
+
// TODO Tune lsm_forest_node_count better.
|
|
143
|
+
.lsm_forest_node_count = 4096,
|
|
144
|
+
.cache_entries_accounts = args.cache_accounts,
|
|
145
|
+
.cache_entries_transfers = args.cache_transfers,
|
|
146
|
+
.cache_entries_posted = args.cache_transfers_posted,
|
|
147
|
+
},
|
|
151
148
|
.message_bus_options = .{
|
|
152
|
-
.configuration = addresses,
|
|
149
|
+
.configuration = args.addresses,
|
|
153
150
|
.io = &command.io,
|
|
154
151
|
},
|
|
155
152
|
}) catch |err| switch (err) {
|
|
@@ -178,7 +175,7 @@ const Command = struct {
|
|
|
178
175
|
log_main.info("{}: cluster={}: listening on {}", .{
|
|
179
176
|
replica.replica,
|
|
180
177
|
replica.cluster,
|
|
181
|
-
addresses[replica.replica],
|
|
178
|
+
args.addresses[replica.replica],
|
|
182
179
|
});
|
|
183
180
|
|
|
184
181
|
while (true) {
|
|
@@ -22,7 +22,8 @@ const message_size_max_padded = constants.message_size_max + constants.sector_si
|
|
|
22
22
|
pub const messages_max_replica = messages_max: {
|
|
23
23
|
var sum: usize = 0;
|
|
24
24
|
|
|
25
|
-
sum += constants.journal_iops_read_max
|
|
25
|
+
sum += constants.journal_iops_read_max; // Journal reads
|
|
26
|
+
sum += constants.journal_iops_write_max; // Journal writes
|
|
26
27
|
sum += constants.clients_max; // SuperBlock.client_table
|
|
27
28
|
sum += 1; // Replica.loopback_queue
|
|
28
29
|
sum += constants.pipeline_max; // Replica.pipeline
|
|
@@ -17,9 +17,7 @@ const StateChecker = @import("test/state_checker.zig").StateChecker;
|
|
|
17
17
|
const StorageChecker = @import("test/storage_checker.zig").StorageChecker;
|
|
18
18
|
const PartitionMode = @import("test/packet_simulator.zig").PartitionMode;
|
|
19
19
|
const MessageBus = @import("test/message_bus.zig").MessageBus;
|
|
20
|
-
const
|
|
21
|
-
const Workload = @import("test/accounting/workload.zig").WorkloadType(StateMachine);
|
|
22
|
-
const Conductor = @import("test/conductor.zig").ConductorType(Client, MessageBus, StateMachine, Workload);
|
|
20
|
+
const Conductor = @import("test/conductor.zig").ConductorType(Client, MessageBus, StateMachine);
|
|
23
21
|
const IdPermutation = @import("test/id.zig").IdPermutation;
|
|
24
22
|
|
|
25
23
|
/// The `log` namespace in this root file is required to implement our custom `log` function.
|
|
@@ -88,16 +86,17 @@ pub fn main() !void {
|
|
|
88
86
|
const idle_on_probability = random.uintLessThan(u8, 20);
|
|
89
87
|
const idle_off_probability = 10 + random.uintLessThan(u8, 10);
|
|
90
88
|
|
|
91
|
-
//
|
|
92
|
-
//
|
|
89
|
+
// The maximum number of transitions from calling `client.request()`, not including
|
|
90
|
+
// `register` messages.
|
|
93
91
|
const requests_committed_max: usize = constants.journal_slot_count * 3;
|
|
94
92
|
|
|
95
93
|
const cluster_options: ClusterOptions = .{
|
|
96
94
|
.cluster = cluster_id,
|
|
97
95
|
.replica_count = replica_count,
|
|
98
96
|
.client_count = client_count,
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
.storage_size_limit = vsr.sector_floor(
|
|
98
|
+
constants.storage_size_max - random.uintLessThan(u64, constants.storage_size_max / 10),
|
|
99
|
+
),
|
|
101
100
|
.seed = random.int(u64),
|
|
102
101
|
.on_change_state = on_replica_change_state,
|
|
103
102
|
.on_compact = on_replica_compact,
|
|
@@ -132,11 +131,7 @@ pub fn main() !void {
|
|
|
132
131
|
.write_latency_mean = 3 + random.uintLessThan(u16, 100),
|
|
133
132
|
.read_fault_probability = random.uintLessThan(u8, 10),
|
|
134
133
|
.write_fault_probability = random.uintLessThan(u8, 10),
|
|
135
|
-
|
|
136
|
-
// is implemented after recovering with decision=fix. Otherwise we can end up with
|
|
137
|
-
// multiple crashes faulting first a redundant headers, then a prepare, upgrading
|
|
138
|
-
// a decision=fix to decision=vsr.
|
|
139
|
-
.crash_fault_probability = if (replica_count == 1) 0 else 80 + random.uintLessThan(u8, 21),
|
|
134
|
+
.crash_fault_probability = 80 + random.uintLessThan(u8, 21),
|
|
140
135
|
.faulty_superblock = true,
|
|
141
136
|
},
|
|
142
137
|
.health_options = .{
|
|
@@ -145,62 +140,18 @@ pub fn main() !void {
|
|
|
145
140
|
.restart_probability = 0.0001,
|
|
146
141
|
.restart_stability = random.uintLessThan(u32, 1_000),
|
|
147
142
|
},
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
.
|
|
151
|
-
.
|
|
152
|
-
|
|
153
|
-
|
|
143
|
+
// TODO(dj) SimulatorType(StateMachine).init(state_machine_options)
|
|
144
|
+
.state_machine_options = switch (constants.state_machine) {
|
|
145
|
+
.testing => .{},
|
|
146
|
+
.accounting => .{
|
|
147
|
+
.lsm_forest_node_count = 4096,
|
|
148
|
+
.cache_entries_accounts = if (random.boolean()) 0 else 2048,
|
|
149
|
+
.cache_entries_transfers = 0,
|
|
150
|
+
.cache_entries_posted = if (random.boolean()) 0 else 2048,
|
|
151
|
+
},
|
|
154
152
|
},
|
|
155
153
|
};
|
|
156
154
|
|
|
157
|
-
const workload_options: Workload.Options = .{
|
|
158
|
-
.auditor_options = .{
|
|
159
|
-
.accounts_max = 2 + random.uintLessThan(usize, 128),
|
|
160
|
-
.account_id_permutation = random_id_permutation(random),
|
|
161
|
-
.client_count = client_count,
|
|
162
|
-
.transfers_pending_max = 256,
|
|
163
|
-
.in_flight_max = Conductor.stalled_queue_capacity,
|
|
164
|
-
},
|
|
165
|
-
.transfer_id_permutation = random_id_permutation(random),
|
|
166
|
-
.operations = .{
|
|
167
|
-
.create_accounts = 1 + random.uintLessThan(usize, 10),
|
|
168
|
-
.create_transfers = 1 + random.uintLessThan(usize, 100),
|
|
169
|
-
.lookup_accounts = 1 + random.uintLessThan(usize, 20),
|
|
170
|
-
.lookup_transfers = 1 + random.uintLessThan(usize, 20),
|
|
171
|
-
},
|
|
172
|
-
.create_account_invalid_probability = 1,
|
|
173
|
-
.create_transfer_invalid_probability = 1,
|
|
174
|
-
.create_transfer_limit_probability = random.uintLessThan(u8, 101),
|
|
175
|
-
.create_transfer_pending_probability = 1 + random.uintLessThan(u8, 100),
|
|
176
|
-
.create_transfer_post_probability = 1 + random.uintLessThan(u8, 50),
|
|
177
|
-
.create_transfer_void_probability = 1 + random.uintLessThan(u8, 50),
|
|
178
|
-
.lookup_account_invalid_probability = 1,
|
|
179
|
-
.lookup_transfer = .{
|
|
180
|
-
.delivered = 1 + random.uintLessThan(usize, 10),
|
|
181
|
-
.sending = 1 + random.uintLessThan(usize, 10),
|
|
182
|
-
},
|
|
183
|
-
.lookup_transfer_span_mean = 10 + random.uintLessThan(usize, 1000),
|
|
184
|
-
.account_limit_probability = random.uintLessThan(u8, 80),
|
|
185
|
-
.linked_valid_probability = random.uintLessThan(u8, 101),
|
|
186
|
-
// 100% chance because this only applies to consecutive invalid transfers, which are rare.
|
|
187
|
-
.linked_invalid_probability = 100,
|
|
188
|
-
// TODO(Timeouts): When timeouts are implemented in the StateMachine, change this to the
|
|
189
|
-
// (commented out) value so that timeouts can actually trigger.
|
|
190
|
-
.pending_timeout_mean = std.math.maxInt(u64) / 2,
|
|
191
|
-
// .pending_timeout_mean = 1 + random.uintLessThan(usize, 1_000_000_000 / 4),
|
|
192
|
-
.accounts_batch_size_min = 0,
|
|
193
|
-
.accounts_batch_size_span = 1 + random.uintLessThan(
|
|
194
|
-
usize,
|
|
195
|
-
StateMachine.constants.batch_max.create_accounts,
|
|
196
|
-
),
|
|
197
|
-
.transfers_batch_size_min = 0,
|
|
198
|
-
.transfers_batch_size_span = 1 + random.uintLessThan(
|
|
199
|
-
usize,
|
|
200
|
-
StateMachine.constants.batch_max.create_transfers,
|
|
201
|
-
),
|
|
202
|
-
};
|
|
203
|
-
|
|
204
155
|
output.info(
|
|
205
156
|
\\
|
|
206
157
|
\\ SEED={}
|
|
@@ -266,7 +217,12 @@ pub fn main() !void {
|
|
|
266
217
|
cluster = try Cluster.create(allocator, random, cluster_options);
|
|
267
218
|
defer cluster.destroy();
|
|
268
219
|
|
|
269
|
-
|
|
220
|
+
const workload_options = StateMachine.Workload.Options.generate(random, .{
|
|
221
|
+
.client_count = client_count,
|
|
222
|
+
.in_flight_max = Conductor.stalled_queue_capacity,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
var workload = try StateMachine.Workload.init(allocator, random, workload_options);
|
|
270
226
|
defer workload.deinit(allocator);
|
|
271
227
|
|
|
272
228
|
var conductor = try Conductor.init(allocator, random, &workload, .{
|
|
@@ -318,9 +274,6 @@ pub fn main() !void {
|
|
|
318
274
|
storage.faulty = replica_normal_min <= i;
|
|
319
275
|
}
|
|
320
276
|
|
|
321
|
-
// The maximum number of transitions from calling `client.request()`, not including
|
|
322
|
-
// `register` messages.
|
|
323
|
-
// TODO When storage is supported, run more transitions than fit in the journal.
|
|
324
277
|
var tick: u64 = 0;
|
|
325
278
|
while (tick < ticks_max) : (tick += 1) {
|
|
326
279
|
const health_options = &cluster.options.health_options;
|
|
@@ -461,16 +414,6 @@ fn random_partition_mode(random: std.rand.Random) PartitionMode {
|
|
|
461
414
|
return @intToEnum(PartitionMode, enumAsInt);
|
|
462
415
|
}
|
|
463
416
|
|
|
464
|
-
fn random_id_permutation(random: std.rand.Random) IdPermutation {
|
|
465
|
-
return switch (random.uintLessThan(usize, 4)) {
|
|
466
|
-
0 => .{ .identity = {} },
|
|
467
|
-
1 => .{ .inversion = {} },
|
|
468
|
-
2 => .{ .zigzag = {} },
|
|
469
|
-
3 => .{ .random = random.int(u64) },
|
|
470
|
-
else => unreachable,
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
|
|
474
417
|
pub fn parse_seed(bytes: []const u8) u64 {
|
|
475
418
|
return std.fmt.parseUnsigned(u64, bytes, 10) catch |err| switch (err) {
|
|
476
419
|
error.Overflow => @panic("seed exceeds a 64-bit unsigned integer"),
|
|
@@ -6,16 +6,16 @@ const std = @import("std");
|
|
|
6
6
|
const assert = std.debug.assert;
|
|
7
7
|
const log = std.log.scoped(.test_auditor);
|
|
8
8
|
|
|
9
|
-
const constants = @import("
|
|
10
|
-
const tb = @import("
|
|
11
|
-
const vsr = @import("
|
|
12
|
-
const RingBuffer = @import("
|
|
13
|
-
const IdPermutation = @import("../id.zig").IdPermutation;
|
|
9
|
+
const constants = @import("../constants.zig");
|
|
10
|
+
const tb = @import("../tigerbeetle.zig");
|
|
11
|
+
const vsr = @import("../vsr.zig");
|
|
12
|
+
const RingBuffer = @import("../ring_buffer.zig").RingBuffer;
|
|
13
|
+
const IdPermutation = @import("../test/id.zig").IdPermutation;
|
|
14
14
|
|
|
15
15
|
// TODO(zig) This won't be necessary in Zig 0.10.
|
|
16
|
-
const PriorityQueue = @import("../priority_queue.zig").PriorityQueue;
|
|
17
|
-
const Storage = @import("../storage.zig").Storage;
|
|
18
|
-
const StateMachine = @import("
|
|
16
|
+
const PriorityQueue = @import("../test/priority_queue.zig").PriorityQueue;
|
|
17
|
+
const Storage = @import("../test/storage.zig").Storage;
|
|
18
|
+
const StateMachine = @import("../state_machine.zig").StateMachineType(Storage, .{
|
|
19
19
|
.message_body_size_max = constants.message_body_size_max,
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -21,18 +21,16 @@ const std = @import("std");
|
|
|
21
21
|
const assert = std.debug.assert;
|
|
22
22
|
const log = std.log.scoped(.test_workload);
|
|
23
23
|
|
|
24
|
-
const constants = @import("
|
|
25
|
-
const tb = @import("
|
|
26
|
-
const vsr = @import("
|
|
27
|
-
const accounting_auditor = @import("
|
|
24
|
+
const constants = @import("../constants.zig");
|
|
25
|
+
const tb = @import("../tigerbeetle.zig");
|
|
26
|
+
const vsr = @import("../vsr.zig");
|
|
27
|
+
const accounting_auditor = @import("auditor.zig");
|
|
28
28
|
const Auditor = accounting_auditor.AccountingAuditor;
|
|
29
|
-
const IdPermutation = @import("../id.zig").IdPermutation;
|
|
30
|
-
const fuzz = @import("../fuzz.zig");
|
|
29
|
+
const IdPermutation = @import("../test/id.zig").IdPermutation;
|
|
30
|
+
const fuzz = @import("../test/fuzz.zig");
|
|
31
31
|
|
|
32
32
|
// TODO(zig) This won't be necessary in Zig 0.10.
|
|
33
|
-
const PriorityQueue = @import("../priority_queue.zig").PriorityQueue;
|
|
34
|
-
|
|
35
|
-
// TODO Test linked create_accounts.
|
|
33
|
+
const PriorityQueue = @import("../test/priority_queue.zig").PriorityQueue;
|
|
36
34
|
|
|
37
35
|
const TransferOutcome = enum {
|
|
38
36
|
/// The transfer is guaranteed to commit.
|
|
@@ -177,44 +175,7 @@ pub fn WorkloadType(comptime AccountingStateMachine: type) type {
|
|
|
177
175
|
return struct {
|
|
178
176
|
const Self = @This();
|
|
179
177
|
|
|
180
|
-
pub const Options =
|
|
181
|
-
auditor_options: Auditor.Options,
|
|
182
|
-
transfer_id_permutation: IdPermutation,
|
|
183
|
-
|
|
184
|
-
operations: std.enums.EnumFieldStruct(Action, usize, null),
|
|
185
|
-
|
|
186
|
-
create_account_invalid_probability: u8, // ≤ 100
|
|
187
|
-
create_transfer_invalid_probability: u8, // ≤ 100
|
|
188
|
-
create_transfer_limit_probability: u8, // ≤ 100
|
|
189
|
-
create_transfer_pending_probability: u8, // ≤ 100
|
|
190
|
-
create_transfer_post_probability: u8, // ≤ 100
|
|
191
|
-
create_transfer_void_probability: u8, // ≤ 100
|
|
192
|
-
lookup_account_invalid_probability: u8, // ≤ 100
|
|
193
|
-
|
|
194
|
-
lookup_transfer: std.enums.EnumFieldStruct(enum {
|
|
195
|
-
/// Query a transfer that has either been committed or rejected.
|
|
196
|
-
delivered,
|
|
197
|
-
/// Query a transfer whose `create_transfers` is in-flight.
|
|
198
|
-
sending,
|
|
199
|
-
}, usize, null),
|
|
200
|
-
|
|
201
|
-
// Size of timespan for querying, measured in transfers
|
|
202
|
-
lookup_transfer_span_mean: usize,
|
|
203
|
-
|
|
204
|
-
account_limit_probability: u8, // ≤ 100
|
|
205
|
-
|
|
206
|
-
/// This probability is only checked for consecutive guaranteed-successful transfers.
|
|
207
|
-
linked_valid_probability: u8,
|
|
208
|
-
/// This probability is only checked for consecutive invalid transfers.
|
|
209
|
-
linked_invalid_probability: u8,
|
|
210
|
-
|
|
211
|
-
pending_timeout_mean: u64,
|
|
212
|
-
|
|
213
|
-
accounts_batch_size_min: usize,
|
|
214
|
-
accounts_batch_size_span: usize, // inclusive
|
|
215
|
-
transfers_batch_size_min: usize,
|
|
216
|
-
transfers_batch_size_span: usize, // inclusive
|
|
217
|
-
};
|
|
178
|
+
pub const Options = OptionsType(AccountingStateMachine, Action);
|
|
218
179
|
|
|
219
180
|
random: std.rand.Random,
|
|
220
181
|
auditor: Auditor,
|
|
@@ -303,7 +264,11 @@ pub fn WorkloadType(comptime AccountingStateMachine: type) type {
|
|
|
303
264
|
}
|
|
304
265
|
|
|
305
266
|
/// A client may build multiple requests to queue up while another is in-flight.
|
|
306
|
-
pub fn build_request(
|
|
267
|
+
pub fn build_request(
|
|
268
|
+
self: *Self,
|
|
269
|
+
client_index: usize,
|
|
270
|
+
body: []align(@alignOf(vsr.Header)) u8,
|
|
271
|
+
) struct {
|
|
307
272
|
operation: Operation,
|
|
308
273
|
size: usize,
|
|
309
274
|
} {
|
|
@@ -785,6 +750,101 @@ pub fn WorkloadType(comptime AccountingStateMachine: type) type {
|
|
|
785
750
|
};
|
|
786
751
|
}
|
|
787
752
|
|
|
753
|
+
fn OptionsType(comptime StateMachine: type, comptime Action: type) type {
|
|
754
|
+
return struct {
|
|
755
|
+
const Options = @This();
|
|
756
|
+
|
|
757
|
+
auditor_options: Auditor.Options,
|
|
758
|
+
transfer_id_permutation: IdPermutation,
|
|
759
|
+
|
|
760
|
+
operations: std.enums.EnumFieldStruct(Action, usize, null),
|
|
761
|
+
|
|
762
|
+
create_account_invalid_probability: u8, // ≤ 100
|
|
763
|
+
create_transfer_invalid_probability: u8, // ≤ 100
|
|
764
|
+
create_transfer_limit_probability: u8, // ≤ 100
|
|
765
|
+
create_transfer_pending_probability: u8, // ≤ 100
|
|
766
|
+
create_transfer_post_probability: u8, // ≤ 100
|
|
767
|
+
create_transfer_void_probability: u8, // ≤ 100
|
|
768
|
+
lookup_account_invalid_probability: u8, // ≤ 100
|
|
769
|
+
|
|
770
|
+
lookup_transfer: std.enums.EnumFieldStruct(enum {
|
|
771
|
+
/// Query a transfer that has either been committed or rejected.
|
|
772
|
+
delivered,
|
|
773
|
+
/// Query a transfer whose `create_transfers` is in-flight.
|
|
774
|
+
sending,
|
|
775
|
+
}, usize, null),
|
|
776
|
+
|
|
777
|
+
// Size of timespan for querying, measured in transfers
|
|
778
|
+
lookup_transfer_span_mean: usize,
|
|
779
|
+
|
|
780
|
+
account_limit_probability: u8, // ≤ 100
|
|
781
|
+
|
|
782
|
+
/// This probability is only checked for consecutive guaranteed-successful transfers.
|
|
783
|
+
linked_valid_probability: u8,
|
|
784
|
+
/// This probability is only checked for consecutive invalid transfers.
|
|
785
|
+
linked_invalid_probability: u8,
|
|
786
|
+
|
|
787
|
+
pending_timeout_mean: u64,
|
|
788
|
+
|
|
789
|
+
accounts_batch_size_min: usize,
|
|
790
|
+
accounts_batch_size_span: usize, // inclusive
|
|
791
|
+
transfers_batch_size_min: usize,
|
|
792
|
+
transfers_batch_size_span: usize, // inclusive
|
|
793
|
+
|
|
794
|
+
pub fn generate(random: std.rand.Random, options: struct {
|
|
795
|
+
client_count: usize,
|
|
796
|
+
in_flight_max: usize,
|
|
797
|
+
}) Options {
|
|
798
|
+
return .{
|
|
799
|
+
.auditor_options = .{
|
|
800
|
+
.accounts_max = 2 + random.uintLessThan(usize, 128),
|
|
801
|
+
.account_id_permutation = IdPermutation.generate(random),
|
|
802
|
+
.client_count = options.client_count,
|
|
803
|
+
.transfers_pending_max = 256,
|
|
804
|
+
.in_flight_max = options.in_flight_max,
|
|
805
|
+
},
|
|
806
|
+
.transfer_id_permutation = IdPermutation.generate(random),
|
|
807
|
+
.operations = .{
|
|
808
|
+
.create_accounts = 1 + random.uintLessThan(usize, 10),
|
|
809
|
+
.create_transfers = 1 + random.uintLessThan(usize, 100),
|
|
810
|
+
.lookup_accounts = 1 + random.uintLessThan(usize, 20),
|
|
811
|
+
.lookup_transfers = 1 + random.uintLessThan(usize, 20),
|
|
812
|
+
},
|
|
813
|
+
.create_account_invalid_probability = 1,
|
|
814
|
+
.create_transfer_invalid_probability = 1,
|
|
815
|
+
.create_transfer_limit_probability = random.uintLessThan(u8, 101),
|
|
816
|
+
.create_transfer_pending_probability = 1 + random.uintLessThan(u8, 100),
|
|
817
|
+
.create_transfer_post_probability = 1 + random.uintLessThan(u8, 50),
|
|
818
|
+
.create_transfer_void_probability = 1 + random.uintLessThan(u8, 50),
|
|
819
|
+
.lookup_account_invalid_probability = 1,
|
|
820
|
+
.lookup_transfer = .{
|
|
821
|
+
.delivered = 1 + random.uintLessThan(usize, 10),
|
|
822
|
+
.sending = 1 + random.uintLessThan(usize, 10),
|
|
823
|
+
},
|
|
824
|
+
.lookup_transfer_span_mean = 10 + random.uintLessThan(usize, 1000),
|
|
825
|
+
.account_limit_probability = random.uintLessThan(u8, 80),
|
|
826
|
+
.linked_valid_probability = random.uintLessThan(u8, 101),
|
|
827
|
+
// 100% chance because this only applies to consecutive invalid transfers, which are rare.
|
|
828
|
+
.linked_invalid_probability = 100,
|
|
829
|
+
// TODO(Timeouts): When timeouts are implemented in the StateMachine, change this to the
|
|
830
|
+
// (commented out) value so that timeouts can actually trigger.
|
|
831
|
+
.pending_timeout_mean = std.math.maxInt(u64) / 2,
|
|
832
|
+
// .pending_timeout_mean = 1 + random.uintLessThan(usize, 1_000_000_000 / 4),
|
|
833
|
+
.accounts_batch_size_min = 0,
|
|
834
|
+
.accounts_batch_size_span = 1 + random.uintLessThan(
|
|
835
|
+
usize,
|
|
836
|
+
StateMachine.constants.batch_max.create_accounts,
|
|
837
|
+
),
|
|
838
|
+
.transfers_batch_size_min = 0,
|
|
839
|
+
.transfers_batch_size_span = 1 + random.uintLessThan(
|
|
840
|
+
usize,
|
|
841
|
+
StateMachine.constants.batch_max.create_transfers,
|
|
842
|
+
),
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
788
848
|
/// Sample from a discrete distribution.
|
|
789
849
|
/// Use integers instead of floating-point numbers to avoid nondeterminism on different hardware.
|
|
790
850
|
fn sample_distribution(
|
|
@@ -8,6 +8,7 @@ const tracer = @import("tracer.zig");
|
|
|
8
8
|
|
|
9
9
|
const tb = @import("tigerbeetle.zig");
|
|
10
10
|
const snapshot_latest = @import("lsm/tree.zig").snapshot_latest;
|
|
11
|
+
const WorkloadType = @import("state_machine/workload.zig").WorkloadType;
|
|
11
12
|
|
|
12
13
|
const Account = tb.Account;
|
|
13
14
|
const AccountFlags = tb.AccountFlags;
|
|
@@ -26,7 +27,6 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
|
|
|
26
27
|
}) type {
|
|
27
28
|
return struct {
|
|
28
29
|
const StateMachine = @This();
|
|
29
|
-
|
|
30
30
|
const Grid = @import("lsm/grid.zig").GridType(Storage);
|
|
31
31
|
const GrooveType = @import("lsm/groove.zig").GrooveType;
|
|
32
32
|
const ForestType = @import("lsm/forest.zig").ForestType;
|
|
@@ -49,6 +49,8 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
|
|
|
49
49
|
);
|
|
50
50
|
const PostedGroove = @import("lsm/posted_groove.zig").PostedGrooveType(Storage);
|
|
51
51
|
|
|
52
|
+
pub const Workload = WorkloadType(StateMachine);
|
|
53
|
+
|
|
52
54
|
pub const Forest = ForestType(Storage, .{
|
|
53
55
|
.accounts = AccountsGroove,
|
|
54
56
|
.transfers = TransfersGroove,
|
|
@@ -180,13 +182,13 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
|
|
|
180
182
|
/// Returns the header's timestamp.
|
|
181
183
|
pub fn prepare(self: *StateMachine, operation: Operation, input: []u8) u64 {
|
|
182
184
|
switch (operation) {
|
|
185
|
+
.reserved => unreachable,
|
|
183
186
|
.root => unreachable,
|
|
184
187
|
.register => {},
|
|
185
188
|
.create_accounts => self.prepare_timestamps(.create_accounts, input),
|
|
186
189
|
.create_transfers => self.prepare_timestamps(.create_transfers, input),
|
|
187
190
|
.lookup_accounts => {},
|
|
188
191
|
.lookup_transfers => {},
|
|
189
|
-
else => unreachable,
|
|
190
192
|
}
|
|
191
193
|
return self.prepare_timestamp;
|
|
192
194
|
}
|
|
@@ -660,10 +662,7 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
|
|
|
660
662
|
if (t.credit_account_id == t.debit_account_id) return .accounts_must_be_different;
|
|
661
663
|
|
|
662
664
|
if (t.pending_id != 0) return .pending_id_must_be_zero;
|
|
663
|
-
if (t.flags.pending) {
|
|
664
|
-
// Otherwise, reserved amounts may never be released.
|
|
665
|
-
if (t.timeout == 0) return .pending_transfer_must_timeout;
|
|
666
|
-
} else {
|
|
665
|
+
if (!t.flags.pending) {
|
|
667
666
|
if (t.timeout != 0) return .timeout_reserved_for_pending_transfer;
|
|
668
667
|
}
|
|
669
668
|
|
|
@@ -822,8 +821,9 @@ pub fn StateMachineType(comptime Storage: type, comptime constants_: struct {
|
|
|
822
821
|
}
|
|
823
822
|
|
|
824
823
|
assert(p.timestamp < t.timestamp);
|
|
825
|
-
|
|
826
|
-
|
|
824
|
+
if (p.timeout > 0) {
|
|
825
|
+
if (p.timestamp + p.timeout <= t.timestamp) return .pending_transfer_expired;
|
|
826
|
+
}
|
|
827
827
|
|
|
828
828
|
self.forest.grooves.transfers.put_no_clobber(&Transfer{
|
|
829
829
|
.id = t.id,
|
|
@@ -1113,6 +1113,7 @@ test "sum_overflows" {
|
|
|
1113
1113
|
const TestContext = struct {
|
|
1114
1114
|
const Storage = @import("test/storage.zig").Storage;
|
|
1115
1115
|
const MessagePool = @import("message_pool.zig").MessagePool;
|
|
1116
|
+
const data_file_size_min = @import("vsr/superblock.zig").data_file_size_min;
|
|
1116
1117
|
const SuperBlock = @import("vsr/superblock.zig").SuperBlockType(Storage);
|
|
1117
1118
|
const Grid = @import("lsm/grid.zig").GridType(Storage);
|
|
1118
1119
|
const StateMachine = StateMachineType(Storage, .{
|
|
@@ -1147,7 +1148,11 @@ const TestContext = struct {
|
|
|
1147
1148
|
};
|
|
1148
1149
|
errdefer ctx.message_pool.deinit(allocator);
|
|
1149
1150
|
|
|
1150
|
-
ctx.superblock = try SuperBlock.init(allocator,
|
|
1151
|
+
ctx.superblock = try SuperBlock.init(allocator, .{
|
|
1152
|
+
.storage = &ctx.storage,
|
|
1153
|
+
.storage_size_limit = data_file_size_min,
|
|
1154
|
+
.message_pool = &ctx.message_pool,
|
|
1155
|
+
});
|
|
1151
1156
|
errdefer ctx.superblock.deinit(allocator);
|
|
1152
1157
|
|
|
1153
1158
|
// Pretend that the superblock is open so that the Forest can initialize.
|
|
@@ -1622,7 +1627,6 @@ test "create_transfers/lookup_transfers" {
|
|
|
1622
1627
|
\\ transfer T1 A8 -0 _ _ T1 _ L0 C0 _ PEN _ _ _ 0 credit_account_id_must_not_be_int_max
|
|
1623
1628
|
\\ transfer T1 A8 A8 _ _ T1 _ L0 C0 _ PEN _ _ _ 0 accounts_must_be_different
|
|
1624
1629
|
\\ transfer T1 A8 A9 _ _ T1 _ L0 C0 _ PEN _ _ _ 0 pending_id_must_be_zero
|
|
1625
|
-
\\ transfer T1 A8 A9 _ _ _ _ L0 C0 _ PEN _ _ _ 0 pending_transfer_must_timeout
|
|
1626
1630
|
\\ transfer T1 A8 A9 _ _ _ -0 L0 C0 _ _ _ _ _ 0 timeout_reserved_for_pending_transfer
|
|
1627
1631
|
\\ transfer T1 A8 A9 _ _ _ -0 L0 C0 _ PEN _ _ _ 0 ledger_must_not_be_zero
|
|
1628
1632
|
\\ transfer T1 A8 A9 _ _ _ -0 L9 C0 _ PEN _ _ _ 0 code_must_not_be_zero
|
|
@@ -1680,11 +1684,12 @@ test "create/lookup 2-phase transfers" {
|
|
|
1680
1684
|
\\ transfer T3 A1 A2 _ _ _ 50 L1 C1 _ PEN _ _ _ 15 ok
|
|
1681
1685
|
\\ transfer T4 A1 A2 _ _ _ 1 L1 C1 _ PEN _ _ _ 15 ok
|
|
1682
1686
|
\\ transfer T5 A1 A2 U9 _ _ 50 L1 C1 _ PEN _ _ _ 7 ok
|
|
1687
|
+
\\ transfer T6 A1 A2 _ _ _ 0 L1 C1 _ PEN _ _ _ 1 ok
|
|
1683
1688
|
\\ commit create_transfers
|
|
1684
1689
|
|
|
1685
1690
|
// Check balances before resolving.
|
|
1686
|
-
\\ lookup_account A1
|
|
1687
|
-
\\ lookup_account A2 0 0
|
|
1691
|
+
\\ lookup_account A1 53 15 0 0
|
|
1692
|
+
\\ lookup_account A2 0 0 53 15
|
|
1688
1693
|
\\ commit lookup_accounts
|
|
1689
1694
|
|
|
1690
1695
|
// Second phase.
|
|
@@ -1715,11 +1720,12 @@ test "create/lookup 2-phase transfers" {
|
|
|
1715
1720
|
\\ transfer T102 A1 A2 U1 _ T3 _ L1 C1 _ _ POS _ _ 13 pending_transfer_already_voided
|
|
1716
1721
|
\\ transfer T102 A1 A2 U1 _ T4 _ L1 C1 _ _ _ VOI _ 15 pending_transfer_expired
|
|
1717
1722
|
\\ transfer T105 A0 A0 U0 _ T5 _ L0 C0 _ _ POS _ _ _ ok
|
|
1723
|
+
\\ transfer T106 A0 A0 U0 _ T6 _ L1 C1 _ _ POS _ _ 0 ok
|
|
1718
1724
|
\\ commit create_transfers
|
|
1719
1725
|
|
|
1720
1726
|
// Check balances after resolving.
|
|
1721
|
-
\\ lookup_account A1 15
|
|
1722
|
-
\\ lookup_account A2 0 0 15
|
|
1727
|
+
\\ lookup_account A1 15 36 0 0
|
|
1728
|
+
\\ lookup_account A2 0 0 15 36
|
|
1723
1729
|
\\ commit lookup_accounts
|
|
1724
1730
|
);
|
|
1725
1731
|
}
|
|
@@ -11,7 +11,7 @@ const Message = MessagePool.Message;
|
|
|
11
11
|
const Network = @import("network.zig").Network;
|
|
12
12
|
const NetworkOptions = @import("network.zig").NetworkOptions;
|
|
13
13
|
|
|
14
|
-
pub const StateMachine =
|
|
14
|
+
pub const StateMachine = constants.StateMachineType(Storage, .{
|
|
15
15
|
.message_body_size_max = constants.message_body_size_max,
|
|
16
16
|
});
|
|
17
17
|
const MessageBus = @import("message_bus.zig").MessageBus;
|
|
@@ -29,7 +29,7 @@ pub const ClusterOptions = struct {
|
|
|
29
29
|
cluster: u32,
|
|
30
30
|
replica_count: u8,
|
|
31
31
|
client_count: u8,
|
|
32
|
-
|
|
32
|
+
storage_size_limit: u64,
|
|
33
33
|
|
|
34
34
|
seed: u64,
|
|
35
35
|
on_change_state: fn (replica: *const Replica) void,
|
|
@@ -76,8 +76,8 @@ pub const Cluster = struct {
|
|
|
76
76
|
pub fn create(allocator: mem.Allocator, prng: std.rand.Random, options: ClusterOptions) !*Cluster {
|
|
77
77
|
assert(options.replica_count > 0);
|
|
78
78
|
assert(options.client_count > 0);
|
|
79
|
-
assert(options.
|
|
80
|
-
assert(options.
|
|
79
|
+
assert(options.storage_size_limit % constants.sector_size == 0);
|
|
80
|
+
assert(options.storage_size_limit <= constants.storage_size_max);
|
|
81
81
|
assert(options.health_options.crash_probability < 1.0);
|
|
82
82
|
assert(options.health_options.crash_probability >= 0.0);
|
|
83
83
|
assert(options.health_options.restart_probability < 1.0);
|
|
@@ -136,17 +136,17 @@ pub const Cluster = struct {
|
|
|
136
136
|
var storage_options = options.storage_options;
|
|
137
137
|
storage_options.replica_index = @intCast(u8, replica_index);
|
|
138
138
|
storage_options.faulty_wal_areas = faulty_wal_areas[replica_index];
|
|
139
|
-
storage.* = try Storage.init(
|
|
140
|
-
allocator,
|
|
141
|
-
superblock_zone_size + constants.journal_size_max + options.grid_size_max,
|
|
142
|
-
storage_options,
|
|
143
|
-
);
|
|
139
|
+
storage.* = try Storage.init(allocator, options.storage_size_limit, storage_options);
|
|
144
140
|
}
|
|
145
141
|
errdefer for (cluster.storages) |*storage| storage.deinit(allocator);
|
|
146
142
|
|
|
147
143
|
// Format each replica's storage (equivalent to "tigerbeetle format ...").
|
|
148
144
|
for (cluster.storages) |*storage, replica_index| {
|
|
149
|
-
var superblock = try SuperBlock.init(allocator,
|
|
145
|
+
var superblock = try SuperBlock.init(allocator, .{
|
|
146
|
+
.storage = storage,
|
|
147
|
+
.message_pool = &cluster.pools[replica_index],
|
|
148
|
+
.storage_size_limit = options.storage_size_limit,
|
|
149
|
+
});
|
|
150
150
|
defer superblock.deinit(allocator);
|
|
151
151
|
|
|
152
152
|
try vsr.format(
|
|
@@ -330,6 +330,7 @@ pub const Cluster = struct {
|
|
|
330
330
|
.{
|
|
331
331
|
.replica_count = @intCast(u8, cluster.replicas.len),
|
|
332
332
|
.storage = &cluster.storages[replica_index],
|
|
333
|
+
.storage_size_limit = cluster.options.storage_size_limit,
|
|
333
334
|
.message_pool = &cluster.pools[replica_index],
|
|
334
335
|
.time = time,
|
|
335
336
|
.state_machine_options = cluster.options.state_machine_options,
|
|
@@ -339,7 +340,6 @@ pub const Cluster = struct {
|
|
|
339
340
|
assert(replica.cluster == cluster.options.cluster);
|
|
340
341
|
assert(replica.replica == replica_index);
|
|
341
342
|
assert(replica.replica_count == cluster.replicas.len);
|
|
342
|
-
assert(replica.status == .recovering);
|
|
343
343
|
|
|
344
344
|
replica.on_change_state = cluster.options.on_change_state;
|
|
345
345
|
replica.on_compact = cluster.options.on_compact;
|