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:
5
.changeset/fusion-db-synchronous-full.md
Normal file
5
.changeset/fusion-db-synchronous-full.md
Normal 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.
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user