writeonce/docs/examples/db-bench
shoney.arickathil 58dcb1f969 test(db2-7): gate leg for WO_DATA=<file> — residency section 8, db-bench --wo-data-file
- residency-accept.sh section 8 (11 checks): file-form seed -> restart prints
  the directory form's line; `find -mindepth 1` shows exactly app.db; missing
  parent exits 2 naming path + parent, no mkdir -p; a fifo exits 2 "neither a
  regular file nor a directory"; `d/` still writes d/shard-0.wal; `nodir/`
  keeps the pre-7 "cannot open .../nodir//shard-0.wal" bytes; WO_EPHEMERAL=1
  with the file exits 2 on the 6a conflict
- kill -9 battery against app.db: stdbuf -oL vehicle, asserts the kill landed
  (rc 137) before verifying every acked row replays; forced compaction
  (WO_CHECKPOINT_BYTES=1, WO_WAL_STATS proves >= 1 ran) leaves app.db the only
  artifact and every row replays
- failing-first on the pre-7 wovm: 9 of 12 new checks red ("cannot open
  .../app.db/shard-0.wal"); the two trailing-slash pins and the 6a conflict
  pass by construction — they pin what must stay byte-identical
- db-bench.py --wo-data-file: restart proof + crash battery against
  <tmp>/app.db, legs tagged .file, file form also asserts app.db is the only
  artifact; no metric, bench/baseline.json untouched; quick run unchanged
  without the flag (181 checks / 5 failures both ways, all five the known
  residency.keys.fit rc 74)
- READMEs: db-bench env-knob row for WO_DATA=<path>.db + the driver flag;
  residency run instructions name the file form
- gates: just residency 32/1 (the seed rc, pre-existing), make -C runtime
  test 21 suites 8452/0, just oop-e2e 129/0

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit aaea6b2c0f818efd0cdf472ccbd9bdd09eac5554)
2026-09-15 01:16:13 +02:00
..
main.wo feat(db2-keys): gate the residency measurement, close out task 7 2026-08-30 20:38:03 +02:00
README.md test(db2-7): gate leg for WO_DATA=<file> — residency section 8, db-bench --wo-data-file 2026-09-15 01:16:13 +02:00
types.wo feat(db2-keys): gate the residency measurement, close out task 7 2026-08-30 20:38:03 +02:00
wo.toml feat(db-bench): sample — serial modes, histogram stats (T2) 2026-08-21 16:18:26 +02:00

db-bench — iteration 22's load generator

The measurement backbone (spec: docs/superpowers/specs/2026-08-21-db-bench-design.md). Pure .wo; every measured mode prints one machine-parsable line per operation class:

<op> <count> <ops/sec> <p50us> <p99us>

Timing is per-operation via time.ticks (CLOCK_MONOTONIC µs). Percentiles come from a 1µs-bucket histogram clamped at 20000µs — exact to the microsecond below the clamp; a p99 AT 20000 means "clamp or worse". (A histogram, not the spec's reservoir: the language has no container element-write or sort, and the histogram's tail fidelity is strictly better. Recorded as a plan deviation.)

Modes

mode what it prices
all N the throughput campaign in ONE process: seed N, read N/2, query N/10, write N/2. Without WO_DATA the store is RAM and dies with the process, so the measured modes must share the seeding run.
seed N timed inserts: one parent per 100 children (FK probe each insert, unique-index maintenance per parent), k non-unique (10 rows/key), deterministic v. Writes Meta expectation rows.
read N indexed take-1 point lookups, LCG-spread keys.
query N full equality probes on the k index (≈10 rows each), materialized and counted.
write N alternating inserts (disjoint k range 2e6+) and updates through query results. Corrupts the checksum by design — durability legs run on a fresh store.
wal N the crash battery's vehicle: insert-only (k range 1e6+), acked <i> printed AFTER each insert returns — the return IS the ack (RAM applied, WAL record staged, ONE commit done).
wmix N C databasev2 4: every op a durable write (update through a query result), C at once. Exists because mix writes on one op in ten with C=4 — 20 writes in a quick run, measured mean batch 1.01 — so no existing leg could show whether group commit engages. Histogram kind 2, because a replayed store still holds the seeding run's kind-0/1 Hist rows. Seed first.
boot databasev2 3: does NOTHING. With WO_DATA set the runtime replays the whole log before main runs, so a mode with no work of its own is the only honest way to price boot
verify store vs its own Meta rows: count, checksum, one unique probe. Exit 3 on mismatch.
verify-acked M after kill -9 mid-wal: rows 1..M exist with the right v; rows beyond M allowed (acked after the last print flushed). Exit 3 on mismatch.

