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 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 };
|
const journalSizeLimit = db.prepare("PRAGMA journal_size_limit").get() as { journal_size_limit: number };
|
||||||
|
|
||||||
expect(synchronous.synchronous).toBe(1); // NORMAL
|
expect(synchronous.synchronous).toBe(2); // FULL
|
||||||
expect(autoCheckpoint.wal_autocheckpoint).toBe(100);
|
expect(autoCheckpoint.wal_autocheckpoint).toBe(1000);
|
||||||
expect(journalSizeLimit.journal_size_limit).toBe(4_194_304);
|
expect(journalSizeLimit.journal_size_limit).toBe(4_194_304);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1319,12 +1319,15 @@ export class Database {
|
|||||||
this.db.exec(`PRAGMA busy_timeout = ${this.busyTimeoutMs}`);
|
this.db.exec(`PRAGMA busy_timeout = ${this.busyTimeoutMs}`);
|
||||||
// Enable WAL mode for concurrent reader/writer access
|
// Enable WAL mode for concurrent reader/writer access
|
||||||
this.db.exec("PRAGMA journal_mode = WAL");
|
this.db.exec("PRAGMA journal_mode = WAL");
|
||||||
// In WAL mode NORMAL is nearly as durable as FULL with much lower fsync cost.
|
// FULL fsyncs on every commit. Slightly slower than NORMAL, but the only
|
||||||
this.db.exec("PRAGMA synchronous = NORMAL");
|
// setting that survives a process crash mid-checkpoint without torn pages
|
||||||
// Checkpoint every 100 pages (~400 KB) to keep WAL small and reduce
|
// — repeated node:sqlite SIGSEGVs inside pager_write have corrupted this
|
||||||
// corruption risk. More aggressive than the default 1000, but paired
|
// db before.
|
||||||
// with journal_size_limit to prevent WAL bloat.
|
this.db.exec("PRAGMA synchronous = FULL");
|
||||||
this.db.exec("PRAGMA wal_autocheckpoint = 100");
|
// 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.
|
// Bound WAL growth between checkpoints/maintenance cycles.
|
||||||
this.db.exec("PRAGMA journal_size_limit = 4194304");
|
this.db.exec("PRAGMA journal_size_limit = 4194304");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user