# Benchmarks Two tools measure the server's read and write paths. Use them before and after any change to a hot path, and put both numbers in the PR. - **`scripts/bench/run.ts`** measures latency (p50, p95, max) and SQL statements per request against a seeded account of about 1.26 million measurement rows, through the production build. It is a local tool, not a CI gate: latency depends on the machine. - **`tests/integration/query-budget-hot-routes.test.ts`** holds a statement budget per hot route on a small fixture. Statement counts are exact on a fixed fixture, so this one is a gate: a change that adds a query per row, or a second read of something already loaded, fails it. ## The large-account benchmark ### 1. A throwaway Postgres with pg_stat_statements Never point the seed or the benchmark at a database that holds real data. They write under a synthetic user, and the seed's `--reset` deletes that user with everything it owns. ```sh docker run -d --name hl-bench-db --shm-size=256m \ -e POSTGRES_USER=bench -e POSTGRES_PASSWORD=bench -e POSTGRES_DB=healthlog_bench \ -p 55432:5432 postgres:18.6-alpine \ postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=top ``` The image and the two settings are the ones `docker-compose.yml` runs in production. ### 2. Environment, migrations, seed ```sh export DATABASE_URL="postgresql://bench:bench@127.0.0.1:55432/healthlog_bench?schema=public" export ENCRYPTION_KEYS='{"v1":"<64 hex>"}' ENCRYPTION_ACTIVE_KEY_ID=v1 export API_TOKEN_HMAC_KEY="<64 hex>" pnpm db:migrate:deploy pnpm dlx tsx scripts/bench/seed-large-account.ts --reset --anchor 2026-10-22 ``` Any throwaway key values work, as long as the seed, the benchmark and the server see the same ones. The seed writes ten years for one user: Apple Health dense rows (heart rate about every ten minutes, HRV, SpO2, respiratory rate, gait, audio), Withings blood pressure, pulse and scale, a weekly manual weight, three years of Oura resting heart rate and RMSSD, 90 days of Nightscout CGM, `stats:` and per-sample cumulative rows, nightly sleep stages, about 3,000 workouts, three years of mood and of three medications with their intake events. It then folds the rollup tier the way the boot backfill does and runs `ANALYZE`. It takes about 80 seconds on the Mac mini. The seed is deterministic for a given `--anchor`: values and timestamps are functions of the row index and that day. Without `--anchor` it anchors on today, which is what you want when the benchmark should see recent data. ### 3. A production build ```sh pnpm build ``` ### 4. Run ```sh pnpm dlx tsx scripts/bench/run.ts --out scripts/bench/results/