querylog: checkpoint every 8192 wal pages instead of 1000

This commit is contained in:
2026-08-21 18:43:45 +02:00
parent 324704b53f
commit 9b0b7c19f4
7 changed files with 140 additions and 3 deletions
+27 -2
View File
@@ -77,6 +77,31 @@ pub const fingerprint: i32 = blk: {
const set_user_version = std.fmt.comptimePrint("PRAGMA user_version = {d};", .{fingerprint});
/// The WAL checkpoint threshold for every read-write `querylog.db` connection,
/// in pages (32 MiB at the 4096-byte page size). SQLite's 1000-page default
/// trips every ~40 min under this workload and rewrites the same hot index and
/// interior pages into the main database each time; 8192 stretches that to ~5 h
/// and cuts those in-place rewrites ~8x, which is SD-card write wear this
/// household appliance does not need to spend.
///
/// The durability consequence, stated precisely:
///
/// - Commit never fsyncs at `synchronous = NORMAL`; the checkpoint's fsync is
/// the only guaranteed durability boundary. This moves that boundary from
/// ~40 min to ~5 h of querylog data (query rows plus upstream-history
/// minutes) under power loss or kernel panic. Typical loss stays far smaller
/// because of kernel writeback, but that is not a guarantee.
/// - Process crash or clean stop loses nothing committed, at any threshold.
/// Consistency is never at risk: recovery replays the longest valid WAL
/// prefix atomically.
/// - 32 MiB is an expectation, not a cap: a pinned reader snapshot stops a
/// passive checkpoint partway and the WAL overshoots until that reader
/// finishes. The daily retention `wal_checkpoint(TRUNCATE)` is the backstop
/// that shrinks the file.
///
/// `config.db` keeps the SQLite default: it holds configuration, not a log.
pub const wal_autocheckpoint_pages: i32 = 8192;
/// Long enough for any path this program will be handed, plus the aside suffix.
/// A longer path is `error.NameTooLong`, which is what the filesystem calls
/// would have returned anyway.
@@ -126,7 +151,7 @@ pub fn open(io: std.Io, dir: std.Io.Dir, path: [:0]const u8) Error!OpenResult {
break :probe recreatable(e) orelse return e;
const opened = &handle.?;
db.applyPragmas(opened, .{}) catch |e|
db.applyPragmas(opened, .{ .wal_autocheckpoint_pages = wal_autocheckpoint_pages }) catch |e|
break :probe recreatable(e) orelse return e;
const healthy = quickCheck(opened) catch |e|
@@ -250,7 +275,7 @@ fn deleteSidecars(io: std.Io, dir: std.Io.Dir, path: []const u8) Error!void {
fn createFresh(path: [:0]const u8) db.Error!db.Db {
var database = try db.Db.open(path, .{ .mode = .read_write_create });
errdefer database.close();
try db.applyPragmas(&database, .{});
try db.applyPragmas(&database, .{ .wal_autocheckpoint_pages = wal_autocheckpoint_pages });
var tx = try db.Tx.begin(&database);
errdefer tx.rollback();