Context
The main SQLite DSN currently sets _synchronous=NORMAL (internal/store/store.go:37):
const defaultSQLiteParams = "?_journal_mode=WAL&_busy_timeout=30000&_synchronous=NORMAL&_foreign_keys=ON"
With WAL + NORMAL, SQLite fsyncs the WAL at checkpoints but not on every commit. Per the SQLite pragma docs and how-to-corrupt guide:
NORMAL is durability-risky (a committed transaction can be rolled back on power loss) but generally not corruption-risky in normal operation.
FULL adds a WAL fsync after each commit, closing the narrow checkpoint-sync failure window that can cause corruption.
- Maximum-reliability recommendation is
FULL.
Proposal
Change the default to _synchronous=FULL for the main Open() path, framed as archival-durability hardening.
Rationale:
- msgvault is an archive, not a latency-sensitive OLTP workload. One extra fsync per commit is imperceptible against Gmail sync throughput.
- The name "vault" implies durability over speed.
- Defense-in-depth against the one corruption window that
NORMAL does leave open.
This is not framed as the fix for #287 — that incident's root cause is not established (could be hardware, WAL sidecar copies, iCloud Drive, kernel panic, etc.). This is independent hardening.
Scope
- Change: default
_synchronous=FULL in defaultSQLiteParams (internal/store/store.go:37).
- Config escape hatch: support
[sync] synchronous = \"normal\" in config.toml for users who prefer the older behavior.
- Release note: document the perf tradeoff.
- Do not change:
OpenReadOnly (synchronous is meaningless on read-only connections) and subset.go (create-subset is the disposable-rebuild case — interrupted runs are re-run, target file is not a long-lived archive).
Out of scope
Any other durability/WAL changes — keep the diff narrow.
Context
The main SQLite DSN currently sets
_synchronous=NORMAL(internal/store/store.go:37):With WAL +
NORMAL, SQLite fsyncs the WAL at checkpoints but not on every commit. Per the SQLite pragma docs and how-to-corrupt guide:NORMALis durability-risky (a committed transaction can be rolled back on power loss) but generally not corruption-risky in normal operation.FULLadds a WAL fsync after each commit, closing the narrow checkpoint-sync failure window that can cause corruption.FULL.Proposal
Change the default to
_synchronous=FULLfor the mainOpen()path, framed as archival-durability hardening.Rationale:
NORMALdoes leave open.This is not framed as the fix for #287 — that incident's root cause is not established (could be hardware, WAL sidecar copies, iCloud Drive, kernel panic, etc.). This is independent hardening.
Scope
_synchronous=FULLindefaultSQLiteParams(internal/store/store.go:37).[sync] synchronous = \"normal\"in config.toml for users who prefer the older behavior.OpenReadOnly(synchronous is meaningless on read-only connections) andsubset.go(create-subset is the disposable-rebuild case — interrupted runs are re-run, target file is not a long-lived archive).Out of scope
Any other durability/WAL changes — keep the diff narrow.