- docs/screenshots: nineteen pages captured with headless Chromium from a running instance; README gains a screenshot section - tests/perf: opt-in load test (NOEMA_PERF=1) seeding 60k events, 6k memories and 1.5k beliefs across two minds, timing sixteen hot reads against a 400 ms ceiling and rejecting sequential-scan plans - migration 0016: indexes the test found missing (memories by mind and recency, beliefs by mind and confidence, events by mind and category, belief versions by mind and time) - docs/performance.md records the numbers and how to rerun
2.2 KiB
Performance pass
tests/perf is an opt-in load test. It seeds a fresh schema with a realistic
volume for one mind plus a second "noise" mind (so scoping matters), times
the hot read paths in steady state, and rejects any plan that sequentially
scans the three large tables.
NOEMA_PERF=1 NOEMA_TEST_DATABASE_URL=postgres://... go test -count=1 -v -run TestHotPaths ./tests/perf/
Seed: 60 000 events, 6 000 memories, 1 500 beliefs with evidence. Ceiling per read: 400 ms (a regression guard, not a target).
Results (PostgreSQL 16 in Docker, laptop, 2026-09-14)
| Read | Before 0016 | After 0016 |
|---|---|---|
| events recent 50 | 3.2 ms | 1.2 ms |
| events by type | 1.8 ms | 1.1 ms |
| events after seq (runtime cursor) | 2.2 ms | 1.2 ms |
| events summary search | 3.3 ms | 1.4 ms |
| events stats (group by category) | 13.8 ms | 15.8 ms |
| memory list 50 | 4.1 ms (seq scan) | 0.5 ms |
| memory text search | 1.7 ms | 0.5 ms |
| memory fused retrieve | 31 ms | 27 ms |
| beliefs list | 3.6 ms (seq scan) | 3.6 ms |
| beliefs search / contested / uncertain / stats / recent changes | ≤ 3 ms | ≤ 3 ms |
| snapshot capture (all state tables) | 288 ms | 296 ms |
Migration 0016_hot_path_indexes adds the indexes the planner needed for the
two list orders that had fallen back to sequential scans, plus
(mind_id, category, seq) on events for replay's stimulus scan and
(mind_id, created_at) on belief versions for the recent-changes view.
What was deliberately left alone
- Fused retrieval (~27 ms) is the most expensive per-cycle read; it runs a ranked full-text query capped at 50 candidates plus optional vector search. It is well inside the cycle budget; tune only if cycle latency becomes visible.
- Snapshot capture (~300 ms) serialises every state table; it is an operator action, not a cycle path.
- Event stats (~16 ms) is a group-by over all of a mind's events, used on the overview page. A materialised counter would be premature at this volume.
Rerun before touching queries
The plan checks fail loudly when a future query drifts off its index, which is the cheapest way to catch a regression that would otherwise only show up with months of history.