Choosing Gatling’s Open vs Closed Workload Models for Realistic Load Tests
Learn when to use Gatling’s open injection (constant arrival rate) versus closed injection (fixed concurrency), see a Scala DSL example, and understand the limits and pitfalls of each model.
31 Mar 2026, 04:13 UTC

Use an open workload model when you want to control the request arrival rate (users per second) and a closed model when you need to limit the number of concurrent virtual users.
How the two models work
In Gatling’s Scala DSL the injection profile determines how virtual users are generated over time.
- Open model – arrival rate is fixed regardless of how fast the system responds. If the server slows down, the injector keeps sending new requests at the configured rate, which can cause queueing on the server side.
- Closed model – a fixed number of virtual users stay active. Each user waits for the previous request to finish before starting the next one, so the measured throughput drops when latency rises.
Worked configuration
The following snippet shows a mixed scenario: an open ramp‑up followed by a closed steady‑state phase. Adjust the placeholders to match your test.
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._
class MixedWorkloadSimulation extends Simulation {
val httpProtocol = http
.baseUrl("http://example.com") // placeholder: replace with your target
.acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
val scn = scenario("Mixed workload")
.exec(http("request_1").get("/")) // placeholder endpoint
setUp(
scn.inject(
// open phase: ramp arrival rate from 0 to 50 req/s over 2 minutes
rampUsersPerSec(0) to 50 during (2.minutes),
// closed phase: keep 30 virtual users active for 5 minutes
constantConcurrentUsers(30) during (5.minutes)
)
.protocols(httpProtocol)
)
}
During the open phase Gatling will try to launch enough new users to maintain the requested arrivals‑per‑second, even if each request takes longer than expected. In the closed phase the number of active users stays at 30; if the server latency doubles, the observed request rate will roughly halve.
Limits and common mistakes
- Misinterpreting closed‑model results – reporting “the system handles X req/s” from a closed test is misleading because the injector self‑throttles; a degrading system shows stable concurrency rather than a growing queue.
- Injector saturation – high open‑model arrival rates can exhaust the load generator’s CPU, file descriptors, or network sockets. Monitor injector‑side metrics (e.g., JVM GC, active threads) or distribute the test across multiple injectors.
- API changes across versions – method names such as
rampUsersPerSecorconstantConcurrentUsersmay differ between Gatling 3.x releases. Always consult the documentation matching your build’s version before copying the DSL. - Throughput assertions behave differently –
global().requestsPerSec()under an open model falling indicates back‑pressure or client saturation; under a closed model it directly mirrors latency changes.
Practical verification
- Run a tiny scenario twice: once with
constantUsersPerSec(10).during(30)(open) and once withconstantConcurrentUsers(10).during(30)(closed). - Add an artificial server delay (e.g.,
Thread.sleep(200)in a mock endpoint) and observe the reported requests‑per‑second in the Gatling report. - In the open run the request rate should stay near 10 req/s despite the added latency; in the closed run the rate should drop roughly proportionally to the added delay.
- Check the “active users over time” chart in the HTML report to confirm the injector followed the intended shape.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.