DEV Community

Devanshu Biswas
Devanshu Biswas

Posted on

k6 Load Testing: Turn "Is It Fast Enough?" Into a Pass/Fail SLO Gate That Fails Your Build

Your pipeline can prove the code is correct (unit tests) and deployable (a green rollout) and still tell you nothing about how it behaves under load. "Is the p95 latency acceptable when 50 users hit it at once? Does the error rate stay near zero?" — before a load test, those get answered by eyeballing a dashboard after the fact. Grafana k6 turns them into the same hard PASS/FAIL a unit test gives you.

A load test is two things working together: a load profile and a set of pass/fail gates.

The profile: a staged VU ramp

The profile lives in options.stages — a list of { duration, target } steps that ramp the number of active virtual users (VUs) over time. A VU is an independent worker that loops the scenario as fast as it can minus think-time, so the VU count controls the offered load.

export const options = {
  stages: [
    { duration: '30s', target: 20 },  // ramp-up   0 → 20 VUs (warm the JIT / pool)
    { duration: '1m',  target: 20 },  // steady    hold 20 (SLO measured here)
    { duration: '30s', target: 50 },  // ramp-up  20 → 50 VUs
    { duration: '1m',  target: 50 },  // steady    hold 50
    { duration: '30s', target: 0  },  // ramp-down 50 →  0 (drain cleanly)
  ],
  // thresholds: { ... }
};
Enter fullscreen mode Exit fullscreen mode

The shape is the intent. This one asks "does it hold at expected traffic?" — a load test. Raise the targets until it breaks and it's a stress test; hold a moderate target for hours and it's a soak test to find leaks. Same script, different stages.

The gates: thresholds are what make it a build tool

Thresholds are boolean expressions over a metric, evaluated once at the end of the run. If any is false, k6 run exits non-zero — which is exactly what makes a CI job go red on a regression.

thresholds: {
  http_req_duration: [
    { threshold: 'p(95)<500' },   // 95% of requests under 500ms
    { threshold: 'p(99)<800' },   // 99% under 800ms
  ],
  http_req_failed:       ['rate<0.01'],   // <1% fail — the availability SLO
  order_create_duration: ['p(95)<400'],   // custom Trend — the write path
  order_flow_success:    ['rate>0.99'],   // custom Rate  — whole journeys
  checks:                ['rate>0.99'],   // 99%+ of check()s pass
},
Enter fullscreen mode Exit fullscreen mode

Note the percentiles: the average hides the tail. A run can average 120ms while 1 in 20 users waits 2 seconds. SLOs are written on p95/p99, so the gates are too. And custom metrics let a gate speak about the thing you actually promise — order_create_duration (a Trend) isolates the expensive POST so a GET-heavy mix can't mask a slow create; order_flow_success (a Rate) gates the fraction of complete create→get→list journeys, a business SLO, not just an HTTP one.

A realistic scenario, then gate CI on it

Each VU loops the journey a real client runs — POST /api/ordersGET /api/orders/{id}GET /api/orders — with check()s asserting status and body shape. Hammering one endpoint would miss how the read paths behave under write pressure.

The whole thing earns its keep in CI:

load-test:
  name: k6 load test (SLO gates)
  needs: build-test            # only stress a GREEN reactor
  steps:
    - uses: grafana/setup-k6-action@v1
    - run: k6 run k6/smoke.js           # cheap 1-VU go/no-go first
    - run: k6 run k6/order-load.js      # breached threshold → exit≠0 → job red
    - if: always()                      # keep the summary even on failure
      uses: actions/upload-artifact@v4
      with: { name: k6-summary, path: k6/summary.json }
Enter fullscreen mode Exit fullscreen mode

The script is code, reviewed in the same PR as the app, and runs identically on a laptop and a runner. p(95)<500 stops being a note in a wiki and becomes an expression k6 evaluates — and if it's false, the regression is caught before it ships, not by a 3am page.

Press "run k6," watch the VU ramp drive the API, then flip to a regressed build and watch p95 blow past the gate: https://dev48v.infy.uk/orderhub/day47-k6-load-test.html

Top comments (0)