fix(core): harden fusion.db pragmas against mid-write crashes

Switch synchronous from NORMAL to FULL and restore the default
wal_autocheckpoint of 1000 (was 100). The aggressive checkpoint
cadence + NORMAL fsync left a wide window for torn pages whenever
a writer crashed mid-checkpoint, which is exactly what happened
when node:sqlite SIGSEGV'd inside pager_write and corrupted the
production db.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-19 22:05:36 -07:00
parent 61dd439e20
commit a164e84b46
3 changed files with 16 additions and 8 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Harden `fusion.db` against process crashes: switch `PRAGMA synchronous` from `NORMAL` to `FULL` and restore the default `wal_autocheckpoint = 1000` (was 100). Repeated node:sqlite SIGSEGVs inside `pager_write` had been corrupting the db; the previous settings left a wide window for torn pages whenever a writer crashed mid-checkpoint. The small fsync cost is worth the durability win.

View File

@@ -194,8 +194,8 @@ describe("Database", () => {
const autoCheckpoint = db.prepare("PRAGMA wal_autocheckpoint").get() as { wal_autocheckpoint: number };
const journalSizeLimit = db.prepare("PRAGMA journal_size_limit").get() as { journal_size_limit: number };
expect(synchronous.synchronous).toBe(1); // NORMAL
expect(autoCheckpoint.wal_autocheckpoint).toBe(100);
expect(synchronous.synchronous).toBe(2); // FULL
expect(autoCheckpoint.wal_autocheckpoint).toBe(1000);
expect(journalSizeLimit.journal_size_limit).toBe(4_194_304);
});

View File

@@ -1319,12 +1319,15 @@ export class Database {
this.db.exec(`PRAGMA busy_timeout = ${this.busyTimeoutMs}`);
// Enable WAL mode for concurrent reader/writer access
this.db.exec("PRAGMA journal_mode = WAL");
// In WAL mode NORMAL is nearly as durable as FULL with much lower fsync cost.
this.db.exec("PRAGMA synchronous = NORMAL");
// Checkpoint every 100 pages (~400 KB) to keep WAL small and reduce
// corruption risk. More aggressive than the default 1000, but paired
// with journal_size_limit to prevent WAL bloat.
this.db.exec("PRAGMA wal_autocheckpoint = 100");
// FULL fsyncs on every commit. Slightly slower than NORMAL, but the only
// setting that survives a process crash mid-checkpoint without torn pages
// — repeated node:sqlite SIGSEGVs inside pager_write have corrupted this
// db before.
this.db.exec("PRAGMA synchronous = FULL");
// Default (1000) checkpoint cadence. The previous value of 100 made the
// db spend most of its life mid-checkpoint, multiplying corruption risk
// when a writer crashed. journal_size_limit below still caps WAL growth.
this.db.exec("PRAGMA wal_autocheckpoint = 1000");
// Bound WAL growth between checkpoints/maintenance cycles.
this.db.exec("PRAGMA journal_size_limit = 4194304");
} else {