Let us talk about a silent killer in performance testing. You write a script, configure a steady count of virtual users, and run a load test against your application. The metrics look clean. Your average response time is a snappy 150ms, and even the p95 latency is under 300ms. You declare the system ready for production. But the moment real traffic hits, your servers stall, database queries pile up, and users experience multi-second delays. What went wrong? You fell victim to coordinated omission.
Coordinated omission is a phenomenon where a load generator silently coordinates its request emission rate with the slow response times of the system under test, corrupting your metrics by omitting tail-latency spikes. Let us look at how this happens and how k6 solves it.
The Closed-Workload Trap
Most performance testers rely on a closed-workload model, which is the default in many tools. In a closed workload, the launch of a new execution iteration is strictly coupled to the completion of the prior one. If a virtual user finishes an iteration, it immediately starts a new one. If the system under test is responding quickly, iterations fly by in milliseconds. But if the system stalls, the virtual user is blocked, waiting for the server to respond.
Think about the math. If your server encounters a database lock and suddenly takes 10 seconds to respond, your virtual users will sit idle, waiting for that 10-second response before sending the next request. Because VUs slow down during backend congestion, the load generator reduces the rate of request emission. By backing off, the tool fails to measure the impact of sustained traffic during system failure. The very requests that would have triggered catastrophic tail-latency spikes are never sent, skewing your averages and percentiles.
Decoupling Traffic with Open-Loop Models
In the real world, user arrival is independent of system response times. If your website experiences a traffic spike during a flash sale, shoppers do not wait for prior requests to complete before landing on your homepage. They keep arriving. To simulate this accurately, you must use an open-workload model.
An open workload decouples iteration scheduling from application latency. Iterations are triggered at fixed, predictable intervals based on a target arrival rate. If the backend slows down, k6 does not back off. Instead, it dynamically spawns new virtual users in the background to maintain the target arrival rate. The concurrency level of the active system is governed by Little's Law:
L = λ * W
Where L is the average number of active parallel iterations, λ is the scheduled arrival rate, and W is the average latency of an iteration. If server latency increases, k6 scales up the VU pool up to your configured ceiling to maintain the target throughput. This exposes the real impact of resource exhaustion and queue build-up.
Configuring Arrival-Rate Executors in k6
To implement an open-loop model, k6 provides the constant-arrival-rate and ramping-arrival-rate executors. Here is how you configure a production scenario in your options object:
export const options = {
scenarios: {
open_loop_load: {
executor: 'constant-arrival-rate',
rate: 100, // Start exactly 100 iterations per second
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 50, // Keep 50 VUs warm on startup
maxVUs: 1000, // Allow scaling up to 1000 VUs to maintain rate during slowdowns
},
},
};
This configuration ensures that exactly 100 transactions are started every second. If the target API slows down, k6 will scale the active VU count up to the 1000 VU limit to maintain the rate. This prevents coordinated omission and gives you an accurate view of system performance under stress.
Beware the dropped iterations footgun. If your system encounters a prolonged lock, the active VUs might hit the 1000 maxVUs limit. At this point, k6 will stop initiating iterations, log a dropped_iterations metric, and reintroduce coordinated omission. Always size your maxVUs headroom to handle anticipated queue growth during system stalls.
To learn how to combine multiple workload scenarios into a single test, read our post on Enterprise k6 Scripting: Custom Telemetry, Thresholds, and SharedArray. If you want to see how to run browser-level transactions in parallel with open-loop API scenarios, check out Hybrid Load Testing: Combining k6 Protocol VUs with k6/browser and Playwright. If you are deploying this enterprise setup on Windows, see our framework guide on Architecting an Enterprise k6 Framework on Windows.