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?
answer
- Two places to attach one ceiling
- setUp shares, population splits
- Both declared means both must admit
- Shortest profile ends the whole run
basics
~20 sA 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.
solid answer
~40 s`throttle(...)` on `setUp` caps the run as a whole. With two scenarios under a 200 requests-per-second ceiling, both populations compete for the same 200 permits each second and the throttler grants them in arrival order, so the split between the scenarios is emergent — which is exactly why Gatling warns that distribution across multiple requests is likely to be unbalanced. Calling `.throttle(...)` on each injected population instead gives each scenario a private ceiling, keyed internally by scenario name, so 120 and 80 still total 200 but the mix is now guaranteed. Declare both and a request needs room in both budgets. Two asymmetries bite: pause disabling is scoped to where the throttle was attached, but the run's duration limit is not — the shortest throttle profile anywhere in the run ends the whole run.
code
java · 6 linessetUp(
browse.injectOpen(constantUsersPerSec(120).during(Duration.ofMinutes(10)))
.throttle(reachRps(120).in(Duration.ofSeconds(30)), holdFor(Duration.ofMinutes(9))),
checkout.injectOpen(constantUsersPerSec(80).during(Duration.ofMinutes(10)))
.throttle(reachRps(80).in(Duration.ofSeconds(30)), holdFor(Duration.ofMinutes(9)))
).protocols(httpProtocol);go deeper
Be ready to say a throttle goes either on setUp for the whole run or on a single injected population, and that the two have different reach.
Be ready to explain that a setUp ceiling is one shared per-second budget granted in arrival order, while per-population throttles are keyed by scenario name.
Be ready to pick the per-population form when the mix between scenarios has to be a guarantee, and to explain why the global form cannot promise one.
Be ready to own the cross-cutting rule: matching throttle profile lengths across populations, because the shortest one silently becomes the whole run's clock.
Gatling lets you attach a throttle in two places, and with two scenarios in one run the difference between them is the difference between a shared budget and two private ones. ## One ceiling on setUp: a shared budget ```java setUp( browse.injectOpen(constantUsersPerSec(120).during(Duration.ofMinutes(10))), checkout.injectOpen(constantUsersPerSec(80).during(Duration.ofMinutes(10))) ).protocols(httpProtocol) .throttle(reachRps(200).in(Duration.ofSeconds(30)), holdFor(Duration.ofMinutes(9))); ``` This is **one** ceiling of 200 requests per second for the whole run. Both populations draw from the same second's permits, and the throttler grants them to whichever request arrives first. There is no weighting, no reservation and no fairness rule. If `browse` happens to be the busier scenario it will take most of the 200, and `checkout` gets whatever is left over. That is not an accident of the implementation; it is the documented caveat. Gatling warns that throttling *"should only be used with single request scenarios"* because otherwise *"distribution between multiple requests is likely to be unbalanced"*. A global throttle buys you a total, and nothing more. ## A ceiling per population: private budgets ```java setUp( browse.injectOpen(constantUsersPerSec(120).during(Duration.ofMinutes(10))) .throttle(reachRps(120).in(Duration.ofSeconds(30)), holdFor(Duration.ofMinutes(9))), checkout.injectOpen(constantUsersPerSec(80).during(Duration.ofMinutes(10))) .throttle(reachRps(80).in(Duration.ofSeconds(30)), holdFor(Duration.ofMinutes(9))) ).protocols(httpProtocol); ``` Now each population carries its own profile, keyed internally by scenario name. `browse` may issue up to 120 requests per second and `checkout` up to 80; neither can borrow the other's unused permits. The run still totals 200, but the **mix** is now guaranteed rather than emergent, which is usually what a tester actually wanted. ## Declaring both at once Nothing stops you doing both, and when you do, a request is admitted only if **both** budgets have room — the run-wide throttle and that scenario's own. Whichever is tighter at that instant binds. A global 200 with a per-scenario 80 on `checkout` therefore reads as: the run may not exceed 200, and `checkout` may not exceed 80 of them, whatever `browse` is doing. | declaration | what it guarantees | |---|---| | `throttle` on `setUp` only | a run total, with an unspecified split between scenarios | | `.throttle` per population | a per-scenario ceiling, and the total is their sum | | both | the run total and the per-scenario ceiling; the tighter one binds each second | ## Two asymmetries worth memorising The two forms are not simply the same mechanism at different scopes. 1. **Pause disabling follows the attachment point.** A throttle on `setUp` forces the pause policy to disabled in *every* population of the run. A `.throttle(...)` on one population disables pauses in that population only, and the others keep their think time. A run with one throttled scenario and one unthrottled scenario is therefore running two different pause regimes at once. 2. **The run's duration limit does not follow the attachment point.** Gatling takes the minimum of the declared `maxDuration`, the global throttle profile's duration and *every* population throttle profile's duration. So the shortest per-scenario throttle profile in the run ends the **whole** run, unthrottled populations included. Putting a two-minute throttle on one scenario of four quietly turns the entire run into a two-minute run. ## Choosing between them * If the scenarios really are one request each and you only care about the total, the global form is simpler — and Gatling would rather you expressed that rate in the injection profile anyway. * If the mix matters, use per-population throttles. It is the only form that makes the split a declaration instead of a race. * If you use per-population throttles, give every population a profile of the same total length, or accept that the shortest one is the run's clock. * Keep the ceilings close to what the injection profile actually offers, in either form: everything over the ceiling is parked in the injector's memory rather than dropped. ## A note on re-declaring A second `.throttle(...)` call on `setUp` replaces the first outright — it is a plain assignment in the Scala core and in the Java API alike, so there is no merging to reason about. If you build a throttle profile conditionally, build the list first and pass it once.
- You declare a global throttle and a per-scenario throttle at once. Which one applies?Both. A request is admitted only when the run-wide budget and that scenario's own budget each still have room for the current second, so whichever is tighter at that instant binds. It reads as a run total plus a per-scenario sub-limit inside it.
- One of four populations carries a two-minute throttle profile. How long does the run last?Two minutes. Gatling takes the minimum of the declared maxDuration, the global throttle profile's duration and every population throttle profile's duration, then stops the run gracefully at that point. The three unthrottled populations are cut short with it.
- Does throttling one population of two disable pauses in the other?No. Pause disabling is resolved per population from whether that population is throttled or a global throttle exists. A per-population throttle silences pauses only in its own scenario, so the run ends up with two different pause regimes at the same time.
A throttle on setUp is a single turnstile at the entrance: two queues, one counter, and whoever pushes forward hardest gets through. A throttle per population is a turnstile for each queue — the totals match, but now the mix is yours to set.
saying these in an interview costs you the question
- Expecting a setUp throttle to split evenly between scenarios
- Thinking a per-population throttle bounds only its own population's duration
- Assuming two throttle calls on setUp accumulate rather than replace
- Believing a scenario-level throttle disables pauses run-wide