writeonce/docs/examples/residency/README.md
shoney.arickathil 516bd8362d docs(db2-keys): a runnable example for per-table storage
- docs/examples/residency: one program, two tables filled by the same
  loop, differing only in the annotation. Run twice against one WO_DATA
  and orders replay while sessions do not
- the example checks its own claim (exits 1 if a durable:false table
  survives, or a durable:true one fails to replay) rather than narrating
  it in a print
- resident: keys is written out as a commented block with the loader's
  exact refusal, so the frontier is visible in the example rather than
  only in a story. It documents WHERE the refusal happens: woc compiles
  it and emits a .wob; wovm exits 2, because the annotation is a
  load-time property
- residency-accept gains two legs: the example runs and its restart
  claim holds, and the refusal message the README quotes is checked so
  doc and code cannot drift apart
- the gate writes the example's output to /tmp/residency.log,
  banner-separated, for tail -F
- README commands verified verbatim; they needed mkdir -p because wovm
  will not create WO_DATA

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit c9c7e03e62c3918cb65ef7994d1e33a0c5337b71)
2026-08-30 20:37:27 +02:00

88 lines
3.7 KiB
Markdown

# residency — per-table storage
What [databasev2 2](../../stories/databasev2/02-table-storage-modes.md) added:
`durable` and `resident`, declared per `@table` instead of one environment
variable for the whole process.
Before it, `WO_DATA` was the only switch. Set, and every table is WAL-logged;
unset, and none are. Applications are not uniform — a session table is
disposable, an orders table is precious, and a 120 GB audit table does not fit
in RAM at all. One global switch forces "everything is precious" or "nothing
is", and you pay for whichever is wrong.
## Run it
```
just residency
```
The gate writes the example's own output to `/tmp/residency.log`, banner-
separated, so you can `tail -F` it while it runs.
Or by hand, which is the whole demonstration — the same program twice against
one data directory:
```
compiler/_build/default/bin/woc --emit docs/examples/residency/main.wo -o /tmp/residency.wob
mkdir -p /tmp/residency-data # WO_DATA must exist; wovm will not create it
WO_DATA=/tmp/residency-data runtime/wovm /tmp/residency.wob seed
WO_DATA=/tmp/residency-data runtime/wovm /tmp/residency.wob
```
```
seeded: orders=3 sessions=3
after restart: orders=3 sessions=0
ok: durable replayed, volatile did not
```
The second run inserts nothing. Three orders come back from the log; zero
sessions do, because they were never written to it. Both tables were filled by
the same loop — only the annotation differs, so the difference after the
restart is the annotation's doing and nothing else's.
## The three modes
| Declaration | Meaning | State today |
| --- | --- | --- |
| `durable: true` (default) | WAL-logged, replayed at boot | ✅ works |
| `durable: false` | never written to the log; costs no disk and no fsync; empty after a restart | ✅ works |
| `resident: all` (default) | every row's payload lives in RAM | ✅ works |
| `resident: keys` | the id map stays resident, the payload lives in the WAL and is read back by offset | ⛔ **refused at load** |
## Why `resident: keys` is refused
It is the mode the track exists for — a table larger than RAM. Storage, the
read paths, deletes and checkpoint survival all work. **Updating such a row
does not:** the row has no slab slot to mutate, so a write would land in a
scratch buffer and be discarded *silently*. Doing it properly is
read-modify-append.
So the loader refuses the annotation rather than honouring it in name only.
Uncomment the `AuditEntry` block in `main.wo` and you get:
```
wovm: class 0 declares `resident: keys`, which is INCOMPLETE: rows are stored
and read keys-only, but UPDATING one is not implemented (it needs
read-modify-append). Remove it until databasev2 2 lands updates;
`resident: all` is what runs
```
Note **where** that comes from: `woc` compiles it happily and emits a `.wob`.
The annotation is a load-time property, so the compiler is green and `wovm`
exits 2.
Refusing at load rather than at the first update is deliberate. A developer who
declared a 120 GB table keys-resident, saw it compile, and shipped would find
the gap in production. That judgement earned its keep in a way nobody had
written down: an audit before relaxing the refusal found that `delete` on such
a table was reading a WAL byte offset as a slab index and freeing whatever it
landed on — memory corruption, not a missing feature. It is fixed and pinned by
a test that SEGVs against the old code, but the refusal is what stood in front
of it.
## What this example does NOT show
The mode-mismatch startup refusal, the zero-WAL-bytes measurement, and the two
compile-time refusals are proven by `scripts/residency-accept.sh` against
purpose-built snippets, because each needs a deliberately broken program or a
byte-level assertion on the log file. This example is the readable half.