Durability & crash safety

How generated writes stay crash-safe — a per-model write-ahead log fsynced before columns, torn-tail recovery, a single-writer directory lock, and a bounded WAL.

Writes are crash-safe. If the process is killed mid-write — even with kill -9 — you never lose a row that was already acknowledged, and the data directory never ends up corrupt. ForgeDB generates this write path per model; nothing reads your schema at runtime.

The durable write path#

Every insert, update, and delete first records what it is about to do in a per-model log, flushes that record to disk, and only then writes the data columns. Because the log lands first, a crash partway through leaves behind a record that recovery can replay.

Recovery and torn-tail repair#

On open, ForgeDB checks each model, discards any half-written row at the end of the columns, and replays the log to restore anything that was acknowledged but never reached the columns. You can run the proof yourself with make crash-test: it generates real database code, inserts rows, kills the process with no clean shutdown, reopens in a fresh process, and asserts every committed row survived.

Single-writer directory lock#

One process writes to a data directory at a time. A second writer that tries to open the same directory is refused rather than allowed to race. This is the v1 contract: single writer per process, per data directory.

Single-writer-per-process is the v1 contract

Concurrent writers are not handled by simply opening the directory twice — the lock refuses that. Multi-process writes are coordinated by a separate control plane; see Transactions & MVCC.

Bounding the WAL — checkpointing#

Left alone, the log would grow forever. So ForgeDB flushes the columns to disk and then trims the log once a model has taken a set number of writes. The log sawtooths instead of growing without bound, and a crash right after a trim only has to replay the small recent tail.

Configuring durability#

Fsync is a knob, not a fixed choice:

[storage]
fsync = "always"   # "always" | "never"
wal_checkpoint_interval = 1000

always, the default, flushes on every commit for full crash safety.

`never` trades safety for throughput

Setting fsync = "never" removes the F_FULLFSYNC barrier from the write path — much faster, but a crash can lose recently-acknowledged writes. Use it only when the durability requirement genuinely allows it.

See storage configuration for the full set of storage knobs, and Backup & restore for taking snapshots of a durable data directory.

Limits#

  • One writer per process, per data directory — enforced by the directory lock. Multi-process writes go through a separate coordinator; see Transactions & MVCC.
  • Many-to-many link tables have no log of their own, so a crash mid-link can leave a duplicate pair. Traversal is latest-wins and does not dedupe.
  • The checkpoint interval is tunable through [storage].wal_checkpoint_interval; fsync is baked in when you generate, not changed at runtime.

Search documentation

Find pages across the ForgeDB docs