---
name: "k6_load_testing"
description: "Write, run, and interpret k6 load tests: scenario design (smoke, load, spike, soak, stress), SLO thresholds that gate CI, reading k6's metrics output, and wiring runs into CI pipelines. Triggers when someone needs to load test an HTTP API or website, asks how many users a system can handle, or wants performance gates in CI."
---

# k6 Load Testing

## Purpose

Turn "is it fast enough under load?" into a runnable k6 script with SLO thresholds, then read the results and know what to fix. Covers HTTP APIs and web services; enough for first-pass browser/perf hybrid awareness but protocol load is the core.

## Workflow

1. **Clarify the target and the question.** What endpoint(s), what environment (never production: staging or a dedicated perf environment), and which question: does it meet SLOs (load test), where does it break (stress/breakpoint), does it survive a surge (spike), does it degrade over time (soak)?
2. **Start with a smoke test.** One script, 1-3 VUs, ~30s. Proves the script, the target, and the wiring work before spending real load. If the smoke test fails, fix the script or the environment, not the thresholds.
3. **Build the real scenario.** Pick from `references/scenarios.md`: ramp (capacity baseline), spike (surge), soak (long-run degradation), stress/breakpoint (find the ceiling). Use multiple named scenarios in one script when a flow needs separate load shapes (see `references/scenarios.md` for executor choice).
4. **Encode SLOs as thresholds.** Latency p95/p99 and error rate first; add p99 only where tail latency matters. Prefer tagged, per-scenario thresholds over one global threshold when different endpoints have different SLOs (see `references/thresholds.md`). k6 exits non-zero on breach, which is what gates CI.
5. **Run, then read like this.** Focus on `http_req_duration` p(95)/p(99), `http_req_failed` rate, `http_reqs` throughput, and the checks summary. See `references/reading-results.md` for the decision tree (is it the app, the DB, or the network?).
6. **Wire into CI** with `references/ci.md`: smoke on every PR, heavier scenarios nightly or pre-release, results exported as JSON artifacts.

## Output Contract

A finished job hands back:
- The k6 script(s) used, with the load profile (executor, VUs, durations) stated in plain language.
- The thresholds enforced and why those numbers.
- The verdict: pass/fail, and the key numbers (p95, p99, error rate, throughput).
- If it failed: what the metrics point at (latency vs errors vs throughput drop) and the most likely bottleneck class per `references/reading-results.md`.

## Worked starter example

A smoke script that actually runs. Uses Grafana's free public test API (the same `quickpizza.grafana.com` endpoint their docs use for examples) so anyone can execute it without a target of their own:

```js
import http from 'k6/http';
import { check, sleep } from 'k6';

const BASE_URL = __ENV.BASE_URL || 'https://quickpizza.grafana.com';

export const options = {
  vus: 2,
  duration: '30s',
  thresholds: {
    http_req_failed: ['rate<0.01'],
    http_req_duration: ['p(95)<2000'],
  },
};

export default function () {
  const res = http.post(`${BASE_URL}/api/pizza`, JSON.stringify({
    maxCaloriesPerSlice: 500,
    mustBeVegetarian: false,
    excludedIngredients: ['pepperoni'],
    excludedTools: ['knife'],
    maxNumberOfToppings: 6,
    minNumberOfToppings: 2,
  }), { headers: { 'Content-Type': 'application/json' } });
  check(res, {
    'status is 200': (r) => r.status === 200,
  });
  sleep(1);
}
```

Run it: `k6 run smoke.js`, or against your own target: `k6 run -e BASE_URL=https://staging.example.com smoke.js`. Thresholds are relaxed here because a shared public API is slower and noisier; your own staging environment gets tighter numbers from your SLOs.

## Operating Rules

1. **Never load test production.** Staging or a dedicated perf environment. If the user only has production, say so and run a 1-VU smoke at most; flag the risk plainly.
2. **Every factual claim in the skill output must be verified.** Never invent latency numbers, capacity figures, or benchmark comparisons. If a number is illustrative, label it as an example threshold, not a measured result.
3. **Checks are not thresholds.** A `check` validates response content and only logs failures; it does not fail the run. A `threshold` enforces an SLO and fails the run when breached. Use checks for correctness, thresholds for SLOs.
4. **Don't average latency.** Report p95 (and p99 for critical paths). Averages hide tail problems; this is the single most common misread of k6 output.
5. **Warm up before judging.** Cold starts skew early results. Give the system a ramp period before the steady-state window you measure against SLOs.
6. **Keep CI thresholds slightly looser than staging reality.** A flaky-noisy gate gets disabled; a slightly relaxed gate that runs on every PR beats a strict one nobody runs.
7. **One script per concern.** Don't cram smoke, soak, and spike into one file. Small composable scripts in a directory, run individually.
8. **Style gate:** no em dashes anywhere in generated text; no filler words (no "delve", "moreover", no rhetorical-question setups). State facts plainly.
