By Performate
k6 Thresholds Examples: Alert on the Metrics That Matter
Concrete k6 threshold patterns for latency percentiles, error rates, and custom metrics—plus how exit codes turn runs into CI gates.
A load test that "looks fine" in a slide deck but never fails the build is decoration. Thresholds turn k6 metrics into release gates: violated thresholds set a non-zero exit code CI can enforce (thresholds documentation).
The art is choosing few, meaningful thresholds—segmented by route and percentile—not copying every metric k6 emits. In this guide you will learn patterns for latency percentiles, error rates, custom Trends, and tagged SLOs that match how product talks about reliability.
Why thresholds beat eyeballing summaries
Checks validate individual responses; thresholds aggregate run-level SLOs:
http_req_durationsupportsp(95),p(99),avg, andmaxselectors (HTTP metrics).- Tagged thresholds like
http_req_duration{route:checkout}isolate critical paths. http_req_failedcatches transport and HTTP failures—pair with error taxonomy when tuning.- Custom metrics (
Trend,Rate,Counter) threshold business steps—payment authorize, cache miss rate.
Without thresholds, teams debate screenshots. With them, merges fail objectively—when configured honestly.
When aggregate thresholds hide checkout pain
Global p(95)<500 passes while checkout p(99) doubles. Align gates with p95 vs p99 guidance and observability tags. When you run multi-version traffic mixes, duplicate that pattern per version tag so a fast v2 path cannot mask a regressing v1 tail.
Pair thresholds with k6 scenario types: smoke runs deserve strict error gates; stress runs may document expected SLO breaches instead of failing the build on every percentile.
Practical k6 implementation: layered thresholds
Layer global safety rails, route-specific latency, and custom business metrics.
Example script (illustrative—not a production-ready test). Fictional SLO numbers.
What this example demonstrates:
- Global error ceiling:
http_req_failedrate gate for any release. - Route-specific latency: checkout tighter than catalog browse.
- Custom Trend threshold:
payment_authorize_durationfor downstream PSP step. - Abort on critical failure:
abortOnFailon checkout error rate for fast CI feedback.
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Trend } from 'k6/metrics';
const paymentAuthorize = new Trend('payment_authorize_duration', true);
const BASE = __ENV.API_BASE || 'https://staging.example.com';
export const options = {
scenarios: {
checkout_path: {
executor: 'constant-arrival-rate',
rate: 10,
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 15,
maxVUs: 60,
tags: { suite: 'threshold-demo' },
exec: 'checkoutJourney',
},
},
thresholds: {
http_req_failed: ['rate<0.01'],
'http_req_failed{route:checkout}': [{ threshold: 'rate<0.005', abortOnFail: true, delayAbortEval: '30s' }],
'http_req_duration{route:catalog}': ['p(95)<400', 'p(99)<700'],
'http_req_duration{route:checkout}': ['p(95)<850', 'p(99)<1300'],
payment_authorize_duration: ['p(95)<2000'],
},
};
export function checkoutJourney() {
let res = http.get(`${BASE}/catalog`, { tags: { route: 'catalog' } });
check(res, { 'catalog 2xx': (r) => r.status >= 200 && r.status < 300 });
sleep(0.3);
res = http.post(`${BASE}/checkout`, JSON.stringify({ sku: 'SKU-1', qty: 1 }), {
headers: { 'Content-Type': 'application/json' },
tags: { route: 'checkout' },
});
check(res, { 'checkout 2xx': (r) => r.status >= 200 && r.status < 300 });
const start = Date.now();
res = http.post(`${BASE}/payments/authorize`, JSON.stringify({ amount: 99 }), {
headers: { 'Content-Type': 'application/json' },
tags: { route: 'payment' },
});
paymentAuthorize.add(Date.now() - start);
check(res, { 'payment 2xx': (r) => r.status >= 200 && r.status < 300 });
sleep(0.5);
}
Patterns that work
- CI smoke: strict
http_req_failed, relaxed duration on non-critical routes. - Pre-release: full percentile stack on revenue paths only.
abortOnFailsparingly: on catastrophic error rates, not on first p99 blip in short runs.- Thresholds as code in Git reviewed like application changes.
Anti-patterns to avoid
- Thresholds on every emitted metric—noise drowns signal.
- Copying prod SLO numbers into 2-minute smokes with insufficient samples.
- Ignoring
thresholdson custom metrics you never defined in script.
Pro tip (example command): fail CI explicitly on threshold breach.
k6 run checkout-thresholds.js; echo "exit code: $?"
What this command demonstrates: k6 exits non-zero on threshold failure—wire this directly into CI steps (load testing in CI/CD).
Decision framework: which thresholds per gate type
| Gate type | Recommended thresholds |
|---|---|
| PR smoke (2–3 min) | http_req_failed; one critical route p(95) |
| Nightly regression | Full route set p(95) + p(99) on checkout |
| Pre-prod capacity | Error rate + custom Trends + dropped_iterations |
| Third-party dependency | Separate tag {dependency:payments} latency/error |
| Post-deploy canary | Tight http_req_failed + tail latency on canary tag |
Gate on p95 if you need stable CI signal on short runs.
Gate on p99 if payment or safety paths tolerate rare slowness poorly.
Gate on custom Trends if business steps span multiple HTTP calls.
Observability, documentation, and next steps
- Document which thresholds map to published SLOs vs internal budgets.
- Store threshold config in version control beside scenario JSON.
- Review false positives monthly—tighten or lengthen runs, do not disable blindly.
- Correlate threshold breaches with error taxonomy buckets.
- Archive summary JSON when thresholds fail for diffable regressions.
How Performate simplifies threshold workflows
Example: checkout SLO gates without syntax hunting
- Import checkout and payment requests. Problem solved: routes exist as named operations for tagged thresholds.
- Set thresholds in the visual panel—catalog
p(95)<400, checkoutp(95)<850, global error<1%. Problem solved: PM-readable SLO lines export to valid k6 syntax. - Run 5-minute arrival-rate scenario. Problem solved: see pass/fail per threshold in integrated report, not only terminal output.
- Compare last three runs for p99 drift on checkout. Problem solved: regression debates use one dashboard.
- Enable abort-on-fail for checkout errors in UI for CI exports. Problem solved: fast pipeline feedback without custom wrapper scripts.
- Export k6 with thresholds embedded for GitOps. Problem solved: desktop tuning matches CI gates exactly.
Closing takeaway
Thresholds are contracts with your pipeline—keep them few, tagged, and tied to real SLO language. Gate errors everywhere; gate latency where the business feels pain; use custom Trends when HTTP averages lie.
Pick one critical route this week, replace a vague "should be fast" note with p(95) and p(99) thresholds, and wire k6's exit code into CI.
Ready to optimize your API performance?
Define and compare threshold outcomes faster with Performate scenario workflows.