In Gatling, how is a load profile declared, and how does that differ from a generator where you size a pool of virtual-user threads?
answer
- The profile is source, not settings
- Chained injection steps, one setUp call
- Registered in the simulation constructor
- Asynchronous engine, no thread per user
basics
~20 sA Gatling load profile is a chain of injection step builders written in the simulation's own source and handed to a single setUp call. There is no thread pool to size, because Gatling's engine is asynchronous and does not allocate one operating-system thread per virtual user.
solid answer
~40 sIn Gatling the unit of load design is an **injection step**: you chain step builders together to describe how virtual users arrive over time, and you register that chain with one `setUp` call in the simulation's constructor. The profile is therefore ordinary program text — it lives in the repository beside the scenario it drives, it is reviewed and diffed like code, and it is parameterised with the language's own variables and functions rather than by editing fields. The second difference is underneath: Gatling's engine is fully asynchronous and built on Netty, and a virtual user is not backed by a dedicated operating-system thread, so raising the user count does not mean raising a thread count on the generator.
code
kotlin · 21 linesimport io.gatling.javaapi.core.*
import io.gatling.javaapi.core.CoreDsl.*
import io.gatling.javaapi.http.HttpDsl.*
import java.time.Duration
class CheckoutLoad : Simulation() {
val httpProtocol = http.baseUrl("https://ecomm.gatling.io")
val browse = scenario("Browse").exec(http("Get home").get("/"))
init {
setUp(
browse.injectOpen(
rampUsers(500).during(Duration.ofMinutes(2)),
constantUsersPerSec(50.0).during(Duration.ofMinutes(10))
)
).protocols(httpProtocol)
.assertions(global().successfulRequests().percent().gt(99.0))
}
}go deeper
Be ready to point at a simulation and say which lines are the load profile and which are the scenario.
Be ready to explain that injection steps chain into a profile registered by one setUp call in the constructor, and that a virtual user is not an operating-system thread.
Be ready to say what the code-as-profile model costs an organisation whose load profiles are changed by people outside the repository, and how you would mitigate it.
Be ready to own the trade between a reviewable profile that requires a build and an editable one that does not, and to say which failure mode your organisation can least afford.
Two things distinguish how Gatling expresses load, and they are frequently confused with each other. One is about **authoring** — what you write down. The other is about **execution** — what the generator does with it. They are independent, and a good answer separates them. ## Authoring: the injection step is the unit A Gatling load profile is not a set of fields you fill in. It is a chain of **injection step builders**, each describing one segment of how virtual users arrive, composed in the order they should happen and passed as arguments to a single registration call. ```kotlin setUp( browse.injectOpen( rampUsers(500).during(Duration.ofMinutes(2)), constantUsersPerSec(50.0).during(Duration.ofMinutes(10)) ) ).protocols(httpProtocol) ``` Three properties follow directly from this being code: * **It is versioned and reviewable.** The profile is a diffable expression in a source file, so a change from a two-minute ramp to a five-minute one shows up in review as a one-line change with an author and a reason. * **It is composable.** Because the builders are ordinary values, you can extract one into a named constant, build one from a parameter, or share a house profile between simulations using the host language's normal mechanisms. * **It is checked before it runs.** On the JVM surfaces a typo in a step name or an argument type is a compile error, not a run that starts and then misbehaves. The registration rule matters as much as the steps. `setUp` is called **exactly once, in the simulation's constructor**, and every step handed to a single `injectOpen` or `injectClosed` call must belong to the same workload kind — open and closed models cannot be mixed inside one injection profile. Note where that restriction lives: it is per injection call, not per `setUp`. A single `setUp` may register an open-model population alongside a closed-model one, because each population carries its own injection profile and nothing validates that they agree. Gatling instantiates the simulation class through its no-argument constructor, so by the time construction finishes the profile must already be registered — there is no later hook where you can add to it. ## Execution: no thread per virtual user Underneath, Gatling's architecture is **fully asynchronous**. Its documentation states plainly that virtual users are modelled as lightweight units of work rather than as threads, so that a single machine can hold many concurrent users with modest resources. The HTTP engine is built on **Netty** — the repository carries a dedicated Netty utility module and depends on Netty's HTTP codec, with native transports available on Linux. The consequence to state precisely is a **negative** one: the number of concurrent virtual users a Gatling generator can hold is not bounded by an operating-system thread per user. That is genuinely different from a generator whose virtual-user model is one blocking thread each, where the thread count is the load knob and the generator's own thread scheduling becomes a factor well before the target system is stressed. ## What this does not buy you Be careful not to over-claim, because interviewers listen for exactly this: 1. **It is not a capacity promise.** Gatling's own FAQ makes generator capacity depend on protocols, resource usage, what each user does and script optimisation, and gives no unconditional figure. The numbers it does print arrive attached to a workload — 64,000 users per second on a local machine *if one user equals one request*, and limited by the operating system rather than by Gatling — so they are worked examples, not a rating. Deciding how much generator you need is a performance-testing discipline in its own right, not a property of the tool. 2. **It does not make the generator unable to be the bottleneck.** An asynchronous engine still runs on a machine with finite CPU, sockets and memory, and a heavy scenario can still saturate it. 3. **It does not make the profile self-documenting.** Code is reviewable, but a chained profile with no comments is as opaque as any other dense expression. ## The trade-off honestly stated Expressing load as code is a real advantage when the people who change the load profile are the people who already work in the repository: the profile travels with the application, it is refactored with it, and there is one review process for both. It is a real cost when they are not. A profile that only compiles inside a build means that changing "run it for thirty minutes instead of ten" requires someone able to edit, build and run a project — which is a higher bar than editing a value in a form. Generators that keep the profile in an editable artifact make that easier and pay for it in reviewability. That trade — reviewability and composition on one side, edit-without-building on the other — is the one worth naming, and it is a decision about your team rather than a defect in either tool.
- If the load profile is code, how do you run the same simulation at two different intensities without duplicating it?Read the intensity from a value the host language can supply — a system property, an environment variable or a constructor-level constant — and build the injection step from it. Because the steps are ordinary expressions, computing an argument is just computing a number; nothing about the registration call changes.
- Does an asynchronous engine mean the generator can never be the bottleneck in a run?No. It removes one specific limit, the operating-system thread per virtual user, but CPU, sockets, memory and the cost of whatever each virtual user actually does all still apply. Whether the generator or the target is the constraint is something a run has to demonstrate, not something the architecture guarantees.
saying these in an interview costs you the question
- Believing an asynchronous engine removes all generator capacity limits
- Thinking setUp can be called again later to extend the profile
- Treating the language's thread count as Gatling's load knob
- Assuming code-as-profile is strictly better than an editable artifact