querylog: checkpoint every 8192 wal pages instead of 1000
This commit is contained in:
+3
-1
@@ -351,7 +351,9 @@ pub const DataDir = struct {
|
||||
_ = io;
|
||||
var database = try db.Db.open(self.querylog_db_path, .{ .mode = .read_write_existing });
|
||||
errdefer database.close();
|
||||
try db.applyPragmas(&database, .{});
|
||||
try db.applyPragmas(&database, .{
|
||||
.wal_autocheckpoint_pages = querylog_schema.wal_autocheckpoint_pages,
|
||||
});
|
||||
return database;
|
||||
}
|
||||
|
||||
|
||||
@@ -754,6 +754,9 @@ pub const Pragmas = struct {
|
||||
journal_wal: bool = true,
|
||||
synchronous_normal: bool = true,
|
||||
foreign_keys: bool = true,
|
||||
/// Null leaves SQLite's 1000-page default. A caller that sets it owns the
|
||||
/// durability consequences, which depend on what the database holds.
|
||||
wal_autocheckpoint_pages: ?i32 = null,
|
||||
};
|
||||
|
||||
/// MUST be called before any transaction is opened: `PRAGMA foreign_keys` is a
|
||||
@@ -785,6 +788,16 @@ pub fn applyPragmas(self: *Db, p: Pragmas) Error!void {
|
||||
return error.SqliteError;
|
||||
}
|
||||
}
|
||||
if (p.wal_autocheckpoint_pages) |pages| {
|
||||
var buf: [64]u8 = undefined;
|
||||
const sql = std.fmt.bufPrintZ(&buf, "PRAGMA wal_autocheckpoint = {d};", .{pages}) catch unreachable;
|
||||
try self.exec(sql);
|
||||
const applied = try self.queryInt("PRAGMA wal_autocheckpoint");
|
||||
if (applied != pages) {
|
||||
log.warn("PRAGMA wal_autocheckpoint = {d} reported {d}", .{ pages, applied });
|
||||
return error.SqliteError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A write transaction.
|
||||
@@ -907,6 +920,18 @@ test "applyPragmas succeeds and foreign_keys reads back as 1" {
|
||||
try testing.expectEqual(@as(i64, 1), try db.queryInt("PRAGMA foreign_keys"));
|
||||
}
|
||||
|
||||
test "applyPragmas leaves wal_autocheckpoint at the default unless a page count is given" {
|
||||
var db = try openMemory();
|
||||
defer db.close();
|
||||
try applyPragmas(&db, .{});
|
||||
try testing.expectEqual(@as(i64, 1000), try db.queryInt("PRAGMA wal_autocheckpoint"));
|
||||
|
||||
var configured = try openMemory();
|
||||
defer configured.close();
|
||||
try applyPragmas(&configured, .{ .wal_autocheckpoint_pages = 8192 });
|
||||
try testing.expectEqual(@as(i64, 8192), try configured.queryInt("PRAGMA wal_autocheckpoint"));
|
||||
}
|
||||
|
||||
test "open on a directory path returns error.CantOpen and leaks no handle" {
|
||||
var i: usize = 0;
|
||||
while (i < 1000) : (i += 1) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -600,6 +600,36 @@ test "S7 case 23: a locked querylog propagates Busy and is never destroyed" {
|
||||
try testing.expectEqual(@as(usize, 0), asides_after.items.items.len);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// the querylog checkpoint threshold (specs/querylog-autocheckpoint.md)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
test "every querylog connection carries the raised wal_autocheckpoint; config.db keeps the default" {
|
||||
if (!build_options.integration) return error.SkipZigTest;
|
||||
|
||||
var f: Fixture = .init();
|
||||
defer f.deinit();
|
||||
|
||||
var data = try openMigrated(&f, "data");
|
||||
defer data.deinit();
|
||||
|
||||
var opened = try data.dir.openQuerylogDb(io);
|
||||
defer opened.database.close();
|
||||
try testing.expectEqual(
|
||||
@as(i64, querylog_schema.wal_autocheckpoint_pages),
|
||||
try opened.database.queryInt("PRAGMA wal_autocheckpoint"),
|
||||
);
|
||||
|
||||
var reopened = try data.dir.reopenQuerylogDb(io);
|
||||
defer reopened.close();
|
||||
try testing.expectEqual(
|
||||
@as(i64, querylog_schema.wal_autocheckpoint_pages),
|
||||
try reopened.queryInt("PRAGMA wal_autocheckpoint"),
|
||||
);
|
||||
|
||||
try testing.expectEqual(@as(i64, 1000), try data.database.queryInt("PRAGMA wal_autocheckpoint"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// case 8-10: config.db, permissions and the schema stamp
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user