Distributed Testing, Remote Engines, Network Topology, and Synchronization: Core Concepts and Mental Model
Chapter 24 turned one JMeter generator into a measured system: JTL, generator telemetry, Backend Listener metrics and SUT metrics were all distinct signals. Distributed testing adds more generators and a second control plane. The hard part is not “starting two machines.” It is preserving the exact workload, data, code, ports, trust boundary and evidence on every engine while remembering one rule that surprises many beginners: JMeter does not divide your Thread Group across remote engines—every engine runs the full supplied plan.
Learning objectives
- Explain controller/client versus remote-engine responsibilities.
- Calculate total distributed workload as the sum of full-plan engine workloads.
- Distinguish JMX transfer from external data/plugin/property distribution.
- Understand RMI registry, server-engine and reverse callback network paths.
- Inspect versions, files, properties, ports, logs and results before scaling.
- Separate target capacity from controller/engine/network/result-transfer capacity.
1. The practical problem: scaling the injector can silently change the experiment
A local plan is configured for 1,000 threads. An engineer adds six remote engines expecting about 167 threads per engine. JMeter actually starts the full 1,000-thread plan on each engine: 6,000 configured threads. If the data file contains only 1,000 unique users or the SUT was authorized for 1,000 users, the mistake is both a validity and a safety failure.
127.0.0.1:8025; the two-engine checkpoint configures
only 2 threads ×3 loops per engine = 12 total samples. RMI must not
be exposed to the public internet. No real credentials, production
target, shared test data or unbounded remote hosts are used.
2. Mental model: controller sends commands; engines generate load
Distributed JMeter is a load-generation system with multiple execution and evidence boundaries. The controller sends one test plan to each engine; every engine executes the full configured workload, reads its own local files/properties, targets the SUT independently, and returns or exports results through a separate network path.
flowchart TD C[Controller/client JMX + remote command + global properties] -->|RMI over SSL| A[Engine A full JMX] C -->|RMI over SSL| B[Engine B full JMX] A -->|reads local shard A| DA[data/engine-a.csv] B -->|reads local shard B| DB[data/engine-b.csv] A -->|HTTP target traffic| T[Authorized SUT] B -->|HTTP target traffic| T A -->|remote sample results| C B -->|remote sample results| C C --> J[Central aggregate JTL + controller jmeter.log] A --> LA[engine-a jmeter.log + generator state] B --> LB[engine-b jmeter.log + generator state] T --> E[target telemetry/events]
The controller holds the JMX and starts remote engines using secured RMI. It sends the same plan to both engines. Engine A and Engine B each instantiate their own Thread Groups, variables, protocol connections and local file handles; therefore each produces its own configured load. External CSV files are not part of the JMX transfer, so each engine reads an explicitly staged shard. Sample results flow back through the RMI result path and the controller can write one aggregate JTL. Engine logs/resource state remain engine-local. The target sees the sum of both engines.
3. Distributed load math
For a closed Thread Group with one sampler:
per_engine_threads = Thread Group threads
per_engine_samples = threads × loops × samplers_per_loop
total_threads = sum(per_engine_threads across engines)
total_samples = sum(per_engine_samples across engines)
Example:
2 engines × (2 threads × 3 loops × 1 sampler) = 12 total samples
6 engines × 1000 configured threads = 6000 configured threads
Do the total-load calculation before starting any server. If engines intentionally use different properties, calculate each term separately rather than multiplying by engine count.
4. Controller/client versus remote engine
| Controller/client | Remote engine |
|---|---|
| Loads JMX, selects remote hosts, initiates/controls test. | Receives the JMX and executes the full plan. |
Supplies -G global properties to servers.
|
Reads engine-local startup properties and distributed global properties. |
Receives remote samples and can write aggregate
-l JTL.
|
Creates actual protocol sessions/target traffic. |
| Owns reverse RMI callback listener ports. | Owns RMI registry/server-engine ports. |
| Can become CPU/network/result-processing bottleneck. | Can become CPU/GC/network/socket/data bottleneck. |
5. Version and plugin identity is part of correctness
Current JMeter remote guidance requires exactly the same JMeter version on client and servers and discourages different Java versions. The same principle applies to third-party plugins, user JARs, Groovy dependencies and certificates: a remote engine can only instantiate classes that exist on that engine.
The mandatory lab uses JMeter 5.6.3, Java 17 and no third-party plugins, so the plugin manifest is intentionally empty. Production should compare file names/hashes for all non-core additions.
6. What is transferred—and what is not
The controller sends the test plan to servers automatically. It does not send CSV data files. A CSV Data Set relative path must resolve on each server in the correct server-side directory; absolute paths work only when that engine actually has the corresponding path.
Do not assume JMX transfer means “the whole project directory was deployed.” Data, plugins, user JARs, certificates and other files require an explicit deployment/manifest step.
7. Local, remote-global and engine-local properties
-
-Jname=valuechanges a property in the process where the command runs—the controller. -
-Gname=valuesends a JMeter property to all remote servers. -
Engine-specific properties such as
engine.idordata.fileshould be set on each server before/startup, not with one global-Gvalue. - Thread-local variables still belong to each engine/thread; they are not shared across JVMs.
In the lab, both engines receive threads=2/loops=3
through -G, but engine-a starts with its own
engine.id/data.file and engine-b starts with different
values.
8. RMI network paths and SSL
JMeter remote mode uses RMI. A controller connects to each server registry (default 1099 unless changed), then also needs reverse connections for returned sample/thread events. Since JMeter 4.0 the default RMI transport is SSL.
For firewall-friendly private environments, explicitly set:
- one registry port per server instance;
server.rmi.localportfor the server engine;-
client.rmi.localporton the controller for up to three consecutive callback ports; - the advertised RMI hostname/IP reachable only inside the authorized network;
- a controlled keystore/truststore shared according to your trust design.
server.rmi.ssl.disable=true.
It exists, but the chapter keeps SSL enabled. The generated
seven-day/default-passphrase keystore is a lab convenience, not a
production PKI standard.
9. Result transfer can become the bottleneck
Remote sample delivery consumes engine/controller/network resources.
Current default sender mode is StrippedBatch, which
strips successful response bodies and batches results.
Standard sends each sample synchronously;
Hold retains all samples in memory until the end and is
discouraged. Asynch uses a queue that can eventually
fill and block sampler threads.
A slow controller or return network can therefore reduce achieved load even when the SUT is healthy.
10. Distributed state checklist
| State | Read-only question |
|---|---|
| Controller | JMeter/Java version, remote host list, callback ports, CPU/heap/network, sample-sender mode? |
| Engine | JMeter/Java/plugin manifest, engine ID, RMI ports/SSL, CPU/GC/network, local log? |
| Thread/arrival | Per-engine threads/loops/timers and calculated total offered load? |
| Files/data | Does every engine have the exact expected CSV/JAR/cert shard/hash? |
| Properties |
Which values are controller-local -J, global
-G, or engine-local startup values?
|
| Protocol/session | Each engine creates independent HTTP/TCP/session pools; any source-IP/NAT limits? |
| Target | Authorization ceiling accounts for sum of all engines; target sees multiple source paths? |
| Results | Controller aggregate JTL, per-engine logs/resource state, target counts, optional backend telemetry? |
| Trust | RMI SSL/keystore/firewall/private routing; no public jmeter-server exposure? |
| Validity | Per-engine achieved count + aggregate count + controller/engine headroom match configured math? |
11. Read-only inspection before starting remote servers
PowerShell:
& "$env:JMETER_HOME\bin\jmeter.bat" -v
java -version
Get-FileHash .\plans\distributed-local.jmx -Algorithm SHA256
Get-FileHash .\data\engine-a.csv,.\data\engine-b.csv -Algorithm SHA256
Get-Content .\config\remote-global.properties
Get-Content .\config\remote-results.properties
Get-NetTCPConnection -State Listen |
Where-Object LocalPort -in 1099,1100,4000,4100,5000,5001,5002
curl.exe --fail --silent http://127.0.0.1:8025/health
Do not start remote engines if ports are unexpectedly occupied, versions differ, files/hashes are missing, or total-load math is not approved.
12. DevOps connection
Distributed load generation is infrastructure. It needs inventory, version pinning, file deployment, secrets/certificates, ports/firewalls, capacity monitoring, health checks, logs, immutable manifests and cleanup just like any other service fleet. You cannot safely scale the SUT workload until you can operate the generators themselves.
Knowledge check
Two remote engines each receive a plan with 100 threads. How many configured threads are offered?
200. JMeter runs the full plan on each engine; it does not split 100 across them.
Which file is sent automatically to remote engines?
The JMX/test plan. External CSV/plugin/JAR/certificate/project files require explicit deployment.
Why is -Gthreads=2 appropriate but -Gengine.id=engine-a wrong for a two-engine lab?
-G sends the same property to every remote server; engine.id must differ per engine and therefore belongs in engine-local startup configuration.
Why can the controller invalidate a distributed test even when engine CPU is low?
Remote result processing/reverse RMI/network traffic can saturate controller CPU/heap/network and reduce achieved engine throughput.
Why is RMI SSL part of measurement/security validity?
Remote engines execute supplied plans and handle results/files; exposing unauthenticated/insecure RMI creates a serious trust boundary and can also change network topology/behavior.
Official references and version notes
- JMeter User Manual — Remote (Distributed) Testing — full-plan replication, same-version guidance, data-file behavior, RMI SSL, remote ports, CLI remote execution, and sample sender modes.
-
JMeter Getting Started
—
-r,-R,-G,-X, CLI/server mode, property semantics. - JMeter Properties Reference — remote hosts, controller/server RMI ports, SSL keystore/truststore settings, client failure policy and result properties.
- Component Reference — CSV Data Set Config — distributed CSV file placement and relative/absolute path behavior.
- JMeter Listeners / Result files — CSV result fields, sample variables and host attribution.
- Apache JMeter downloads — current stable release and Java requirement.
Version-sensitive statements were rechecked against current Apache
JMeter primary documentation on 2026-09-05. The course baseline
remains Apache JMeter 5.6.3 with a Java 17 JDK;
JMeter 5.6.3 requires Java 8+. JMeter remote mode sends the test
plan to every remote server, but each engine runs the entire test
plan; workload is not divided automatically. All controller/server
nodes should run exactly the same JMeter version, and JMeter
discourages mixing Java versions. External data files are not sent
by the controller and must exist on every server in the expected
path; plugins/user JARs likewise need an explicit identical
deployment. CLI -r starts servers listed in
remote_hosts; -Rhost1,host2 explicitly
selects/overrides the server list; -Gname=value or
-Gpropertyfile sends JMeter properties to remote
servers. Since JMeter 4.0, RMI uses SSL by default. JMeter ships
create-rmi-keystore, whose generated test certificate
is documented as valid for seven days and uses default alias
rmi/passphrase changeit; these defaults
are suitable only for a disposable private lab.
server.rmi.ssl.disable defaults to false and this
chapter never disables it. The remote-testing manual describes
dynamic server-engine ports conceptually, while the current
properties reference lists
server.rmi.localport default 4000; production/private
labs should set registry/server/callback ports explicitly instead
of relying on defaults. The controller reverse callback
client.rmi.localport defaults to 0 (random); when set
non-zero JMeter can use up to three consecutive ports. Current
default remote sample sender mode is StrippedBatch:
successful response bodies are stripped and results are batched.
Remote mode can consume more resources than equivalent independent
CLI workers, and the controller/client or its network can become
the bottleneck.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.