Env knobs

var effect
WO_DATA=<dir> durability on: replay <dir>/shard-0.wal at boot, log every write. Without it the store is RAM-only
WO_SHARDS=<n> shard count. 1 means every statement runs inline on shard 0 and group commit cannot engage — batches form only where writes queue from other shards
WO_CHECKPOINT_BYTES / WO_CHECKPOINT_RATIO databasev2 3: the checkpoint trigger — the log must exceed the floor AND exceed the ratio times the last compaction's own size. A tiny floor forces compaction in a few writes, which is how the gate tests the policy at all; an enormous one disables it, which is how the checkpoint leg measures the same workload with and without
WO_WAL_STATS=1 databasev2 4: print one line at exit — walstats batches=… records=… peak_batch=… peak_staged=… compactions=… compact_us_max=… compact_us_total=… compacted_bytes=…. Opt-in so it does not pollute every durable program's output. Mean batch is records/batches; mean 1.0 means group commit is not engaging, which is expected for a serial writer or WO_SHARDS=1 and a bug anywhere else
WO_DATA=<path>.db databasev2 7: the store as ONE file — the path IS the log (created if absent, its parent must exist; a directory or trailing / keeps the <dir>/shard-0.wal form). scripts/db-bench.py --wo-data-file runs the restart proof and the kill -9 battery against <tmp>/app.db instead of a directory; same acceptance, no metric, baseline untouched

Do not put WO_DATA on /tmp. It is tmpfs on the reference machine, where fdatasync is free: the same wmix run measured 195 000 ops/s at p50 1 µs there against 2200 ops/s at p50 7200 µs on ext4. There is no durability barrier to price on a memory filesystem. The driver keeps its stores under bench/ for exactly this reason.

Coordination idiom (this side of iteration 31)

There is no request/response surface yet: concurrent modes drive completion the db-actor way — actors write rows, main polls the store until the expected count, then settles. Retired when 31 lands.

Standing finding (2026-08-21, first run)

A hand-built multi Bucket of insert results SEGVs on drop: the compiler classifies the elements OWNED while table refs are scalar ids. Query-built multis are runtime-typed and safe. Worked around here (single ref local, bucket-major seeding); the compiler fix is its own slice.

Reference-machine numbers (first campaign, 2026-08-21)

bench/baseline.json is the contract; headline readings:

  • ram seed 245–290k inserts/s; durable seed ≈4.5k/s (fsync-per-commit ≈220µs each — the gap iteration 23 exists to close).
  • reads/queries ≈1.1–1.3M ops/s at p50 1µs since the read-path index slice (2026-08-22, engine wo_idx_probe + emitter index selection) — up from ≈1.5k/s at p50 600µs when point lookups walked every slab (~×850). mixread 89k ops/s single-shard, ~1.9k multi-shard (was 1,280 / 21): the RPC round-trip is now the visible cost, as designed.
  • msgrate ≈13M msgs/s same-heap vs ≈2.4M cross-shard (the mutex-inbox number, stage-2 deviation 4).
  • Tolerance policy lives in the DRIVER (tolerance_for), not hand-edits — a baseline refresh regenerates it: mix*/sN/read/query 50% (scheduling + µs-scale jitter), rest 15%; latency floors max(4×value, 100µs) — the tripwire means "µs became ms".

The gate must bite (proven 2026-08-21)

scripts/db-bench.py --check <results.json> evaluates a recorded run: the real results pass 74/0; a doctored copy FAILS on exactly the doctored metrics — use a 15%-class metric (seed) halved plus a 50%-class metric (read) quartered, so both tolerance classes prove they bite. Re-run the smoke after any gate or policy change.