Write-behind snapshot persistence
Read references/STATE-SNAPSHOT-PERSISTENCE.md before applying any of this.
That file is the standard; everything below it is a summary to help you decide
whether this skill applies and to check your work afterwards.
Reference-architecture principles: P4, P8.
What this standard covers
- Mark dirty, flush on a timer
- The schema is one table, bootstrapped idempotently
- Rehydrate on start, with a bound
- A dead database degrades; it never blocks ingest
- Deletions must reach the store, or they come back
- When not to use this
Failure modes
| Symptom | Cause |
|---|---|
| A burst of traffic produces a burst of writes and latency follows it | Writing on mutation instead of marking dirty and flushing on a timer (§1) |
| The hot path stalls whenever the database is slow | The flush holds the lock across the database call, so the hot path queues behind it (§1) |
| One entry stops being persisted and nothing reports it | A failed write dropped its key instead of re-queueing; the entry updates again only if it is touched again (§1) |
| Deleted or merged entries reappear after a restart | The removal never reached the table, so rehydrate restored them as ghosts (§5) |
| Startup time grows with the size of the history | Rehydrate is unbounded — it reads the whole table rather than the most recent N (§3) |
| The service ran for weeks with no persistence and nobody knew | The degrade-to-memory path logs at a level nobody reads (§4) |
| A schema change requires a migration this shape was chosen to avoid | Too many fields promoted out of the document into columns (§2) |
| A reviewer reads the idempotent DDL as a skipped migration | The choice between this shape and an ORM-managed schema was never written down (§6) |
Checklist
- The hot path only marks keys dirty under a lock — no serialising, no awaiting, no database call
- The flush loop swaps the dirty set inside the lock and writes outside it
- A failed write re-queues its key for the next tick rather than retrying in place or dropping it
- One table, one JSON document per aggregate, columns promoted only for what the read path queries or sorts on
- Schema bootstrapped with idempotent DDL, and the upsert safe to repeat on the same key
- Rehydrate on startup is bounded, ordered by a promoted column, and the bound is configuration
- Bootstrap and rehydrate failures log and continue; an unavailable database never blocks the hot path or fails the boot
- Every in-memory removal and merge deletes the corresponding row
- The repository records why this shape was chosen over an ORM-managed schema
Generated from docs/guides/STATE-SNAPSHOT-PERSISTENCE.md by scripts/build-marketplace.mjs. Do not edit this file: change the source document, or its entry in catalog/marketplace.catalog.json, and re-run the generator.