tigerbeetle-node 0.11.1 → 0.11.3
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 -0
- package/package.json +2 -1
- package/src/tigerbeetle/src/cli.zig +62 -20
- package/src/tigerbeetle/src/config.zig +8 -3
- package/src/tigerbeetle/src/ewah.zig +18 -29
- package/src/tigerbeetle/src/ewah_fuzz.zig +128 -0
- package/src/tigerbeetle/src/lsm/compaction.zig +49 -1
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +5 -0
- package/src/tigerbeetle/src/lsm/grid.zig +3 -3
- package/src/tigerbeetle/src/lsm/tree.zig +19 -2
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +6 -1
- package/src/tigerbeetle/src/main.zig +18 -11
- package/src/tigerbeetle/src/simulator.zig +1 -1
- package/src/tigerbeetle/src/state_machine.zig +46 -1
- package/src/tigerbeetle/src/test/state_checker.zig +1 -1
- package/src/tigerbeetle/src/test/storage.zig +9 -0
- package/src/tigerbeetle/src/tracer.zig +319 -0
- package/src/tigerbeetle/src/unit_tests.zig +2 -0
- package/src/tigerbeetle/src/vsr/journal.zig +40 -109
- package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +109 -0
- package/src/tigerbeetle/src/vsr/replica.zig +45 -156
- package/src/tigerbeetle/src/vsr/replica_format.zig +216 -0
- package/src/tigerbeetle/src/vsr.zig +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cd58e7f1a46beef96d550cfb8423c01542980ff7f9832127e73ad2e5cd0e3a63 dist/client.node
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tigerbeetle-node",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "TigerBeetle Node.js client",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "dist/index.d.ts",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"LICENSE",
|
|
14
14
|
"README.md",
|
|
15
15
|
"dist",
|
|
16
|
+
"dist/.client.node.sha256",
|
|
16
17
|
"!dist/client.node",
|
|
17
18
|
"package.json",
|
|
18
19
|
"package-lock.json",
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
2
|
const assert = std.debug.assert;
|
|
3
3
|
const fmt = std.fmt;
|
|
4
|
+
const math = std.math;
|
|
4
5
|
const mem = std.mem;
|
|
5
6
|
const meta = std.meta;
|
|
6
7
|
const net = std.net;
|
|
7
8
|
const os = std.os;
|
|
8
9
|
|
|
9
10
|
const config = @import("config.zig");
|
|
11
|
+
const tigerbeetle = @import("tigerbeetle.zig");
|
|
10
12
|
const vsr = @import("vsr.zig");
|
|
11
13
|
const IO = @import("io.zig").IO;
|
|
12
14
|
|
|
13
|
-
// TODO Document --
|
|
15
|
+
// TODO Document --cache-accounts, --cache-transfers, --cache-transfers-posted
|
|
14
16
|
const usage = fmt.comptimePrint(
|
|
15
17
|
\\Usage:
|
|
16
18
|
\\
|
|
@@ -84,7 +86,9 @@ pub const Command = union(enum) {
|
|
|
84
86
|
start: struct {
|
|
85
87
|
args_allocated: std.ArrayList([:0]const u8),
|
|
86
88
|
addresses: []net.Address,
|
|
87
|
-
|
|
89
|
+
cache_accounts: u32,
|
|
90
|
+
cache_transfers: u32,
|
|
91
|
+
cache_transfers_posted: u32,
|
|
88
92
|
path: [:0]const u8,
|
|
89
93
|
},
|
|
90
94
|
version: struct {
|
|
@@ -110,7 +114,9 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
|
|
|
110
114
|
var cluster: ?[]const u8 = null;
|
|
111
115
|
var replica: ?[]const u8 = null;
|
|
112
116
|
var addresses: ?[]const u8 = null;
|
|
113
|
-
var
|
|
117
|
+
var cache_accounts: ?[]const u8 = null;
|
|
118
|
+
var cache_transfers: ?[]const u8 = null;
|
|
119
|
+
var cache_transfers_posted: ?[]const u8 = null;
|
|
114
120
|
var verbose: ?bool = null;
|
|
115
121
|
|
|
116
122
|
var args = try std.process.argsWithAllocator(allocator);
|
|
@@ -140,14 +146,25 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
|
|
|
140
146
|
try args_allocated.append(arg);
|
|
141
147
|
|
|
142
148
|
if (mem.startsWith(u8, arg, "--cluster")) {
|
|
149
|
+
if (command != .format) fatal("--cluster: supported only by 'format' command", .{});
|
|
143
150
|
cluster = parse_flag("--cluster", arg);
|
|
144
151
|
} else if (mem.startsWith(u8, arg, "--replica")) {
|
|
152
|
+
if (command != .format) fatal("--replica: supported only by 'format' command", .{});
|
|
145
153
|
replica = parse_flag("--replica", arg);
|
|
146
154
|
} else if (mem.startsWith(u8, arg, "--addresses")) {
|
|
155
|
+
if (command != .start) fatal("--addresses: supported only by 'start' command", .{});
|
|
147
156
|
addresses = parse_flag("--addresses", arg);
|
|
148
|
-
} else if (mem.startsWith(u8, arg, "--
|
|
149
|
-
|
|
157
|
+
} else if (mem.startsWith(u8, arg, "--cache-accounts")) {
|
|
158
|
+
if (command != .start) fatal("--cache-accounts: supported only by 'start' command", .{});
|
|
159
|
+
cache_accounts = parse_flag("--cache-accounts", arg);
|
|
160
|
+
} else if (mem.startsWith(u8, arg, "--cache-transfers")) {
|
|
161
|
+
if (command != .start) fatal("--cache-transfers: supported only by 'start' command", .{});
|
|
162
|
+
cache_transfers = parse_flag("--cache-transfers", arg);
|
|
163
|
+
} else if (mem.startsWith(u8, arg, "--cache-transfers-posted")) {
|
|
164
|
+
if (command != .start) fatal("--cache-transfers-posted: supported only by 'start' command", .{});
|
|
165
|
+
cache_transfers_posted = parse_flag("--cache-transfers-posted", arg);
|
|
150
166
|
} else if (mem.eql(u8, arg, "--verbose")) {
|
|
167
|
+
if (command != .version) fatal("--verbose: supported only by 'version' command", .{});
|
|
151
168
|
verbose = true;
|
|
152
169
|
} else if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
|
|
153
170
|
std.io.getStdOut().writeAll(usage) catch os.exit(1);
|
|
@@ -155,6 +172,7 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
|
|
|
155
172
|
} else if (mem.startsWith(u8, arg, "-")) {
|
|
156
173
|
fatal("unexpected argument: '{s}'", .{arg});
|
|
157
174
|
} else if (path == null) {
|
|
175
|
+
if (!(command == .format or command == .start)) fatal("unexpected path", .{});
|
|
158
176
|
path = arg;
|
|
159
177
|
} else {
|
|
160
178
|
fatal("unexpected argument: '{s}' (must start with '--')", .{arg});
|
|
@@ -163,21 +181,11 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
|
|
|
163
181
|
|
|
164
182
|
switch (command) {
|
|
165
183
|
.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
184
|
return Command{
|
|
173
185
|
.version = .{ .verbose = verbose orelse false },
|
|
174
186
|
};
|
|
175
187
|
},
|
|
176
188
|
.format => {
|
|
177
|
-
if (addresses != null) fatal("--addresses: supported only by 'start' command", .{});
|
|
178
|
-
if (memory != null) fatal("--memory: supported only by 'start' command", .{});
|
|
179
|
-
if (verbose != null) fatal("--verbose: supported only by 'version' command", .{});
|
|
180
|
-
|
|
181
189
|
return Command{
|
|
182
190
|
.format = .{
|
|
183
191
|
.args_allocated = args_allocated,
|
|
@@ -188,10 +196,6 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
|
|
|
188
196
|
};
|
|
189
197
|
},
|
|
190
198
|
.start => {
|
|
191
|
-
if (cluster != null) fatal("--cluster: supported only by 'format' command", .{});
|
|
192
|
-
if (replica != null) fatal("--replica: supported only by 'format' command", .{});
|
|
193
|
-
if (verbose != null) fatal("--verbose: supported only by 'version' command", .{});
|
|
194
|
-
|
|
195
199
|
return Command{
|
|
196
200
|
.start = .{
|
|
197
201
|
.args_allocated = args_allocated,
|
|
@@ -199,7 +203,21 @@ pub fn parse_args(allocator: std.mem.Allocator) !Command {
|
|
|
199
203
|
allocator,
|
|
200
204
|
addresses orelse fatal("required: --addresses", .{}),
|
|
201
205
|
),
|
|
202
|
-
.
|
|
206
|
+
.cache_accounts = parse_size_to_count(
|
|
207
|
+
tigerbeetle.Account,
|
|
208
|
+
cache_accounts,
|
|
209
|
+
config.cache_accounts_max,
|
|
210
|
+
),
|
|
211
|
+
.cache_transfers = parse_size_to_count(
|
|
212
|
+
tigerbeetle.Transfer,
|
|
213
|
+
cache_transfers,
|
|
214
|
+
config.cache_transfers_max,
|
|
215
|
+
),
|
|
216
|
+
.cache_transfers_posted = parse_size_to_count(
|
|
217
|
+
u256, // TODO(#264): Use actual type here, once exposed.
|
|
218
|
+
cache_transfers_posted,
|
|
219
|
+
config.cache_transfers_posted_max,
|
|
220
|
+
),
|
|
203
221
|
.path = path orelse fatal("required: <path>", .{}),
|
|
204
222
|
},
|
|
205
223
|
};
|
|
@@ -323,6 +341,30 @@ test "parse_size" {
|
|
|
323
341
|
try expectEqual(@as(u64, 1000 * kib), parse_size(" 1000 kb "));
|
|
324
342
|
}
|
|
325
343
|
|
|
344
|
+
/// Given a limit like `10GiB`, return the maximum power-of-two count of `T`s
|
|
345
|
+
/// that can fit in the limit.
|
|
346
|
+
fn parse_size_to_count(comptime T: type, string_opt: ?[]const u8, comptime default: u32) u32 {
|
|
347
|
+
var result: u32 = default;
|
|
348
|
+
if (string_opt) |string| {
|
|
349
|
+
const byte_size = parse_size(string);
|
|
350
|
+
const count_u64 = math.floorPowerOfTwo(u64, @divFloor(byte_size, @sizeOf(T)));
|
|
351
|
+
const count = math.cast(u32, count_u64) catch |err| switch (err) {
|
|
352
|
+
error.Overflow => fatal("size value is too large: '{s}'", .{string}),
|
|
353
|
+
};
|
|
354
|
+
if (count < 2048) fatal("size value is too small: '{s}'", .{string});
|
|
355
|
+
assert(count * @sizeOf(T) <= byte_size);
|
|
356
|
+
|
|
357
|
+
result = count;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// SetAssociativeCache requires a power-of-two cardinality and a minimal
|
|
361
|
+
// size.
|
|
362
|
+
assert(result >= 2048);
|
|
363
|
+
assert(math.isPowerOfTwo(result));
|
|
364
|
+
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
|
|
326
368
|
fn parse_replica(raw_replica: []const u8) u8 {
|
|
327
369
|
comptime assert(config.replicas_max <= std.math.maxInt(u8));
|
|
328
370
|
const replica = fmt.parseUnsigned(u8, raw_replica, 10) catch |err| switch (err) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const std = @import("std");
|
|
2
2
|
const assert = std.debug.assert;
|
|
3
|
-
const tigerbeetle = @import("tigerbeetle.zig");
|
|
4
3
|
const vsr = @import("vsr.zig");
|
|
5
4
|
|
|
5
|
+
const build_options = @import("tigerbeetle_build_options");
|
|
6
|
+
|
|
6
7
|
const Environment = enum {
|
|
7
8
|
development,
|
|
8
9
|
production,
|
|
@@ -64,7 +65,7 @@ pub const cache_transfers_max = switch (deployment_environment) {
|
|
|
64
65
|
|
|
65
66
|
/// The maximum number of two-phase transfers to store in memory:
|
|
66
67
|
/// This impacts the amount of memory allocated at initialization by the server.
|
|
67
|
-
pub const
|
|
68
|
+
pub const cache_transfers_posted_max = cache_transfers_max;
|
|
68
69
|
|
|
69
70
|
/// The maximum number of batch entries in the journal file:
|
|
70
71
|
/// A batch entry may contain many transfers, so this is not a limit on the number of transfers.
|
|
@@ -331,6 +332,10 @@ pub const verify = true;
|
|
|
331
332
|
pub const journal_size_headers = journal_slot_count * @sizeOf(vsr.Header);
|
|
332
333
|
pub const journal_size_prepares = journal_slot_count * message_size_max;
|
|
333
334
|
|
|
335
|
+
// Which backend to use for ./tracer.zig.
|
|
336
|
+
// Default is `.none`.
|
|
337
|
+
pub const tracer_backend = build_options.tracer_backend;
|
|
338
|
+
|
|
334
339
|
// TODO Move these into a separate `config_valid.zig` which we import here:
|
|
335
340
|
comptime {
|
|
336
341
|
// vsr.parse_address assumes that config.address/config.port are valid.
|
|
@@ -383,7 +388,7 @@ comptime {
|
|
|
383
388
|
// SetAssociativeCache requires a power-of-two cardinality.
|
|
384
389
|
assert(std.math.isPowerOfTwo(cache_accounts_max));
|
|
385
390
|
assert(std.math.isPowerOfTwo(cache_transfers_max));
|
|
386
|
-
assert(std.math.isPowerOfTwo(
|
|
391
|
+
assert(std.math.isPowerOfTwo(cache_transfers_posted_max));
|
|
387
392
|
}
|
|
388
393
|
|
|
389
394
|
pub const is_32_bit = @sizeOf(usize) == 4; // TODO Return a compile error if we are not 32-bit.
|
|
@@ -160,6 +160,24 @@ pub fn ewah(comptime Word: type) type {
|
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
test "ewah encode→decode cycle" {
|
|
164
|
+
const fuzz = @import("./ewah_fuzz.zig");
|
|
165
|
+
var prng = std.rand.DefaultPrng.init(123);
|
|
166
|
+
|
|
167
|
+
inline for (.{ u8, u16, u32, u64, usize }) |Word| {
|
|
168
|
+
var decoded: [4096]Word = undefined;
|
|
169
|
+
|
|
170
|
+
std.mem.set(Word, &decoded, 0);
|
|
171
|
+
try fuzz.fuzz_encode_decode(Word, std.testing.allocator, &decoded);
|
|
172
|
+
|
|
173
|
+
std.mem.set(Word, &decoded, std.math.maxInt(Word));
|
|
174
|
+
try fuzz.fuzz_encode_decode(Word, std.testing.allocator, &decoded);
|
|
175
|
+
|
|
176
|
+
prng.random().bytes(std.mem.asBytes(&decoded));
|
|
177
|
+
try fuzz.fuzz_encode_decode(Word, std.testing.allocator, &decoded);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
163
181
|
test "ewah Word=u8 decode→encode→decode" {
|
|
164
182
|
try test_decode_with_word(u8);
|
|
165
183
|
|
|
@@ -179,35 +197,6 @@ test "ewah Word=u8 decode→encode→decode" {
|
|
|
179
197
|
}
|
|
180
198
|
}
|
|
181
199
|
|
|
182
|
-
test "ewah Word=u8 encode→decode→encode" {
|
|
183
|
-
const codec = ewah(u8);
|
|
184
|
-
|
|
185
|
-
var seed: u64 = undefined;
|
|
186
|
-
try std.os.getrandom(mem.asBytes(&seed));
|
|
187
|
-
|
|
188
|
-
var prng = std.rand.DefaultPrng.init(seed);
|
|
189
|
-
const random = prng.random();
|
|
190
|
-
|
|
191
|
-
var decoded_expect: [4096]u8 = undefined;
|
|
192
|
-
var decoded_actual: [4096]u8 = undefined;
|
|
193
|
-
|
|
194
|
-
const encoded_actual = try std.testing.allocator.alignedAlloc(
|
|
195
|
-
u8,
|
|
196
|
-
@alignOf(u8),
|
|
197
|
-
codec.encode_size_max(decoded_expect.len),
|
|
198
|
-
);
|
|
199
|
-
defer std.testing.allocator.free(encoded_actual);
|
|
200
|
-
|
|
201
|
-
var t: usize = 0;
|
|
202
|
-
while (t < 100) : (t += 1) {
|
|
203
|
-
random.bytes(decoded_expect[0..]);
|
|
204
|
-
_ = codec.encode(decoded_expect[0..], encoded_actual);
|
|
205
|
-
const decoded_actual_length = codec.decode(encoded_actual[0..], decoded_actual[0..]);
|
|
206
|
-
try std.testing.expectEqual(decoded_expect.len, decoded_actual_length);
|
|
207
|
-
try std.testing.expectEqual(decoded_expect, decoded_actual);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
200
|
test "ewah Word=u16" {
|
|
212
201
|
try test_decode_with_word(u16);
|
|
213
202
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
//! Fuzz EWAH encode/decode cycle.
|
|
2
|
+
const std = @import("std");
|
|
3
|
+
const assert = std.debug.assert;
|
|
4
|
+
const log = std.log.scoped(.fuzz_ewah);
|
|
5
|
+
|
|
6
|
+
const ewah = @import("./ewah.zig");
|
|
7
|
+
const fuzz = @import("./test/fuzz.zig");
|
|
8
|
+
|
|
9
|
+
pub fn main() !void {
|
|
10
|
+
const allocator = std.testing.allocator;
|
|
11
|
+
const args = try fuzz.parse_fuzz_args(allocator);
|
|
12
|
+
|
|
13
|
+
inline for (.{ u8, u16, u32, u64, usize }) |Word| {
|
|
14
|
+
var prng = std.rand.DefaultPrng.init(args.seed);
|
|
15
|
+
const random = prng.random();
|
|
16
|
+
|
|
17
|
+
const decoded_size_max = @divExact(1024 * 1024, @sizeOf(Word));
|
|
18
|
+
const decoded_size = 1 + random.uintLessThan(usize, decoded_size_max);
|
|
19
|
+
const decoded = try allocator.alloc(Word, decoded_size);
|
|
20
|
+
defer allocator.free(decoded);
|
|
21
|
+
|
|
22
|
+
const decoded_bits_total = decoded_size * @bitSizeOf(Word);
|
|
23
|
+
const decoded_bits = random.uintLessThan(usize, decoded_bits_total);
|
|
24
|
+
generate_bits(random, std.mem.sliceAsBytes(decoded[0..decoded_size]), decoded_bits);
|
|
25
|
+
|
|
26
|
+
var context = try ContextType(Word).init(allocator, decoded.len);
|
|
27
|
+
defer context.deinit(allocator);
|
|
28
|
+
|
|
29
|
+
const encoded_size = try context.test_encode_decode(decoded);
|
|
30
|
+
|
|
31
|
+
log.info("word={} decoded={} encoded={} compression_ratio={d:.2} set={d:.2}", .{
|
|
32
|
+
Word,
|
|
33
|
+
decoded_size,
|
|
34
|
+
encoded_size,
|
|
35
|
+
@intToFloat(f64, decoded_size) / @intToFloat(f64, encoded_size),
|
|
36
|
+
@intToFloat(f64, decoded_bits) / @intToFloat(f64, decoded_bits_total),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
pub fn fuzz_encode_decode(
|
|
42
|
+
comptime Word: type,
|
|
43
|
+
allocator: std.mem.Allocator,
|
|
44
|
+
decoded: []const Word,
|
|
45
|
+
) !void {
|
|
46
|
+
var context = try ContextType(Word).init(allocator, decoded.len);
|
|
47
|
+
defer context.deinit(allocator);
|
|
48
|
+
|
|
49
|
+
_ = try context.test_encode_decode(decoded);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Modify `data` such that it has exactly `bits_set_total` randomly-chosen bits set,
|
|
53
|
+
/// with the remaining bits unset.
|
|
54
|
+
fn generate_bits(random: std.rand.Random, data: []u8, bits_set_total: usize) void {
|
|
55
|
+
const bits_total = data.len * @bitSizeOf(u8);
|
|
56
|
+
assert(bits_set_total <= bits_total);
|
|
57
|
+
|
|
58
|
+
// Start off full or empty to save some work.
|
|
59
|
+
const init_empty = bits_set_total < @divExact(bits_total, 2);
|
|
60
|
+
std.mem.set(u8, data, if (init_empty) @as(u8, 0) else std.math.maxInt(u8));
|
|
61
|
+
|
|
62
|
+
var bits_set = if (init_empty) 0 else bits_total;
|
|
63
|
+
while (bits_set != bits_set_total) {
|
|
64
|
+
const bit = random.uintLessThan(usize, bits_total);
|
|
65
|
+
const word = @divFloor(bit, @bitSizeOf(u8));
|
|
66
|
+
const mask = @as(u8, 1) << @intCast(std.math.Log2Int(u8), bit % @bitSizeOf(u8));
|
|
67
|
+
|
|
68
|
+
if (init_empty) {
|
|
69
|
+
if (data[word] & mask != 0) continue;
|
|
70
|
+
data[word] |= mask;
|
|
71
|
+
bits_set += 1;
|
|
72
|
+
} else {
|
|
73
|
+
if (data[word] & mask == 0) continue;
|
|
74
|
+
data[word] &= ~mask;
|
|
75
|
+
bits_set -= 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn ContextType(comptime Word: type) type {
|
|
81
|
+
return struct {
|
|
82
|
+
const Self = @This();
|
|
83
|
+
const Codec = ewah.ewah(Word);
|
|
84
|
+
|
|
85
|
+
decoded_actual: []Word,
|
|
86
|
+
encoded_actual: []align(@alignOf(Word)) u8,
|
|
87
|
+
|
|
88
|
+
fn init(allocator: std.mem.Allocator, size_max: usize) !Self {
|
|
89
|
+
const decoded_actual = try allocator.alloc(Word, size_max);
|
|
90
|
+
errdefer allocator.free(decoded_actual);
|
|
91
|
+
|
|
92
|
+
const encoded_actual = try allocator.alignedAlloc(
|
|
93
|
+
u8,
|
|
94
|
+
@alignOf(Word),
|
|
95
|
+
Codec.encode_size_max(size_max),
|
|
96
|
+
);
|
|
97
|
+
errdefer allocator.free(encoded_actual);
|
|
98
|
+
|
|
99
|
+
return Self{
|
|
100
|
+
.decoded_actual = decoded_actual,
|
|
101
|
+
.encoded_actual = encoded_actual,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn deinit(context: *Self, allocator: std.mem.Allocator) void {
|
|
106
|
+
allocator.free(context.decoded_actual);
|
|
107
|
+
allocator.free(context.encoded_actual);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn test_encode_decode(context: Self, decoded_expect: []const Word) !usize {
|
|
111
|
+
assert(decoded_expect.len > 0);
|
|
112
|
+
|
|
113
|
+
const encoded_size = Codec.encode(decoded_expect, context.encoded_actual);
|
|
114
|
+
const decoded_actual_size = Codec.decode(
|
|
115
|
+
context.encoded_actual[0..encoded_size],
|
|
116
|
+
context.decoded_actual[0..],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
try std.testing.expectEqual(decoded_expect.len, decoded_actual_size);
|
|
120
|
+
try std.testing.expectEqualSlices(
|
|
121
|
+
Word,
|
|
122
|
+
decoded_expect,
|
|
123
|
+
context.decoded_actual[0..decoded_actual_size],
|
|
124
|
+
);
|
|
125
|
+
return encoded_size;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -36,6 +36,8 @@ const math = std.math;
|
|
|
36
36
|
const assert = std.debug.assert;
|
|
37
37
|
|
|
38
38
|
const log = std.log.scoped(.compaction);
|
|
39
|
+
const tracer = @import("../tracer.zig");
|
|
40
|
+
|
|
39
41
|
const config = @import("../config.zig");
|
|
40
42
|
|
|
41
43
|
const GridType = @import("grid.zig").GridType;
|
|
@@ -120,6 +122,8 @@ pub fn CompactionType(
|
|
|
120
122
|
done,
|
|
121
123
|
};
|
|
122
124
|
|
|
125
|
+
tree_name: []const u8,
|
|
126
|
+
|
|
123
127
|
grid: *Grid,
|
|
124
128
|
grid_reservation: Grid.Reservation,
|
|
125
129
|
range: Manifest.CompactionRange,
|
|
@@ -155,7 +159,9 @@ pub fn CompactionType(
|
|
|
155
159
|
|
|
156
160
|
tables_output_count: usize = 0,
|
|
157
161
|
|
|
158
|
-
|
|
162
|
+
tracer_slot: ?tracer.SpanStart = null,
|
|
163
|
+
|
|
164
|
+
pub fn init(allocator: mem.Allocator, tree_name: []const u8) !Compaction {
|
|
159
165
|
var iterator_a = try IteratorA.init(allocator);
|
|
160
166
|
errdefer iterator_a.deinit(allocator);
|
|
161
167
|
|
|
@@ -166,6 +172,8 @@ pub fn CompactionType(
|
|
|
166
172
|
errdefer table_builder.deinit(allocator);
|
|
167
173
|
|
|
168
174
|
return Compaction{
|
|
175
|
+
.tree_name = tree_name,
|
|
176
|
+
|
|
169
177
|
// Assigned by start()
|
|
170
178
|
.grid = undefined,
|
|
171
179
|
.grid_reservation = undefined,
|
|
@@ -216,6 +224,7 @@ pub fn CompactionType(
|
|
|
216
224
|
assert(compaction.callback == null);
|
|
217
225
|
assert(compaction.io_pending == 0);
|
|
218
226
|
assert(!compaction.merge_done and compaction.merge_iterator == null);
|
|
227
|
+
assert(compaction.tracer_slot == null);
|
|
219
228
|
|
|
220
229
|
assert(op_min % @divExact(config.lsm_batch_multiple, 2) == 0);
|
|
221
230
|
assert(range.table_count > 0);
|
|
@@ -230,6 +239,8 @@ pub fn CompactionType(
|
|
|
230
239
|
assert(drop_tombstones or level_b < config.lsm_levels - 1);
|
|
231
240
|
|
|
232
241
|
compaction.* = .{
|
|
242
|
+
.tree_name = compaction.tree_name,
|
|
243
|
+
|
|
233
244
|
.grid = grid,
|
|
234
245
|
// Reserve enough blocks to write our output tables in the worst case, where:
|
|
235
246
|
// - no tombstones are dropped,
|
|
@@ -337,6 +348,15 @@ pub fn CompactionType(
|
|
|
337
348
|
|
|
338
349
|
compaction.callback = callback;
|
|
339
350
|
|
|
351
|
+
tracer.start(
|
|
352
|
+
&compaction.tracer_slot,
|
|
353
|
+
.{ .tree = .{ .tree_name = compaction.tree_name } },
|
|
354
|
+
.{ .tree_compaction_tick = .{
|
|
355
|
+
.tree_name = compaction.tree_name,
|
|
356
|
+
.level_b = compaction.level_b,
|
|
357
|
+
} },
|
|
358
|
+
);
|
|
359
|
+
|
|
340
360
|
// Generate fake IO to make sure io_pending doesn't reach zero multiple times from
|
|
341
361
|
// IO being completed inline down below.
|
|
342
362
|
// The fake IO is immediately resolved and triggers the cpu_merge_start if all
|
|
@@ -405,6 +425,16 @@ pub fn CompactionType(
|
|
|
405
425
|
assert(compaction.io_pending == 0);
|
|
406
426
|
assert(!compaction.merge_done);
|
|
407
427
|
|
|
428
|
+
var tracer_slot: ?tracer.SpanStart = null;
|
|
429
|
+
tracer.start(
|
|
430
|
+
&tracer_slot,
|
|
431
|
+
.{ .tree = .{ .tree_name = compaction.tree_name } },
|
|
432
|
+
.{ .tree_compaction_merge = .{
|
|
433
|
+
.tree_name = compaction.tree_name,
|
|
434
|
+
.level_b = compaction.level_b,
|
|
435
|
+
} },
|
|
436
|
+
);
|
|
437
|
+
|
|
408
438
|
// Create the merge iterator only when we can peek() from the read iterators.
|
|
409
439
|
// This happens after IO for the first reads complete.
|
|
410
440
|
if (compaction.merge_iterator == null) {
|
|
@@ -422,6 +452,23 @@ pub fn CompactionType(
|
|
|
422
452
|
compaction.cpu_merge_finish();
|
|
423
453
|
}
|
|
424
454
|
|
|
455
|
+
tracer.end(
|
|
456
|
+
&tracer_slot,
|
|
457
|
+
.{ .tree = .{ .tree_name = compaction.tree_name } },
|
|
458
|
+
.{ .tree_compaction_merge = .{
|
|
459
|
+
.tree_name = compaction.tree_name,
|
|
460
|
+
.level_b = compaction.level_b,
|
|
461
|
+
} },
|
|
462
|
+
);
|
|
463
|
+
tracer.end(
|
|
464
|
+
&compaction.tracer_slot,
|
|
465
|
+
.{ .tree = .{ .tree_name = compaction.tree_name } },
|
|
466
|
+
.{ .tree_compaction_tick = .{
|
|
467
|
+
.tree_name = compaction.tree_name,
|
|
468
|
+
.level_b = compaction.level_b,
|
|
469
|
+
} },
|
|
470
|
+
);
|
|
471
|
+
|
|
425
472
|
// TODO Implement pacing here by deciding if we should do another compact_tick()
|
|
426
473
|
// instead of invoking the callback, using compaction.range.table_count as the heuristic.
|
|
427
474
|
|
|
@@ -560,6 +607,7 @@ pub fn CompactionType(
|
|
|
560
607
|
assert(compaction.callback == null);
|
|
561
608
|
assert(compaction.io_pending == 0);
|
|
562
609
|
assert(compaction.merge_done);
|
|
610
|
+
assert(compaction.tracer_slot == null);
|
|
563
611
|
|
|
564
612
|
// TODO(Beat Pacing) This should really be where the compaction callback is invoked,
|
|
565
613
|
// but currently that can occur multiple times per beat.
|
|
@@ -6,7 +6,9 @@ const assert = std.debug.assert;
|
|
|
6
6
|
const config = @import("../config.zig");
|
|
7
7
|
const fuzz = @import("../test/fuzz.zig");
|
|
8
8
|
const vsr = @import("../vsr.zig");
|
|
9
|
+
|
|
9
10
|
const log = std.log.scoped(.lsm_forest_fuzz);
|
|
11
|
+
const tracer = @import("../tracer.zig");
|
|
10
12
|
|
|
11
13
|
const MessagePool = @import("../message_pool.zig").MessagePool;
|
|
12
14
|
const Transfer = @import("../tigerbeetle.zig").Transfer;
|
|
@@ -395,6 +397,9 @@ pub fn generate_fuzz_ops(random: std.rand.Random) ![]const FuzzOp {
|
|
|
395
397
|
}
|
|
396
398
|
|
|
397
399
|
pub fn main() !void {
|
|
400
|
+
try tracer.init(allocator);
|
|
401
|
+
defer tracer.deinit(allocator);
|
|
402
|
+
|
|
398
403
|
const fuzz_args = try fuzz.parse_fuzz_args(allocator);
|
|
399
404
|
var rng = std.rand.DefaultPrng.init(fuzz_args.seed);
|
|
400
405
|
const random = rng.random();
|
|
@@ -172,9 +172,9 @@ pub fn GridType(comptime Storage: type) type {
|
|
|
172
172
|
// Resolve reads that were seen in the cache during start_read()
|
|
173
173
|
// but deferred to be asynchronously resolved on the next tick.
|
|
174
174
|
//
|
|
175
|
-
// Drain directly from the queue so that new cache reads (added upon completion of old
|
|
176
|
-
// cache reads) that can be serviced immediately aren't deferred until the next tick
|
|
177
|
-
// (which may be milliseconds later due to IO.run_for_ns). This is necessary to ensure
|
|
175
|
+
// Drain directly from the queue so that new cache reads (added upon completion of old
|
|
176
|
+
// cache reads) that can be serviced immediately aren't deferred until the next tick
|
|
177
|
+
// (which may be milliseconds later due to IO.run_for_ns). This is necessary to ensure
|
|
178
178
|
// that groove prefetch completes promptly.
|
|
179
179
|
//
|
|
180
180
|
// Even still, we cap the reads processed to prevent going over
|
|
@@ -7,6 +7,7 @@ const mem = std.mem;
|
|
|
7
7
|
const os = std.os;
|
|
8
8
|
|
|
9
9
|
const log = std.log.scoped(.tree);
|
|
10
|
+
const tracer = @import("../tracer.zig");
|
|
10
11
|
|
|
11
12
|
const config = @import("../config.zig");
|
|
12
13
|
const div_ceil = @import("../util.zig").div_ceil;
|
|
@@ -144,6 +145,8 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
|
|
|
144
145
|
checkpoint_callback: ?fn (*Tree) void,
|
|
145
146
|
open_callback: ?fn (*Tree) void,
|
|
146
147
|
|
|
148
|
+
tracer_slot: ?tracer.SpanStart = null,
|
|
149
|
+
|
|
147
150
|
pub const Options = struct {
|
|
148
151
|
/// The maximum number of keys that may be committed per batch.
|
|
149
152
|
///
|
|
@@ -188,13 +191,13 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
|
|
|
188
191
|
var manifest = try Manifest.init(allocator, node_pool, grid, tree_hash);
|
|
189
192
|
errdefer manifest.deinit(allocator);
|
|
190
193
|
|
|
191
|
-
var compaction_table_immutable = try CompactionTableImmutable.init(allocator);
|
|
194
|
+
var compaction_table_immutable = try CompactionTableImmutable.init(allocator, tree_name);
|
|
192
195
|
errdefer compaction_table_immutable.deinit(allocator);
|
|
193
196
|
|
|
194
197
|
var compaction_table: [@divFloor(config.lsm_levels, 2)]CompactionTable = undefined;
|
|
195
198
|
for (compaction_table) |*compaction, i| {
|
|
196
199
|
errdefer for (compaction_table[0..i]) |*c| c.deinit(allocator);
|
|
197
|
-
compaction.* = try CompactionTable.init(allocator);
|
|
200
|
+
compaction.* = try CompactionTable.init(allocator, tree_name);
|
|
198
201
|
}
|
|
199
202
|
errdefer for (compaction_table) |*c| c.deinit(allocator);
|
|
200
203
|
|
|
@@ -221,6 +224,8 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
|
|
|
221
224
|
}
|
|
222
225
|
|
|
223
226
|
pub fn deinit(tree: *Tree, allocator: mem.Allocator) void {
|
|
227
|
+
assert(tree.tracer_slot == null);
|
|
228
|
+
|
|
224
229
|
tree.compaction_table_immutable.deinit(allocator);
|
|
225
230
|
for (tree.compaction_table) |*compaction| compaction.deinit(allocator);
|
|
226
231
|
|
|
@@ -548,6 +553,12 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
|
|
|
548
553
|
assert(tree.compaction_io_pending == 0);
|
|
549
554
|
assert(tree.compaction_callback == null);
|
|
550
555
|
|
|
556
|
+
tracer.start(
|
|
557
|
+
&tree.tracer_slot,
|
|
558
|
+
.{ .tree = .{ .tree_name = tree_name } },
|
|
559
|
+
.{ .tree_compaction_beat = .{ .tree_name = tree_name } },
|
|
560
|
+
);
|
|
561
|
+
|
|
551
562
|
tree.compaction_callback = callback;
|
|
552
563
|
|
|
553
564
|
const compaction_beat = tree.compaction_op % config.lsm_batch_multiple;
|
|
@@ -917,6 +928,12 @@ pub fn TreeType(comptime TreeTable: type, comptime Storage: type, comptime tree_
|
|
|
917
928
|
assert(tree.compaction_io_pending == 0);
|
|
918
929
|
assert(tree.table_mutable.can_commit_batch(tree.options.commit_entries_max));
|
|
919
930
|
|
|
931
|
+
tracer.end(
|
|
932
|
+
&tree.tracer_slot,
|
|
933
|
+
.{ .tree = .{ .tree_name = tree_name } },
|
|
934
|
+
.{ .tree_compaction_beat = .{ .tree_name = tree_name } },
|
|
935
|
+
);
|
|
936
|
+
|
|
920
937
|
// Invoke the compact() callback after the manifest compacts at the end of the beat.
|
|
921
938
|
const callback = tree.compaction_callback.?;
|
|
922
939
|
tree.compaction_callback = null;
|
|
@@ -6,7 +6,9 @@ const assert = std.debug.assert;
|
|
|
6
6
|
const config = @import("../config.zig");
|
|
7
7
|
const fuzz = @import("../test/fuzz.zig");
|
|
8
8
|
const vsr = @import("../vsr.zig");
|
|
9
|
+
|
|
9
10
|
const log = std.log.scoped(.lsm_tree_fuzz);
|
|
11
|
+
const tracer = @import("../tracer.zig");
|
|
10
12
|
|
|
11
13
|
const MessagePool = @import("../message_pool.zig").MessagePool;
|
|
12
14
|
const Transfer = @import("../tigerbeetle.zig").Transfer;
|
|
@@ -27,7 +29,7 @@ const Table = @import("table.zig").TableType(
|
|
|
27
29
|
Key.tombstone,
|
|
28
30
|
Key.tombstone_from_key,
|
|
29
31
|
);
|
|
30
|
-
const Tree = @import("tree.zig").TreeType(Table, Storage,
|
|
32
|
+
const Tree = @import("tree.zig").TreeType(Table, Storage, "Key.Value");
|
|
31
33
|
|
|
32
34
|
const Grid = GridType(Storage);
|
|
33
35
|
const SuperBlock = vsr.SuperBlockType(Storage);
|
|
@@ -440,6 +442,9 @@ pub fn generate_fuzz_ops(random: std.rand.Random) ![]const FuzzOp {
|
|
|
440
442
|
}
|
|
441
443
|
|
|
442
444
|
pub fn main() !void {
|
|
445
|
+
try tracer.init(allocator);
|
|
446
|
+
defer tracer.deinit(allocator);
|
|
447
|
+
|
|
443
448
|
const fuzz_args = try fuzz.parse_fuzz_args(allocator);
|
|
444
449
|
var rng = std.rand.DefaultPrng.init(fuzz_args.seed);
|
|
445
450
|
const random = rng.random();
|