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 }