A JMeter Thread Group asks for 1,000 threads and the plan runs on six remote engines - how many threads hit the target?
answer
- The plan is copied, not split
- The controller injects nothing itself
- The Thread Group field is per engine
- One thousand, and six engines
basics
~10 sSix thousand. JMeter never splits a plan across engines: the controller sends the whole plan to each one, so the Thread Group's 1,000 threads start once per server. You do the division, not JMeter.
solid answer
~50 sJMeter replicates a plan; it never shards it. The controller serialises the whole test tree and hands an identical copy to every remote engine over RMI, and each engine runs that plan in full through an ordinary local engine. A Thread Group set to 1,000 threads therefore starts 1,000 threads **on each** of the six servers, and the target sees **6,000**. The controller starts no threads of its own in a remote run - it configures the engines, tells them to start, and collects the samples they send back. The division is yours to do: put the per-engine share in the `ThreadGroup.num_threads` field (167 if you want roughly 1,000 in total across six engines) and remember that every other per-engine number in the plan - loop counts, per-engine rate targets, anything scoped to one JVM - is multiplied by the engine count in the same way.
code
xml · 6 lines<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.num_threads">1000</stringProp>
<stringProp name="ThreadGroup.ramp_time">300</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
</ThreadGroup>go deeper
Remember the shape: one plan, copied whole to every engine. Asked what 1,000 threads on six JMeter servers gives, the answer is 6,000 - not 167 each, and not 1,000 spread around.
Be able to say why. The controller serialises the tree and each engine runs it in full, so every number the plan scopes to one JVM gets multiplied by the engine count.
Show that you compute the applied total before every distributed run, and that you treat a lost or added engine as a change to the load rather than an operational detail.
Own where the division lives. Decide whether the per-engine share is baked into the .jmx or supplied at run time, and make the run record the engine count beside the result so old runs stay comparable.
## Replication, not sharding JMeter's distributed mode copies a test plan; it does not cut it up. When a controller starts a remote run it clones the test tree, serialises it, and calls the same configure step on **every** engine in its list with the identical tree. Each `jmeter-server` process hands that tree to a plain local engine - the very same engine class a standalone run uses - and executes it from the top. Nothing in the wire protocol carries a "which slice am I" hint, and no stock element in the plan reads one. That gives the arithmetic every JMeter operator learns exactly once: a Thread Group whose **Number of Threads** field reads `1000`, started against six servers, applies **6,000** threads. The JMeter 6.0.0 manual states it in those words, and the code agrees - the controller loops over the host list creating one client engine per host and sends each the same tree. ## The controller is not a seventh generator When a run is started remotely, the controller builds *only* client-side engines. It does not additionally create a local engine for itself, so the total is `threads x engines`, never `threads x (engines + 1)`. Its own CPU goes on serialising the plan out, deserialising sample results back, and writing the result file. An overloaded controller therefore shows up as a *collection* problem, not as missing load - the servers still applied their full share. ## What multiplies and what does not | Plan setting | Scope it is evaluated in | Effect across six engines | |---|---|---| | `ThreadGroup.num_threads` | one engine | x6 threads applied | | Loop count / iteration budget | one thread on one engine | x6 total iterations | | Ramp-up period | one engine, on its own clock | unchanged; all six ramp in parallel | | A file read position | one JVM | six independent positions | | A "shared across all threads" scope | one JVM | six independent groups | The pattern is one rule: **anything JMeter scopes to a JVM becomes six of that thing.** Durations and periods are the exception, because the engines run concurrently rather than in sequence - six engines ramping for five minutes still take five minutes. ## Doing the division yourself 1. Decide the total the target must see. That decision is a workload question and lives outside JMeter. 2. Count the engines you will actually start - the number you *intend* to start is not the number that came up. 3. Put the quotient in the Thread Group field, and accept the rounding: 167 on six engines is 1,002, not 1,000. 4. Walk the rest of the plan for other per-engine numbers and divide those too. 5. Record the engine count next to the result. A number from "1,000 threads" means nothing without it. ## Where this bites in practice - **An engine is added "to be safe".** Six becomes seven and the applied load rises by a sixth, with no change to the `.jmx` and nothing in the result file that says so. - **An engine fails to come up and the run continues on the rest.** The applied load silently drops by a sixth; the plan is byte-identical to yesterday's. - **A plan is promoted from a laptop smoke run.** The field that was right for one engine is now six times too large. - **Two Thread Groups in one plan.** Both replicate, so the multiplier applies to the whole plan, not to a single group. - **A run is compared to an older one.** Unless the engine count matched, the two runs applied different loads whatever the field says. ## The check that costs nothing Before a distributed run, multiply the Thread Group field by the number of engines you are about to start and say the product out loud. If that product is not the number you meant to apply, stop and fix the field. The mistake this catches - a plan that quietly applies six times the intended concurrency and takes the target down - is the single most common distributed-JMeter incident, and it is arithmetic, not configuration. ## Why JMeter works this way The replication is not an oversight; it is the cheapest possible protocol. An engine needs no knowledge of its peers, engines can be added or lost without renegotiating anything, and a plan that runs on one machine runs unchanged on twenty. The price is that the only component that knows how many engines exist is you, standing at the controller, and JMeter never asks you to write that number down anywhere it could check.
- You want 1,000 threads in total across six JMeter engines. What do you set, and what does the rounding cost you?Set `ThreadGroup.num_threads` to 167 and accept 1,002 applied threads, or drop to five engines of 200 for an exact 1,000. JMeter has no per-engine override in the plan itself, so an uneven split means either accepting the rounding or changing the engine count.
- During a remote-started JMeter run, does the controller machine apply any load of its own?No. On a remote start the controller creates only client engines, one per host, and no local engine. It serialises the plan out, receives sample results, and writes the result file. Its CPU cost is collection, not injection.
- One of your six JMeter engines fails to configure and the run proceeds on the other five. What has changed about the test?The applied load dropped by a sixth while the plan stayed identical. Any comparison against a six-engine baseline is now invalid. Treat the engine count as part of the run's configuration and record it with the result, not as an operational detail.
It is like emailing the same shopping list to six housemates. Nobody buys a sixth of it - you come home with six of everything. If you want the list bought once, you cut it into six lists before you send it.
saying these in an interview costs you the question
- Says the controller splits the thread count across the servers.
- Thinks adding an engine spreads the same load rather than adding to it.
- Counts the controller as an extra load generator alongside the servers.
- Assumes loop counts stay global while only threads multiply.
- Compares two runs without checking that the engine counts matched.