skip to content

In a Gatling scenario, what does pace(5) do that pause(5) does not, and which of the two holds a virtual user's request rate steady when the server slows down?

level: middleimportance: must knowfreq 60%

answer

  1. two waiting steps, one holds a rate
  2. pause adds, pace absorbs
  3. pace stores a deadline under a counter name
  4. pace enforces a minimum period only

basics

~20 s

pace holds an iteration rate, pause does not. pause(5) always adds five seconds on top of the work; pace(5) waits only the remainder of a five-second window measured from the user's previous visit to that step.

solid answer

~60 s

Both are wait steps in a chain, but they measure different things. `pause(5)` is unconditional: it sleeps five seconds after whatever came before, so an iteration that spends 800 ms in the server takes 5.8 s and one that spends 3 s takes 8 s — the rate drifts with latency. `pace(5)` keeps a deadline in the virtual user's session and waits only until five seconds after that user's previous visit, so it absorbs the server's own latency and the loop turns over every five seconds. Two consequences matter. `pace` sets a **minimum** period, not a fixed one: if the work overran the interval the user passes straight through with no wait and the rate slips. And a *lone* `pace` outside a loop never waits, because its one visit finds nothing armed and simply arms a deadline. Note *lone*: the deadline is keyed by the step's **counter name**, not by the step itself, and the Scala DSL's default name is the constant `gatling.pace`, so two default-named Scala paces share one deadline and the second does wait outside a loop. The Java and Kotlin APIs generate a unique name per call.

code

java · 6 lines
java
ScenarioBuilder search = scenario("Search every five seconds").exec(
  forever().on(
    pace(Duration.ofSeconds(5))
      .exec(http("Search").get("/search?q=gatling"))
  )
);

go deeper

for a junior

Be ready to say which of the two steps you reach for when a requirement is phrased as a rate rather than as think time.

for a middle

Be ready to walk through the deadline pace keeps in the session under a counter name, to show why a visit that finds nothing armed never waits, and to say why two default-named Scala paces share one deadline.

for a senior

Be ready to explain that pace silently degrades to no wait once the work overruns, and to say where in the report you would check whether the intended rate actually held.

for a principal

Be ready to weigh pacing each user against declaring the arrival profile, and to say what each choice makes the run's numbers mean.

