lib/sql/src/profiling/pager.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const bench = @import("bench");
3 const coz = @import("coz");
4 const sql = @import("sql");
5
6 const wal_frame_count = 256;
7 const wal_page_count = 16;
8 const file_clean_open_repeats = 32;
9 const pager_snapshot_limit = 32;
10 const sparse_wal_page_count = 2_048;
11 const sparse_wal_lookup_repeats = 128;
12 const current_view_commit_count = 2_048;
13 const database_page_count_commit_count = 4_096;
14 const base_history_page_count = 2_048;
15 const base_history_lookup_repeats = 8_192;
16 const base_history_retention_versions = 4;
17 const durable_checkpoint_pages = 1_024;
18 const durable_checkpoint_rotations = 4;
19 const transaction_batch_pages = 1_024;
20
21 pub fn addTo(suite: *bench.Suite) !void {
22 try suite.add("sql wal append 256", walAppendFrames, .{});
23 try suite.add("sql wal recover 256", walRecoverFrames, .{});
24 try suite.add("sql pager snapshot lookup 256", pagerSnapshotLookup, .{});
25 try suite.add("sql wal sparse lookup 2048x128", walSparseLookup, .{});
26 try suite.add("sql pager sparse lookup 2048x128", pagerSparseLookup, .{});
27 try suite.add("sql pager current view commits 2048", pagerCurrentViewCommits, .{});
28 try suite.add("sql pager database page count commits 4096", pagerDatabasePageCountCommits, .{});
29 try suite.add("sql pager base history lookup 2048x8192", pagerBaseHistoryLookup, .{});
30 try suite.add("sql pager base history retention 2048x4", pagerBaseHistoryRetention, .{});
31 try suite.add("sql pager wal frame retention 2048+1", pagerWalFrameRetention, .{});
32 try suite.add("sql pager wal tail rewrite 2048+1", pagerWalTailRewrite, .{});
33 try suite.add("sql pager checkpoint restart 256", pagerCheckpointRestart, .{});
34 try suite.add("sql file recover 256", fileRecoverPager, .{});
35 try suite.add("sql file database lifecycle 256", fileDatabaseLifecycle, .{});
36 try suite.add("sql file database clean open 16x32", fileDatabaseCleanOpen, .{});
37 try suite.add("sql file durable checkpoint rotation 1024x4", fileDurableCheckpointRotation, .{});
38 try suite.add("sql file database transactions 256", fileDatabaseTransactions, .{});
39 try suite.add("sql file transaction durable batch 1024", fileDatabaseTransactionBatch, .{});
40 }
41
42 fn fillWalImage(image: *[sql.page.size]u8, frame: usize) void {
43 @memset(image, 0);
44 image[0] = @intCast(frame % wal_page_count);
45 image[1] = @intCast(frame & 0xff);
46 }
47
48 fn fillSparseWalImage(image: *[sql.page.size]u8, page_id: usize) void {
49 @memset(image, 0);
50 image[0] = @intCast(page_id & 0xff);
51 image[1] = @intCast((page_id >> 8) & 0xff);
52 }
53
54 fn sparseLookupPageId(repeat: usize) u32 {
55 return @intCast((repeat * 137) % sparse_wal_page_count + 1);
56 }
57
58 fn initWalWriter(allocator: std.mem.Allocator, header: sql.wal.Header, frames: usize) sql.wal.Writer {
59 const limits: sql.wal.Writer.Limits = .{
60 .header = header,
61 .frames = frames,
62 };
63 const capacity = sql.wal.Writer.Capacity.derive(limits) catch @panic("wal capacity failed");
64 const storage = allocator.alloc(u8, capacity.storage_bytes) catch @panic("wal storage failed");
65 var writer = sql.wal.Writer.init(storage, limits) catch {
66 allocator.free(storage);
67 @panic("wal init failed");
68 };
69 writer.activate();
70 return writer;
71 }
72
73 fn initPager(allocator: std.mem.Allocator, header: sql.wal.Header, wal_frames: usize) sql.Pager {
74 const options: sql.PagerInitOptions = .{
75 .header = header,
76 .wal_frames = wal_frames,
77 };
78 var workspace = sql.Pager.Workspace.allocate(allocator, options) catch @panic("pager workspace allocation failed");
79 return sql.Pager.init(allocator, &workspace, options) catch {
80 workspace.deallocate(allocator);
81 @panic("pager init failed");
82 };
83 }
84
85 fn deinitPager(allocator: std.mem.Allocator, pager: *sql.Pager) void {
86 var workspace = pager.deinit();
87 workspace.deallocate(allocator);
88 }
89
90 fn walAppendFrames(sample_allocator: std.mem.Allocator) void {
91 var writer = initWalWriter(sample_allocator, .{
92 .sequence = 1,
93 .salt = .{ .first = 0x1234_5678, .second = 0x9abc_def0 },
94 }, wal_frame_count);
95 defer sample_allocator.free(writer.deinit());
96
97 var index: usize = 0;
98 while (index < wal_frame_count) : (index += 1) {
99 var image: [sql.page.size]u8 = undefined;
100 fillWalImage(&image, index);
101 writer.append(
102 @intCast(index % wal_page_count + 1),
103 if ((index + 1) % wal_page_count == 0) @as(u32, wal_page_count) else 0,
104 &image,
105 ) catch @panic("wal append failed");
106 }
107 std.mem.doNotOptimizeAway(writer.bytes().len);
108 }
109
110 fn walRecoverFrames(sample_allocator: std.mem.Allocator) void {
111 var writer = initWalWriter(sample_allocator, .{
112 .sequence = 2,
113 .salt = .{ .first = 0xabcd_9876, .second = 0x1020_3040 },
114 }, wal_frame_count);
115 defer sample_allocator.free(writer.deinit());
116
117 var index: usize = 0;
118 while (index < wal_frame_count) : (index += 1) {
119 var image: [sql.page.size]u8 = undefined;
120 fillWalImage(&image, index);
121 writer.append(
122 @intCast(index % wal_page_count + 1),
123 if ((index + 1) % wal_page_count == 0) @as(u32, wal_page_count) else 0,
124 &image,
125 ) catch @panic("wal append failed");
126 }
127
128 const mark = sql.wal.endMark(writer.bytes()) catch @panic("wal end mark failed");
129 var checksum: usize = mark;
130 var page_id: u32 = 1;
131 while (page_id <= wal_page_count) : (page_id += 1) {
132 if (sql.wal.pageAt(writer.bytes(), page_id, mark) catch @panic("wal page lookup failed")) |image| {
133 checksum +%= image[0];
134 checksum +%= image[1];
135 }
136 }
137 std.mem.doNotOptimizeAway(checksum);
138 }
139
140 fn walSparseLookup(sample_allocator: std.mem.Allocator) void {
141 var writer = initWalWriter(sample_allocator, .{
142 .sequence = 8,
143 .salt = .{ .first = 0x8a8a_1111, .second = 0x2222_b8b8 },
144 }, sparse_wal_page_count);
145 defer sample_allocator.free(writer.deinit());
146
147 const prepare_phase = bench.phaseAt("sql.wal.sparse.prepare", @src());
148 prepare_phase.setName("wal sparse lookup 2048x128");
149 {
150 defer prepare_phase.end();
151 var index: usize = 0;
152 while (index < sparse_wal_page_count) : (index += 1) {
153 const page_id = index + 1;
154 var image: [sql.page.size]u8 = undefined;
155 fillSparseWalImage(&image, page_id);
156 writer.append(
157 @intCast(page_id),
158 if (index + 1 == sparse_wal_page_count) @as(u32, sparse_wal_page_count) else 0,
159 &image,
160 ) catch @panic("wal append failed");
161 }
162 }
163
164 const mark = sql.wal.endMark(writer.bytes()) catch @panic("wal end mark failed");
165 var checksum: usize = mark;
166 const lookup_phase = bench.phaseAt("sql.wal.sparse.lookup", @src());
167 lookup_phase.setName("wal sparse lookup 2048x128");
168 {
169 defer lookup_phase.end();
170 var repeat: usize = 0;
171 while (repeat < sparse_wal_lookup_repeats) : (repeat += 1) {
172 const page_id = sparseLookupPageId(repeat);
173 const image = (sql.wal.pageAt(writer.bytes(), page_id, mark) catch @panic("wal page lookup failed")) orelse @panic("wal page missing");
174 checksum +%= image[0];
175 checksum +%= image[1];
176 }
177 }
178 std.mem.doNotOptimizeAway(checksum);
179 }
180
181 fn pagerSnapshotLookup(sample_allocator: std.mem.Allocator) void {
182 var pager = initPager(sample_allocator, .{
183 .sequence = 3,
184 .salt = .{ .first = 0x3141_5926, .second = 0x5358_9793 },
185 }, wal_frame_count);
186 defer deinitPager(sample_allocator, &pager);
187 pager.reserve(.{
188 .base_pages = wal_page_count,
189 .wal_frames = wal_frame_count,
190 .wal_pages = wal_page_count,
191 }) catch @panic("pager reserve failed");
192
193 var page_id: u32 = 1;
194 while (page_id <= wal_page_count) : (page_id += 1) {
195 var image: [sql.page.size]u8 = undefined;
196 fillWalImage(&image, page_id);
197 pager.installBase(page_id, &image) catch @panic("pager base install failed");
198 }
199
200 var snapshots: std.ArrayList(sql.PagerSnapshot) = .empty;
201 defer snapshots.deinit(sample_allocator);
202
203 var index: usize = 0;
204 while (index < wal_frame_count) : (index += 1) {
205 var image: [sql.page.size]u8 = undefined;
206 fillWalImage(&image, index);
207 const commit = (index + 1) % wal_page_count == 0;
208 pager.appendWal(
209 @intCast(index % wal_page_count + 1),
210 if (commit) @as(u32, wal_page_count) else 0,
211 &image,
212 ) catch @panic("pager wal append failed");
213 if (commit and snapshots.items.len < pager_snapshot_limit) {
214 snapshots.append(sample_allocator, pager.beginRead() catch @panic("pager snapshot failed")) catch @panic("snapshot append failed");
215 }
216 }
217
218 var checksum: usize = 0;
219 for (snapshots.items) |snapshot| {
220 page_id = 1;
221 while (page_id <= wal_page_count) : (page_id += 1) {
222 if (snapshot.get(page_id) catch @panic("pager lookup failed")) |image| {
223 checksum +%= image[0];
224 checksum +%= image[1];
225 }
226 }
227 }
228 std.mem.doNotOptimizeAway(checksum);
229 }
230
231 fn pagerSparseLookup(sample_allocator: std.mem.Allocator) void {
232 var pager = initPager(sample_allocator, .{
233 .sequence = 9,
234 .salt = .{ .first = 0x9a9a_3333, .second = 0x4444_c9c9 },
235 }, sparse_wal_page_count);
236 defer deinitPager(sample_allocator, &pager);
237 pager.reserve(.{
238 .wal_frames = sparse_wal_page_count,
239 .wal_pages = sparse_wal_page_count,
240 }) catch @panic("pager reserve failed");
241
242 const prepare_phase = bench.phaseAt("sql.pager.sparse.prepare", @src());
243 prepare_phase.setName("pager sparse lookup 2048x128");
244 {
245 defer prepare_phase.end();
246 var index: usize = 0;
247 while (index < sparse_wal_page_count) : (index += 1) {
248 const page_id = index + 1;
249 var image: [sql.page.size]u8 = undefined;
250 fillSparseWalImage(&image, page_id);
251 pager.appendWal(
252 @intCast(page_id),
253 if (index + 1 == sparse_wal_page_count) @as(u32, sparse_wal_page_count) else 0,
254 &image,
255 ) catch @panic("pager wal append failed");
256 }
257 }
258
259 const snapshot = pager.beginRead() catch @panic("pager snapshot failed");
260 var checksum: usize = snapshot.view.end_mark;
261 const lookup_phase = bench.phaseAt("sql.pager.sparse.lookup", @src());
262 lookup_phase.setName("pager sparse lookup 2048x128");
263 {
264 defer lookup_phase.end();
265 var repeat: usize = 0;
266 while (repeat < sparse_wal_lookup_repeats) : (repeat += 1) {
267 const page_id = sparseLookupPageId(repeat);
268 const image = (snapshot.get(page_id) catch @panic("pager lookup failed")) orelse @panic("pager page missing");
269 checksum +%= image[0];
270 checksum +%= image[1];
271 }
272 }
273 std.mem.doNotOptimizeAway(checksum);
274 }
275
276 fn pagerCurrentViewCommits(sample_allocator: std.mem.Allocator) void {
277 var pager = initPager(sample_allocator, .{
278 .sequence = 17,
279 .salt = .{ .first = 0x1717_aaaa, .second = 0xbbbb_7171 },
280 }, current_view_commit_count);
281 defer deinitPager(sample_allocator, &pager);
282 pager.reserve(.{
283 .wal_frames = current_view_commit_count,
284 .wal_pages = current_view_commit_count,
285 }) catch @panic("pager reserve failed");
286
287 const phase = bench.phaseAt("sql.pager.current_view.commits", @src());
288 phase.setName("pager current view commits 2048");
289 defer phase.end();
290
291 var checksum: usize = 0;
292 var index: usize = 0;
293 while (index < current_view_commit_count) : (index += 1) {
294 const page_id = index + 1;
295 var image: [sql.page.size]u8 = undefined;
296 fillSparseWalImage(&image, page_id);
297 pager.appendWal(@intCast(page_id), @intCast(page_id), &image) catch @panic("pager wal append failed");
298 const snapshot = pager.beginRead() catch @panic("pager snapshot failed");
299 checksum +%= snapshot.view.end_mark;
300 }
301 std.mem.doNotOptimizeAway(checksum);
302 }
303
304 fn pagerDatabasePageCountCommits(sample_allocator: std.mem.Allocator) void {
305 var pager = initPager(sample_allocator, .{
306 .sequence = 18,
307 .salt = .{ .first = 0x1818_bbbb, .second = 0xcccc_8181 },
308 }, database_page_count_commit_count);
309 defer deinitPager(sample_allocator, &pager);
310 pager.reserve(.{
311 .wal_frames = database_page_count_commit_count,
312 .wal_pages = database_page_count_commit_count,
313 }) catch @panic("pager reserve failed");
314
315 const phase = bench.phaseAt("sql.pager.database_page_count.commits", @src());
316 phase.setName("pager database page count commits 4096");
317 defer phase.end();
318
319 var checksum: usize = 0;
320 var index: usize = 0;
321 while (index < database_page_count_commit_count) : (index += 1) {
322 const page_id = index + 1;
323 var image: [sql.page.size]u8 = undefined;
324 fillSparseWalImage(&image, page_id);
325 pager.appendWal(@intCast(page_id), @intCast(page_id), &image) catch @panic("pager wal append failed");
326 checksum +%= pager.databasePageCount();
327 }
328 std.mem.doNotOptimizeAway(checksum);
329 }
330
331 fn pagerBaseHistoryLookup(sample_allocator: std.mem.Allocator) void {
332 var pager = initPager(sample_allocator, .{
333 .sequence = 10,
334 .salt = .{ .first = 0xaaaa_5555, .second = 0x6666_bbbb },
335 }, 0);
336 defer deinitPager(sample_allocator, &pager);
337 pager.reserve(.{ .base_pages = base_history_page_count }) catch @panic("pager reserve failed");
338
339 const prepare_phase = bench.phaseAt("sql.pager.base_history.prepare", @src());
340 prepare_phase.setName("pager base history lookup 2048x8192");
341 {
342 defer prepare_phase.end();
343 var index: usize = 0;
344 while (index < base_history_page_count) : (index += 1) {
345 const page_id = index + 1;
346 var image: [sql.page.size]u8 = undefined;
347 fillSparseWalImage(&image, page_id);
348 pager.installBase(@intCast(page_id), &image) catch @panic("pager base install failed");
349 }
350 }
351
352 const snapshot = pager.beginRead() catch @panic("pager snapshot failed");
353 var checksum: usize = @intCast(snapshot.view.base_generation);
354 const lookup_phase = bench.phaseAt("sql.pager.base_history.lookup", @src());
355 lookup_phase.setName("pager base history lookup 2048x8192");
356 {
357 defer lookup_phase.end();
358 var repeat: usize = 0;
359 while (repeat < base_history_lookup_repeats) : (repeat += 1) {
360 const page_id = sparseLookupPageId(repeat);
361 const image = (snapshot.get(page_id) catch @panic("pager base lookup failed")) orelse @panic("pager base page missing");
362 checksum +%= image[0];
363 checksum +%= image[1];
364 }
365 }
366 std.mem.doNotOptimizeAway(checksum);
367 }
368
369 fn pagerBaseHistoryRetention(sample_allocator: std.mem.Allocator) void {
370 var pager = initPager(sample_allocator, .{
371 .sequence = 11,
372 .salt = .{ .first = 0xabab_7777, .second = 0x8888_cdcd },
373 }, 0);
374 defer deinitPager(sample_allocator, &pager);
375 pager.reserve(.{ .base_pages = base_history_page_count * base_history_retention_versions }) catch @panic("pager reserve failed");
376
377 const prepare_phase = bench.phaseAt("sql.pager.base_history.retention.prepare", @src());
378 prepare_phase.setName("pager base history retention 2048x4");
379 {
380 defer prepare_phase.end();
381 var page_index: usize = 0;
382 while (page_index < base_history_page_count) : (page_index += 1) {
383 const page_id = page_index + 1;
384 var version: usize = 0;
385 while (version < base_history_retention_versions) : (version += 1) {
386 var image: [sql.page.size]u8 = undefined;
387 fillSparseWalImage(&image, page_id + version);
388 pager.installBase(@intCast(page_id), &image) catch @panic("pager base install failed");
389 }
390 }
391 }
392
393 const retain_phase = bench.phaseAt("sql.pager.base_history.retention", @src());
394 retain_phase.setName("pager base history retention 2048x4");
395 {
396 defer retain_phase.end();
397 _ = pager.checkpoint(.{}) catch @panic("pager checkpoint failed");
398 }
399 std.mem.doNotOptimizeAway(pager.databasePageCount());
400 }
401
402 fn pagerWalFrameRetention(sample_allocator: std.mem.Allocator) void {
403 var pager = initPager(sample_allocator, .{
404 .sequence = 12,
405 .salt = .{ .first = 0xacac_9999, .second = 0xaaaa_dede },
406 }, sparse_wal_page_count + 1);
407 defer deinitPager(sample_allocator, &pager);
408 pager.reserve(.{
409 .wal_frames = sparse_wal_page_count + 1,
410 .wal_pages = sparse_wal_page_count + 1,
411 }) catch @panic("pager reserve failed");
412
413 const prepare_phase = bench.phaseAt("sql.pager.wal_frames.retention.prepare", @src());
414 prepare_phase.setName("pager wal frame retention 2048+1");
415 {
416 defer prepare_phase.end();
417 var index: usize = 0;
418 while (index < sparse_wal_page_count) : (index += 1) {
419 const page_id = index + 1;
420 var image: [sql.page.size]u8 = undefined;
421 fillSparseWalImage(&image, page_id);
422 pager.appendWal(
423 @intCast(page_id),
424 if (index + 1 == sparse_wal_page_count) @as(u32, sparse_wal_page_count) else 0,
425 &image,
426 ) catch @panic("pager wal append failed");
427 }
428 var tail: [sql.page.size]u8 = undefined;
429 fillSparseWalImage(&tail, sparse_wal_page_count + 1);
430 pager.appendWal(sparse_wal_page_count + 1, 0, &tail) catch @panic("pager wal tail append failed");
431 }
432
433 const retain_phase = bench.phaseAt("sql.pager.wal_frames.retention", @src());
434 retain_phase.setName("pager wal frame retention 2048+1");
435 const checkpoint = checkpoint: {
436 defer retain_phase.end();
437 break :checkpoint pager.checkpoint(.{}) catch @panic("pager checkpoint failed");
438 };
439 std.mem.doNotOptimizeAway(checkpoint.pages + pager.storage().wal_frames + pager.frameCount());
440 }
441
442 fn pagerWalTailRewrite(sample_allocator: std.mem.Allocator) void {
443 var pager = initPager(sample_allocator, .{
444 .sequence = 13,
445 .salt = .{ .first = 0xbcbc_8888, .second = 0xbbbb_efef },
446 }, sparse_wal_page_count + 1);
447 defer deinitPager(sample_allocator, &pager);
448 pager.reserve(.{
449 .wal_frames = sparse_wal_page_count + 1,
450 .wal_pages = sparse_wal_page_count + 1,
451 }) catch @panic("pager reserve failed");
452
453 const prepare_phase = bench.phaseAt("sql.pager.wal_tail.rewrite.prepare", @src());
454 prepare_phase.setName("pager wal tail rewrite 2048+1");
455 {
456 defer prepare_phase.end();
457 var index: usize = 0;
458 while (index < sparse_wal_page_count) : (index += 1) {
459 const page_id = index + 1;
460 var image: [sql.page.size]u8 = undefined;
461 fillSparseWalImage(&image, page_id);
462 pager.appendWal(
463 @intCast(page_id),
464 if (index + 1 == sparse_wal_page_count) @as(u32, sparse_wal_page_count) else 0,
465 &image,
466 ) catch @panic("pager wal append failed");
467 }
468 var tail: [sql.page.size]u8 = undefined;
469 fillSparseWalImage(&tail, sparse_wal_page_count + 1);
470 pager.appendWal(sparse_wal_page_count + 1, 0, &tail) catch @panic("pager wal tail append failed");
471 }
472
473 const rewrite_phase = bench.phaseAt("sql.pager.wal_tail.rewrite", @src());
474 rewrite_phase.setName("pager wal tail rewrite 2048+1");
475 const checkpoint = checkpoint: {
476 defer rewrite_phase.end();
477 break :checkpoint pager.checkpoint(.{
478 .restart_header = .{
479 .sequence = 14,
480 .salt = .{ .first = 0x8989_cdcd, .second = 0xefef_2323 },
481 },
482 }) catch @panic("pager checkpoint failed");
483 };
484 std.mem.doNotOptimizeAway(checkpoint.pages + pager.storage().wal_frames + pager.frameCount());
485 }
486
487 fn pagerCheckpointRestart(sample_allocator: std.mem.Allocator) void {
488 var pager = initPager(sample_allocator, .{
489 .sequence = 4,
490 .salt = .{ .first = 0x1122_3344, .second = 0x5566_7788 },
491 }, wal_frame_count);
492 defer deinitPager(sample_allocator, &pager);
493 pager.reserve(.{
494 .base_pages = wal_page_count,
495 .wal_frames = wal_frame_count,
496 .wal_pages = wal_page_count,
497 }) catch @panic("pager reserve failed");
498
499 var index: usize = 0;
500 while (index < wal_frame_count) : (index += 1) {
501 var image: [sql.page.size]u8 = undefined;
502 fillWalImage(&image, index);
503 pager.appendWal(
504 @intCast(index % wal_page_count + 1),
505 if ((index + 1) % wal_page_count == 0) @as(u32, wal_page_count) else 0,
506 &image,
507 ) catch @panic("pager wal append failed");
508 }
509
510 const checkpoint = pager.checkpoint(.{
511 .restart_header = .{
512 .sequence = 5,
513 .salt = .{ .first = 0x99aa_bbcc, .second = 0xddee_ff00 },
514 },
515 }) catch @panic("pager checkpoint failed");
516 std.mem.doNotOptimizeAway(checkpoint.pages + pager.frameCount());
517 }
518
519 fn fileRecoverPager(sample_allocator: std.mem.Allocator) void {
520 var tmp = std.testing.tmpDir(.{});
521 defer tmp.cleanup();
522
523 var pager = initPager(sample_allocator, .{
524 .sequence = 6,
525 .salt = .{ .first = 0x1234_abcd, .second = 0x5678_cdef },
526 }, wal_frame_count);
527 defer deinitPager(sample_allocator, &pager);
528 pager.reserve(.{
529 .wal_frames = wal_frame_count,
530 .wal_pages = wal_page_count,
531 }) catch @panic("pager reserve failed");
532
533 var index: usize = 0;
534 while (index < wal_frame_count) : (index += 1) {
535 var image: [sql.page.size]u8 = undefined;
536 fillWalImage(&image, index);
537 pager.appendWal(
538 @intCast(index % wal_page_count + 1),
539 if ((index + 1) % wal_page_count == 0) @as(u32, wal_page_count) else 0,
540 &image,
541 ) catch @panic("pager wal append failed");
542 }
543
544 sql.file.writeBase(std.Options.debug_io, tmp.dir, "bench.db", &pager) catch @panic("file base write failed");
545 sql.file.writeWal(std.Options.debug_io, tmp.dir, "bench.wal", &pager) catch @panic("file wal write failed");
546 const recover_options = sql.file.RecoverOptions{
547 .paths = .{ .database = "bench.db", .wal = "bench.wal" },
548 .header = .{
549 .sequence = 7,
550 .salt = .{ .first = 0xaaaa_bbbb, .second = 0xcccc_dddd },
551 },
552 };
553 var workspace = sql.FileDatabase.Workspace.allocate(sample_allocator, .{
554 .header = recover_options.header,
555 .max_wal_bytes = recover_options.max_wal_bytes,
556 .path_storage = .{ .database_bytes = 0, .wal_bytes = 0 },
557 }) catch @panic("file recover workspace failed");
558 defer workspace.deallocate(sample_allocator);
559 var recovered = sql.file.recover(
560 sample_allocator,
561 &workspace,
562 tmp.dir,
563 recover_options,
564 ) catch @panic("file recover failed");
565 defer workspace.release(&recovered);
566
567 const snapshot = recovered.beginRead() catch @panic("recover snapshot failed");
568 var checksum: usize = 0;
569 var page_id: u32 = 1;
570 while (page_id <= wal_page_count) : (page_id += 1) {
571 if (snapshot.get(page_id) catch @panic("recover lookup failed")) |image| {
572 checksum +%= image[0];
573 checksum +%= image[1];
574 }
575 }
576 std.mem.doNotOptimizeAway(checksum);
577 }
578
579 fn fileDatabaseLifecycle(sample_allocator: std.mem.Allocator) void {
580 var tmp = std.testing.tmpDir(.{});
581 defer tmp.cleanup();
582
583 {
584 var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
585 .paths = .{ .database = "bench.db", .wal = "bench.wal" },
586 .header = .{
587 .sequence = 8,
588 .salt = .{ .first = 0x1111_aaaa, .second = 0x2222_bbbb },
589 },
590 }) catch @panic("file database open failed");
591 defer database.deinit();
592 database.reserve(.{
593 .wal_frames = wal_frame_count,
594 }) catch @panic("file database reserve failed");
595
596 appendTransactionBatches(&database, wal_frame_count, wal_page_count);
597 database.syncWal() catch @panic("file database sync failed");
598 }
599
600 var reopened = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
601 .paths = .{ .database = "bench.db", .wal = "bench.wal" },
602 .header = .{
603 .sequence = 9,
604 .salt = .{ .first = 0x3333_cccc, .second = 0x4444_dddd },
605 },
606 }) catch @panic("file database reopen failed");
607 defer reopened.deinit();
608
609 var read = reopened.beginRead() catch @panic("file database snapshot failed");
610 defer read.deinit();
611 const snapshot = read.snapshot();
612 var checksum: usize = 0;
613 var page_id: u32 = 1;
614 while (page_id <= wal_page_count) : (page_id += 1) {
615 var image: [sql.page.size]u8 = undefined;
616 if (snapshot.copyPage(page_id, &image) catch @panic("file database lookup failed")) {
617 checksum +%= image[0];
618 checksum +%= image[1];
619 }
620 }
621 std.mem.doNotOptimizeAway(checksum);
622 }
623
624 fn fileDurableCheckpointRotation(sample_allocator: std.mem.Allocator) void {
625 var tmp = std.testing.tmpDir(.{});
626 defer tmp.cleanup();
627
628 var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
629 .paths = .{ .database = "bench.db", .wal = "bench.wal" },
630 .header = .{
631 .sequence = 100,
632 .salt = .{ .first = 0xaaaa_1111, .second = 0xbbbb_2222 },
633 },
634 }) catch @panic("file durable checkpoint open failed");
635 defer database.deinit();
636 database.reserve(.{
637 .wal_frames = durable_checkpoint_pages,
638 .wal_pages = durable_checkpoint_pages,
639 }) catch @panic("file durable checkpoint reserve failed");
640
641 const phase = bench.phaseAt("sql.file.durable_checkpoint.rotation", @src());
642 phase.setName("file durable checkpoint rotation 1024x4");
643 var checksum: usize = 0;
644 for (0..durable_checkpoint_rotations) |rotation| {
645 {
646 var tx = database.beginWrite() catch @panic("file durable checkpoint transaction failed");
647 defer tx.deinit();
648 for (0..durable_checkpoint_pages) |index| {
649 const page_id = rotation * durable_checkpoint_pages + index + 1;
650 var image: [sql.page.size]u8 = undefined;
651 fillSparseWalImage(&image, page_id);
652 tx.putPage(@intCast(page_id), &image) catch @panic("file durable checkpoint put failed");
653 }
654 _ = tx.commit(.{}) catch @panic("file durable checkpoint commit failed");
655 }
656 const checkpoint_value = database.checkpoint(.{ .restart_header = .{
657 .sequence = @intCast(101 + rotation),
658 .salt = .{
659 .first = @intCast(0xcccc_3000 + rotation),
660 .second = @intCast(0xdddd_4000 + rotation),
661 },
662 } }) catch @panic("file durable checkpoint failed");
663 const storage = database.pager.storage();
664 if (!checkpoint_value.restarted or storage.base_images != 0 or storage.base_capacity != 0) {
665 @panic("file durable checkpoint retained base images");
666 }
667 checksum +%= checkpoint_value.pages;
668 }
669 phase.end();
670 coz.progressNamed("sql.file.durable_checkpoint.rotation");
671 std.mem.doNotOptimizeAway(checksum);
672 }
673
674 fn fileDatabaseCleanOpen(sample_allocator: std.mem.Allocator) void {
675 var tmp = std.testing.tmpDir(.{});
676 defer tmp.cleanup();
677
678 const initial_header: sql.wal.Header = .{
679 .sequence = 90,
680 .salt = .{ .first = 0x1111_dddd, .second = 0x2222_eeee },
681 };
682 const clean_header: sql.wal.Header = .{
683 .sequence = 91,
684 .salt = .{ .first = 0x3333_aaaa, .second = 0x4444_bbbb },
685 };
686
687 {
688 var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
689 .paths = .{ .database = "bench.db", .wal = "bench.wal" },
690 .header = initial_header,
691 }) catch @panic("file database clean open setup failed");
692 defer database.deinit();
693 database.reserve(.{
694 .wal_frames = wal_page_count,
695 }) catch @panic("file database clean open reserve failed");
696
697 appendTransactionBatches(&database, wal_page_count, wal_page_count);
698 database.syncWal() catch @panic("file database clean open sync failed");
699 _ = database.checkpoint(.{ .restart_header = clean_header }) catch @panic("file database clean open checkpoint failed");
700 }
701
702 const phase = bench.phaseAt("sql.file.database.clean_open", @src());
703 phase.setName("file database clean open 16x32");
704 defer phase.end();
705
706 var checksum: usize = 0;
707 var repeat: usize = 0;
708 while (repeat < file_clean_open_repeats) : (repeat += 1) {
709 {
710 var reopened = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
711 .paths = .{ .database = "bench.db", .wal = "bench.wal" },
712 .header = clean_header,
713 }) catch @panic("file database clean open reopen failed");
714 defer reopened.deinit();
715 var read = reopened.beginRead() catch @panic("file database clean open snapshot failed");
716 defer read.deinit();
717 const snapshot = read.snapshot();
718 var image: [sql.page.size]u8 = undefined;
719 if (snapshot.copyPage(1, &image) catch @panic("file database clean open lookup failed")) {
720 checksum +%= image[0];
721 checksum +%= image[1];
722 }
723 checksum +%= reopened.wal_written;
724 }
725 }
726 std.mem.doNotOptimizeAway(checksum);
727 }
728
729 fn fileDatabaseTransactions(sample_allocator: std.mem.Allocator) void {
730 var tmp = std.testing.tmpDir(.{});
731 defer tmp.cleanup();
732
733 {
734 var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
735 .paths = .{ .database = "bench.db", .wal = "bench.wal" },
736 .header = .{
737 .sequence = 10,
738 .salt = .{ .first = 0x5555_aaaa, .second = 0x6666_bbbb },
739 },
740 }) catch @panic("file database open failed");
741 defer database.deinit();
742 database.reserve(.{
743 .wal_frames = wal_frame_count,
744 }) catch @panic("file database reserve failed");
745
746 var index: usize = 0;
747 while (index < wal_frame_count) {
748 {
749 var tx = database.beginWrite() catch @panic("file database transaction failed");
750 defer tx.deinit();
751
752 var page_index: usize = 0;
753 while (page_index < wal_page_count and index < wal_frame_count) : ({
754 page_index += 1;
755 index += 1;
756 }) {
757 var image: [sql.page.size]u8 = undefined;
758 fillWalImage(&image, index);
759 tx.putPage(@intCast(page_index + 1), &image) catch @panic("file database transaction put failed");
760 }
761
762 _ = tx.commit(.{ .durability = .buffered }) catch @panic("file database transaction commit failed");
763 }
764 }
765 database.syncWal() catch @panic("file database sync failed");
766 }
767
768 var reopened = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
769 .paths = .{ .database = "bench.db", .wal = "bench.wal" },
770 .header = .{
771 .sequence = 11,
772 .salt = .{ .first = 0x7777_cccc, .second = 0x8888_dddd },
773 },
774 }) catch @panic("file database reopen failed");
775 defer reopened.deinit();
776
777 var read = reopened.beginRead() catch @panic("file database snapshot failed");
778 defer read.deinit();
779 const snapshot = read.snapshot();
780 var checksum: usize = 0;
781 var page_id: u32 = 1;
782 while (page_id <= wal_page_count) : (page_id += 1) {
783 var image: [sql.page.size]u8 = undefined;
784 if (snapshot.copyPage(page_id, &image) catch @panic("file database lookup failed")) {
785 checksum +%= image[0];
786 checksum +%= image[1];
787 }
788 }
789 std.mem.doNotOptimizeAway(checksum);
790 }
791
792 fn fileDatabaseTransactionBatch(sample_allocator: std.mem.Allocator) void {
793 var tmp = std.testing.tmpDir(.{});
794 defer tmp.cleanup();
795
796 var database = sql.FileDatabase.openForTesting(sample_allocator, tmp.dir, .{
797 .paths = .{ .database = "bench.db", .wal = "bench.wal" },
798 .header = .{
799 .sequence = 12,
800 .salt = .{ .first = 0x1357_2468, .second = 0xace0_bdf1 },
801 },
802 }) catch @panic("file transaction batch open failed");
803 defer database.deinit();
804 database.reserve(.{
805 .wal_frames = transaction_batch_pages,
806 .wal_pages = transaction_batch_pages,
807 }) catch @panic("file transaction batch reserve failed");
808
809 var tx = database.beginWrite() catch @panic("file transaction batch begin failed");
810 defer tx.deinit();
811 for (0..transaction_batch_pages) |index| {
812 var image: [sql.page.size]u8 = undefined;
813 fillWalImage(&image, index);
814 tx.putPage(@intCast(index + 1), &image) catch @panic("file transaction batch put failed");
815 }
816
817 const phase = bench.phaseAt("sql.file.transaction.durable_batch", @src());
818 phase.setName("file transaction durable batch 1024");
819 _ = tx.commit(.{}) catch @panic("file transaction batch commit failed");
820 phase.end();
821 coz.progressNamed("sql.file.transaction.durable_batch");
822 if (database.wal_io.writes != 1 or database.wal_io.resizes != 0 or database.wal_io.syncs != 1) {
823 @panic("file transaction batch issued other than one write and one sync");
824 }
825 std.mem.doNotOptimizeAway(database.wal_written);
826 }
827
828 fn appendTransactionBatches(database: *sql.FileDatabase, frames: usize, pages_per_transaction: usize) void {
829 var index: usize = 0;
830 while (index < frames) {
831 var tx = database.beginWrite() catch @panic("file database transaction failed");
832 defer tx.deinit();
833 var page_index: usize = 0;
834 while (page_index < pages_per_transaction and index < frames) : ({
835 page_index += 1;
836 index += 1;
837 }) {
838 var image: [sql.page.size]u8 = undefined;
839 fillWalImage(&image, index);
840 tx.putPage(@intCast(page_index + 1), &image) catch @panic("file database transaction put failed");
841 }
842 _ = tx.commit(.{ .durability = .buffered }) catch @panic("file database transaction commit failed");
843 }
844 }