Performance Testing (k6)¶
Test environment¶
k6 tests are never run against production. Both in local
verification and in CI (the k6-smoke / k6-load-stress jobs), they
run against a server started by the backend's own
tests/performance/server/k6TestServer.ts — the real createApp()
combined with the same in-memory fake repositories (see the "Test
database approach" section of Testing — the same
in-memory pattern).
What this measures: the real performance of the routing + middleware (helmet/cors/rate-limit) + JWT auth + validation + business logic layer. What it does NOT measure: MongoDB I/O latency. The numbers are NOT production-equivalent — they only give real, useful information about the application code's own layer. See the Performance Report for the detailed, actually-executed results.
"Never run against production" rule¶
- k6 scripts target
http://127.0.0.1:4001(the in-memory test server) by default (BASE_URLenv var). BASE_URLis never set to a real/staging/production address in the CI pipeline.- The test user and every dictionary record used are prefixed with
K6_— never mixed with real data, and automatically cleaned up when the process exits since everything is in-memory.
Smoke vs. Load/Stress¶
| Level | When it runs | VUs/Duration | Scripts |
|---|---|---|---|
| Smoke | Every push/PR (k6-smoke job) |
1 VU, short | auth-smoke.js, dictionary-read.js, dictionary-crud.js, personnel-read.js |
| Load/Stress | Manual only (workflow_dispatch, run_k6_load_test: true) |
10-100 VUs, 30-60s | dictionary-read.js, auth-smoke.js |
Smoke tests give a fast "broken or not" signal on every commit; load/stress tests take longer and are noisier (they deliberately saturate the rate limiter), so they don't run on every push.
VUs/Duration logic¶
Scripts can be parameterized via __ENV.VUS / __ENV.DURATION
(defaults are defined in the script, usually 1 VU / 6-10s for smoke).
The load test configuration that was actually run: staged 10 → 25 → 50
VUs, 60s total (see Performance Report, section 3).
Result metrics¶
k6's standard output includes:
http_reqs— total request count and req/shttp_req_duration—avg/med/p90/p95/maxlatencyhttp_req_failed— k6's built-in "failed" rate (counts 4xx/5xx — NOTE: for this project 429 [rate-limit] also counts here, and that is NOT a crash, see the honest commentary in section 3 of the Performance Report)checks— the script's OWN expected-status assertions (usually treats 200 OR 429 as "expected")
Raw JSON results are written to backend/tests/performance/results/
and are not committed to Git (see .gitignore) — only curated
markdown summaries like this page are committed.