# Load test suite (k6) This directory contains the production-rehearsal k6 load tests for the boilerplate. They are the load-tier companion to the unit, integration, and verifier layers — useful before a release cut, after a perf regression suspicion, or when sizing a new staging tier. The suite is **opt-in**. CI does not run it by default because it requires a deployed target environment, a tenant API key, and a k6 binary on the runner. `scripts/run-load-tests.mjs` is the safe wrapper: it SKIPs cleanly when either prerequisite is missing. ## Install k6 k6 is a single Go binary — install it from the upstream packages: - macOS: `brew install k6` - Linux: see - Container: `grafana/k6:latest` (mount this repo and run inside) Verify the install with `k6 version`. The runner script checks the same. ## Scripts | Script | Profile | Thresholds | | --- | --- | --- | | `login.js` | 50 VUs, 30s ramp + 30s hold against `POST /v1/auth/login` | `http_req_failed: rate<0.05`, `http_req_duration: p(99)<800ms`, `http_req_duration{name:login}: p(95)<400ms` | | `api-read.js` | 100 VUs, 60s constant against `GET /v1/entities//rows` | `http_req_duration: p(99)<300ms`, `http_reqs: rate>200/s` | | `api-mutating.js` | 30 VUs, 30s constant against `POST /v1/entities//rows` | `http_req_duration: p(99)<600ms` | | `scenarios.js` | Composite — runs all three back-to-back | Per-suite thresholds tagged by `name:` | Each per-script file is also runnable standalone — useful when isolating a regression to one path. ## Threshold rationale - `http_req_failed: rate<0.05` on login allows for 401s from the synthetic account while still catching server-side 5xx storms. - `http_req_duration: p(99)<800ms` on login covers password hashing + session creation under realistic contention. Tighten as the auth path matures. - `http_req_duration{name:login}: p(95)<400ms` keeps the typical login fast; it is the user-facing budget, not the worst-case. - `http_req_duration: p(99)<300ms` on the read path matches the warm-cache-hit budget for the hottest API surface. - `http_reqs: rate>200/s` is a throughput floor — percentile checks alone miss regressions that quietly halve RPS under contention. - `http_req_duration: p(99)<600ms` on the mutating path covers transactional write + audit + queue enqueue. If a threshold becomes too tight for a particular environment (e.g. a small staging tier), override at invocation time with `k6 run --threshold` rather than editing the script — keep the production budget in source. ## Running locally ```sh # Single script K6_TARGET_URL=http://localhost:3000 \ LOAD_TENANT_API_KEY=tnk_load_test_key \ k6 run tests/load/login.js # Full suite via the safe runner (SKIPs if k6 is missing or target down) K6_TARGET_URL=http://localhost:3000 \ LOAD_TENANT_API_KEY=tnk_load_test_key \ pnpm run verify:load ``` Required env vars: - `K6_TARGET_URL` — base URL of the target environment (default `http://localhost:3000`). - `LOAD_TENANT_API_KEY` — tenant-scoped API key for the test tenant. The read/mutating scripts default to a placeholder that will return 401 — fine for SKIP-rehearsal but not for a real load run. Optional env vars: - `LOAD_LOGIN_EMAIL` / `LOAD_LOGIN_PASSWORD` — synthetic login credentials. - `LOAD_ENTITY_SLUG` — entity slug to target on the read/mutating paths. ## Stress vs. smoke To scale a smoke run down, pass `--vus 5 --duration 10s` to `k6 run`. To scale a stress run up, increase the per-script VUs via the k6 CLI rather than editing the file. The committed VU counts are the production-rehearsal contract. ## Safety notes - Never point a load test at production without an explicit human approval. - Mutating runs append synthetic rows tagged with `load-test-row-*` so they can be cleaned up by a downstream verifier. Pick a throwaway tenant. - Load tests are part of the production-readiness ladder, not part of CI Auto-Mode. Keep `verify:load` out of the default lane until a target environment is wired up.