skip to content

Applied Rate Ceiling

A throttle lays a requests-per-second ceiling over a run, but it only caps what the profile already produces, disables every pause, and the docs would rather you declared an open rate.

on this pageshow

explore

questions

5

In a Gatling simulation, why can a throttle set to 200 requests per second finish the run below 200, and what does enabling that throttle do to the scenario's pauses?

level: middleimportance: must knowfreq 50%

answer

  1. A valve, never a pump
  2. Caps what the profile already offers
  3. Switching it on silences policy pauses
  4. Raise load in the injection profile

basics

~20 s

A throttle only caps, never raises. Gatling cannot exceed what the injection profile and scenario already offer once pauses are off, so an under-supplied run stays below the ceiling. Enabling one also forces the covered populations' pause policy off.

solid answer

~50 s

`throttle(...)` is a ceiling, not a target. Gatling holds back requests that would exceed the current limit, but it has nothing to accelerate: if the injection profile and the scenario together offer only 120 requests per second, a `reachRps(200)` ceiling is never touched and the run finishes at about 120. To ask for more traffic you change the injection profile, not the throttle. The one throughput-raising thing the throttle does is a side effect — building a throttled population forces its pause policy to disabled, logging *Throttle is enabled, disabling pauses*, so every `pause(...)` that takes that policy stops waiting. Two waiting constructs survive it: `pace(...)`, and any `pause(...)` given an explicit pause type, because the per-step force wins. That override beats a pause policy declared on the population, and it is scoped: on `setUp` it silences pauses everywhere, on one population only there.

code

java · 8 lines
java
ScenarioBuilder browse = scenario("browse")
  .exec(http("list").get("/products"))
  .pause(Duration.ofSeconds(5))
  .exec(http("detail").get("/products/1"));

setUp(browse.injectOpen(constantUsersPerSec(20).during(Duration.ofMinutes(10))))
  .protocols(httpProtocol)
  .throttle(reachRps(200).in(Duration.ofSeconds(30)), holdFor(Duration.ofMinutes(9)));

go deeper

for a junior

Be ready to say that a throttle caps traffic and cannot create it, and that load is declared in the injection profile instead.

for a middle

Be ready to explain that a throttled population has its pause policy forced to disabled, and that the override beats whatever pause policy the population declared — while a pause given an explicit pause type still waits, because that per-step force is resolved last.

for a senior

Be ready to diagnose a throttled run that lands under its ceiling by checking offered traffic, protocol coverage, the ramp and the ramp's share of the run.

for a principal

Be ready to rule on whether a suite may use a throttle at all, given that turning one on quietly deletes the think time every result was previously measured with, except where a pause carries its own pause type.

