A load test can report attractive latency while users still see multi-second stalls because the test is measuring the wrong system boundary under the wrong arrival model. In a closed-loop test, each client waits for its previous request to finish before sending the next one, so a slow server automatically reduces new arrivals. That self-throttling hides queue growth and undercounts the waiting users actually experience. If you need to answer a capacity question, prefer an open arrival model and report both offered load and end-to-end waiting.
What is going wrong in the test?
This is the classic coordinated omission problem. In k6’s closed model, “VU iterations start only when the last iteration finishes,” so response time directly changes how much new work the test generates. The k6 docs explicitly warn that slower responses cause “a lower arrival rate of new iterations” and that this is “known as coordinated omission” in some testing literature, because the test waits right when a real system would be accumulating demand in a queue (k6 open and closed models).
That is why dashboards can look calm during a stall. Your test clients are blocked, so they stop offering load. The server may still be causing painful user-visible waits, but the measurement stream becomes sparse exactly during the failure.
Which workload model answers the real capacity question?
Use a closed model when you are intentionally modeling a fixed population cycling through think time: for example, “what happens when 200 workers each do the next job only after the last one completes?” That is a valid question.
Use an open model when you need to know whether the system can sustain an arrival rate such as 500 checkouts per second. k6’s arrival-rate executors are open-model: iterations “start independently of system response,” and the constant-arrival-rate executor is recommended “when you want iterations to remain constant, independent of the performance of the system under test” for a more accurate RPS view (k6 constant-arrival-rate).
A useful rule: if production users keep arriving regardless of whether earlier users are stuck, your test should do the same.
What should you measure instead of just request latency?
Do not stop at server response time percentiles from completed requests. Record:
- offered load: how many requests per second you attempted to start
- achieved throughput: how many completed
- concurrency in flight
- rejected or timed-out work
- end-to-end waiting from scheduled start time to completion
Worked example: suppose you target 100 arrivals per second and the service freezes for 10 seconds. A closed test with 100 looping clients may send almost nothing new during the freeze, then report only a small set of slow completions. An open test still tries to start 1,000 arrivals during that window; if capacity is gone, you will see backlog, timeouts, or rejections. That is much closer to the user story.
If you post-process latency distributions, use tooling that can correct for coordinated omission. The HdrHistogram project documents “record value with correction for coordinated omission,” and its Python port exposes record_corrected_value(latency, 10) for an expected interval example (HdrHistogram_py, HdrHistogram). That correction is helpful, but it is still better to design the workload model correctly up front.
Does client-side waiting belong in the reported latency?
Yes, if the question is what a user or upstream caller experiences. No, if the question is narrowly “how long did the handler spend after it accepted the request?” Keep both if you can, but label them clearly. Mixing queue wait, retry delay, and service time into one unlabeled latency number creates confusion.
When is a closed model still appropriate?
If your real system has a bounded worker pool or a human-driven loop where each actor waits before starting the next task, a closed model is appropriate. Just do not use it to claim the platform can sustain an external arrival rate it never actually faced.
Decision checklist?
If arrivals in production are independent, use an open model. If users wait in queues, include that waiting in at least one headline SLO view. Always chart offered load beside achieved throughput. Treat dropped, timed-out, and rejected work as first-class outcomes, not footnotes.
Next step: rerun one scenario as constant-arrival-rate, add scheduled-start-to-finish latency, and compare the tail during induced stalls.
Reviewed: 2026-09-05.
Sources are linked throughout this guide. Product capabilities can change; consult the linked documentation for your deployment.
Read our editorial approach ↗