Files
noema/docs/performance.md
Andy Dixon d9ae91e406 Close the plan: screenshots and a measured performance pass
- 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
2026-07-06 23:18:31 +01:00

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.