The single most common mistake with Gatling's `throttle(...)` is reading it as a load setting. It is not one. A throttle is a **valve, not a pump**: it can close down on traffic the simulation is already producing, and it has no way whatever of producing more. ## Why a 200 rps ceiling can finish at 120 Gatling's throughput comes from two places, and neither of them is the throttle: 1. **The injection profile** decides how many virtual users arrive, and when. 2. **The scenario** decides how many requests each of those users issues before it finishes. Multiply those together and you get the traffic the simulation offers. The throttler sits downstream of that: each second it computes the current ceiling from the throttle profile, lets that many requests through, and defers the rest. If the offered traffic is 120 requests per second and the ceiling is 200, the ceiling is simply never reached, and the report shows about 120. Gatling's own documentation states the limit plainly — a throttle *"can't generate a throughput that's higher than the one normally generated by your simulation once pauses are disabled"*. The fix is never to raise the throttle. It is to change the injection profile — more users per second, or a longer or steeper ramp — or to change the scenario so each user does more work. ## The one thing a throttle does raise There is exactly one way a throttle increases throughput, and it is a side effect rather than a feature: **switching a throttle on disables pauses**. When Gatling builds a population it resolves the pause policy, and if that population is throttled it forces the policy to disabled and logs *"Throttle is enabled, disabling pauses"*. Every `pause(...)` that takes that policy — which means every pause declared without an explicit pause type — then becomes a no-op, and the virtual user goes straight to its next action. Three details matter about that: * **It overrides what you declared.** A population that asks for `exponentialPauses()` still gets no pauses from that policy if it is throttled. The throttle's decision wins over the declared pause policy. * **The scope follows where the throttle was attached.** A throttle on `setUp` disables pauses in every population of the run. A `.throttle(...)` on a single injected population disables pauses only in that population; the others keep waiting normally. * **It is the pause policy, not every waiting construct.** The policy layer the throttle overrides is the one that `disablePauses`, `constantPauses` and their siblings set, and two constructs are not built from it. A `pace(...)` step is a different action driven by its own counter, so it keeps regulating its own loop. And any `pause(...)` overload given an explicit pause type — `pause(Duration.ofSeconds(5), constantPauses)` in Java or Kotlin, `pause(5.seconds, constantPauses)` in Scala — still waits, because Gatling builds each pause from `force.getOrElse(ctx.pauseType)` and the per-step force beats the throttle-resolved disabled policy. Gatling's own documentation states the unqualified version — a throttle *"disables all the pauses"* — so that exception is visible only in the source. ## Reading the report Two runs of the same simulation, one throttled and one not, are therefore **not** comparable in the way people expect. The throttled run has no think time left in it beyond whatever its pauses forced for themselves, so its virtual users cycle faster, hold fewer concurrent sessions, and hit the target in a much tighter pattern. If a suite switches a throttle on to "cap" a run, it has also silently deleted every pause the scenario declared without a pause type of its own. | you want | the construct that does it | |---|---| | more traffic | change the injection profile or the scenario | | a hard ceiling on traffic | `throttle(...)` on `setUp` or a population | | no waiting between actions | the pause policy, e.g. `disablePauses` | | a ceiling that also silently removes waiting | `throttle(...)` — this is the trap | | waiting that survives a throttle | `pace(...)`, or a `pause(...)` given an explicit pause type | ## Why the documentation pushes back Gatling's own guidance is that if each virtual user performs one request, you should express the rate in the **injection profile** instead, with an open-model step such as `constantUsersPerSec`. Its warning about multi-request scenarios is about fairness: the throttler grants the second's budget to whichever request asks first, so in a scenario with several different requests the distribution between them *"is likely to be unbalanced"*. You get 200 requests per second, but you do not get to say which 200. ## What to check when the number is short When a throttled run comes in under its ceiling, work outward in this order: 1. Confirm the offered traffic. Users per second times requests per user is the real cap. 2. Check the protocol. Throttling covers HTTP requests and JMS only, so requests issued over WebSocket or SSE never consult the throttler and never count toward the ceiling either. 3. Check the ramp. `reachRps(200).in(30)` spends the first thirty seconds below 200 by design, so a short run can average well under the plateau. 4. Check the response times. A ceiling is expressed per second of wall clock; if the system under test is slow enough that the simulation cannot issue the requests, the ceiling is irrelevant.

  • The run needs to actually hit 200 requests per second. What do you change?
    The injection profile or the scenario, never the throttle. Raise the arrival rate so users per second times requests per user clears 200, or lengthen the scenario so each user issues more requests. The throttle stays as the ceiling that stops you overshooting.
  • Does the throttle's pause disabling reach a pace step too?
    No. The throttle overrides the population's pause policy, which is the layer that disablePauses and its siblings set, and a pace step is not built from that policy. It keeps regulating its own loop interval while the scenario's policy-driven pauses go silent. Pace is not the only survivor: a pause given an explicit pause type, such as pause(Duration.ofSeconds(5), constantPauses), also waits, because Gatling resolves each pause as force.getOrElse(ctx.pauseType) and the per-step force wins.
  • A population declares exponentialPauses and is then throttled. Which wins?
    The throttle. When Gatling builds a throttled population it resolves the pause type to disabled unconditionally, before the declared policy is consulted, so the exponential setting never takes effect for any pause that relies on it. A pause handed exponentialPauses directly as its own argument is unaffected: that force is resolved per step. Nothing warns you beyond an informational log line.

A throttle is the valve on a pipe, not the pump feeding it. Closing the valve reliably reduces flow; opening it wider does nothing at all unless the pump is already pushing more than the valve was letting through.

saying these in an interview costs you the question

  • Raising the throttle to try to increase offered load
  • Assuming a plain pause still waits under an enabled throttle
  • Comparing a throttled run with an unthrottled one directly
  • Thinking a scenario-level throttle silences pauses run-wide
open as a page

In a Gatling simulation, which building blocks make up a throttle profile, and what does each one do?

level: juniorimportance: should knowfreq 42%

basics

~10 s

Three chain inside throttle(...): reachRps(target).in(duration) ramps the requests-per-second ceiling to a target, jumpToRps(target) steps to it instantly, and holdFor(duration) holds the current value. Declare the chain on setUp or on one injected population.

open as a page

In a Gatling simulation running two scenarios at once, what does a single throttle declared on setUp cap, and how does that differ from calling throttle on each injected population?

level: seniorimportance: should knowfreq 33%

basics

~20 s

A throttle on setUp is one shared ceiling for the whole run: both scenarios draw on the same per-second budget, first come first served, with no guaranteed split. A throttle per injected population gives each scenario its own ceiling.

open as a page

Your Gatling run must sit at 200 requests per second across two scenarios, so how would you decide between declaring that rate with throttle and declaring it in the injection profile?

level: principalimportance: should knowfreq 30%

basics

~20 s

Decide by what the number is. A rate the test exists to prove belongs in the injection profile, which keeps think time and the request mix intact. A rate that is only a safety limit belongs in throttle.

open as a page

In a Gatling run, what happens to requests the throttle ceiling will not admit yet, and what ends a run whose throttle profile is shorter than its injection profile?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

Nothing is dropped. Excess requests are buffered in an unbounded in-memory queue inside the load generator and replayed on the next one-second tick, risking an OutOfMemoryError. The throttle profile's total duration also bounds the run, stopping it like maxDuration.

open as a page