Gatling ships two waiting steps, and the difference between them is the whole reason the second one exists. `pause` waits for a duration. `pace` waits until a moment. Everything else follows from that. ## `pause` measures nothing `pause(5)` is unconditional. When a virtual user reaches it, it schedules the continuation five seconds later, whatever happened before. So an iteration whose requests took 800 ms occupies 5.8 seconds and an iteration whose requests took 3 seconds occupies 8 seconds. The user's request rate is therefore a function of the server's latency: the slower the system under test gets, the fewer iterations each user completes. That is often exactly what you want, because it is how a real person behaves. It is not what you want when the requirement is stated as a rate. ## `pace` measures elapsed time `pace(5)` keeps a deadline in the virtual user's own session. The action's logic is short enough to state exactly: 1. Read the deadline stored under this pace's **counter name** out of the session. 2. If there is no deadline, or the stored one has already passed, let the user through **immediately** and store `now + 5s`. 3. Otherwise schedule the user to continue at the stored deadline, and when that fires, store a new deadline five seconds after the release. So a visit that finds nothing armed never waits — it only arms the clock. On every later visit it waits the *remainder* of the window, absorbing whatever the requests in between consumed. An iteration that spent 800 ms in the server waits 4.2 s; one that spent 3 s waits 2 s; the loop turns over every five seconds either way. ## Worked example: one search every five seconds The requirement "each user must hit the search endpoint once every five seconds whatever the server does" is a `pace`, and it has to be inside a loop: ```java ScenarioBuilder search = scenario("Search every five seconds").exec( forever().on( pace(Duration.ofSeconds(5)) .exec(http("Search").get("/search?q=gatling")) ) ); ``` Write the same thing with `pause(5)` and the interval becomes "five seconds plus however long search took", which drifts the moment the endpoint slows down — and drifts most exactly when the system is under the pressure you are trying to measure. ## The two limits worth knowing * **`pace` is a floor, not a guarantee.** When the work overruns the interval, step 2 above fires: there is no wait at all, the deadline is re-armed from the late arrival, and the period becomes the duration of the work. `pace` cannot compress an iteration that already overran, and it never tries to catch up the time it lost. If you need to know whether the rate held, you have to look at the achieved throughput in the report rather than assume the declaration held. * **A *lone* `pace` outside a loop does nothing.** Its first and only visit finds nothing armed and takes the immediate branch, so a one-off `pace` between two requests never waits; it belongs at the top of a `repeat`, `during` or `forever` body. Read that as *lone*, not as *any*. The deadline lives under the step's **counter name**, not under the step itself, and the Scala DSL's default name is the constant `gatling.pace` — `generatePrivateAttribute("pace")`, not the unique variant the loop counters use. So in `exec(a).pace(5).exec(b).pace(5).exec(c)` the second Scala `pace` reads the deadline the first one armed, finds it still in the future, and **does** wait, on its very first visit, outside any loop. The Java and Kotlin APIs generate a unique counter name per call, so there each step owns its deadline — which is exactly why `pace` takes an optional counter name. ## `pace` paces one user The deadline lives in the session, and every virtual user has its own session. `pace(5)` therefore holds *one user* to one lap per five seconds; a thousand such users produce roughly two hundred laps per second between them. It is not a cap on the run's total request rate and it does not coordinate users with each other. ## Side by side | | `pause(5)` | `pace(5)` | |---|---|---| | what it waits for | a fixed five seconds | until five seconds after the previous visit | | effect of server latency | adds to the iteration | absorbed, up to five seconds | | when the work overruns | still adds five seconds | no wait at all | | first visit, nothing armed yet | waits | does not wait | | a lone step outside a loop | waits | does not wait — it only arms its counter | | affected by the run-wide pause type | yes | no | That last row is the one people are most often surprised by. The pause *type* declared on `setUp` — constant, exponential, uniform, normal, custom or disabled — is consulted only when Gatling builds a `pause` action. `pace` never looks at it, so `disablePauses()` strips every `pause` out of the scenario and leaves every `pace` exactly where it was. ## Both take a range `pace(min, max)` exists as well and draws the target interval uniformly at random, the same way `pause(min, max)` draws its wait. It is the right shape when you want an average rate rather than a metronome — the window is random, but it is still measured from the previous visit rather than added to the work.

  • What happens when the work inside a pace(5) loop takes eight seconds?
    Nothing waits. On the next visit the stored deadline has already passed, so pace lets the user through immediately and re-arms the deadline from that moment. The loop then turns over every eight seconds rather than every five: pace enforces a minimum period and never tries to make up time it has lost.
  • Does pace(5) limit the whole simulation to one request every five seconds?
    No. The deadline is stored in each virtual user's own session, so pace paces one user. A thousand users each running pace(5) produce roughly two hundred iterations per second between them; pace coordinates nothing across users and caps nothing run-wide.
  • Why does pace accept an optional counter name?
    The deadline is held under a session attribute and the counter name is that attribute's key, so two paces sharing a name share one deadline. The default differs by SDK, which is what makes this worth knowing: the Scala DSL names it with the constant gatling.pace, so every default-named pace in one Scala scenario already shares a single deadline, while the Java and Kotlin APIs generate a unique name per call. Name each pace explicitly when a Scala scenario holds more than one and you want them independent — or share a name deliberately when you want two steps to hold one rhythm.

A pause is a rest you take after finishing a job. A pace is a departure board: the next train leaves at half past whether boarding took two minutes or nine, and if boarding took forty you simply miss the slot and leave at once.

saying these in an interview costs you the question

  • Believing pace guarantees the rate however slow the server is.
  • Putting a lone pace outside a loop and expecting it to wait.
  • Thinking pace caps the run's total request rate.
  • Assuming disabling pauses also disables pace.