Modeling Realistic Load Patterns with k6 Scenarios and Thresholds
Learn how to combine k6 scenario executors with global thresholds to simulate warm‑up, spike, and steady‑state traffic while enforcing latency and error‑rate SLAs in a single test script.
08 Nov 2025, 16:51 UTC

The problem: one‑size‑fits‑all load tests miss real traffic spikes
Most teams start with a single constant‑VU run and call it a day. That approach tells you how the service behaves under a steady stream, but it hides the latency spikes that appear when a marketing campaign drives a sudden burst of requests. If your SLA says "95th‑percentile latency < 500 ms and error rate < 1%" you need a test that exercises both the calm and the chaotic periods in the same run.
Why k6 scenarios solve this
k6’s scenarios block lets you declare multiple execution modes—constant VUs, ramping VUs, and arrival‑rate—each with its own duration and target. Because every scenario shares the same global thresholds, a single script can verify that the SLA holds during warm‑up, spike, and steady‑state traffic without stitching together separate test files.
Defining scenarios in a script
Each scenario entry contains:
- executor – the execution model (e.g.,
constant-vus,ramping-vus,constant-arrival-rate). - vus or rate – the target concurrency or request rate.
- duration – how long the phase runs.
- startTime (optional) – to sequence phases; if omitted they run in parallel.
Thresholds are declared once at the top level and apply to the aggregated metrics of all scenarios.
Worked example: warm‑up → spike → steady
The script below hits /api/orders and enforces the two SLA thresholds. Save it as test.js.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
warmup: {
executor: 'constant-vus',
vus: 10,
duration: '5m',
},
spike: {
executor: 'ramping-vus',
startVUs: 10,
stages: [
{ duration: '1m', target: 100 },
{ duration: '1m', target: 100 },
],
startTime: '5m',
},
steady: {
executor: 'constant-vus',
vus: 50,
duration: '10m',
startTime: '7m',
},
},
thresholds: {
'http_req_duration{type:api}': ['p(95)<500'],
'http_req_failed{type:api}': ['rate<0.01'],
},
};
export default function () {
const res = http.get('https://api.example.com/api/orders', {
tags: { type: 'api' },
});
check(res, { 'status 200': (r) => r.status === 200 });
sleep(1);
}
Key points:
- The
tagsobject on the request lets thresholds filter only the API calls. startTimesequences the phases so they run back‑to‑back rather than concurrently.- Threshold expressions use the
p(95)percentile function and a failure‑rate check.
Running the test locally
- Install k6 (macOS example):
Requires admin rights for the Homebrew prefix.brew install k6 - Execute the script:
Run from the directory containingk6 run test.jstest.js. No elevated privileges needed. - Observe the end‑of‑run summary. Look for the
thresholdssection – each line showsPASSorFAILfor the latency and error‑rate checks. - (Optional) Export raw metrics for deeper analysis:
Then inspectk6 run --out json=result.json test.jsresult.jsonfor the exact 95th‑percentile values.
Risk: On a laptop the CPU may saturate before reaching 100 VUs, causing the ramping stage to plateau early. For high‑scale validation use k6 Cloud or a distributed runner and ensure the test network mirrors production (same DNS, TLS termination, etc.).
Trade‑offs and limitations
- Script complexity – Multiple scenarios increase lines of code and the mental model of phase ordering.
- Cross‑scenario interference – Because all scenarios share the same VU pool (unless you use
execto separate), a long‑running request from the warm‑up phase can bleed into the spike metrics. Mitigate by keeping request logic idempotent and short, or run each scenario in an isolated test run. - Version dependence – The scenario executor syntax stabilized in k6 v0.45. Older releases may lack
ramping-vusor produce different summary fields.
Actionable next step
Start with a single baseline scenario (e.g., constant 20 VUs for 5 minutes) and the two thresholds. Verify the run passes. Then add a spike scenario, watch the threshold output, and finally layer in a steady‑state phase. Each incremental addition lets you confirm that the SLA holds under progressively more realistic traffic without overwhelming the test script.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.