Most ActiveMQ performance benchmarks are wrong, not slightly off, but fundamentally invalid for capacity planning. Performance benchmarking done incorrectly is worse than not benchmarking at all. A number that looks like a throughput measurement but was collected without JVM warmup, without latency percentiles, with the load generator co-located on the broker host, and while producer flow control was intermittently activating. That number is not a performance measurement. It is noise with a convincing appearance of precision.
This guide provides the ActiveMQ performance benchmarking methodology that produces results you can rely on: the seven measurement errors to eliminate before running a single test, the built-in benchmark tools for Apache ActiveMQ® and Apache Artemis™, the benchmark scenario matrix that covers the cases production actually cares about, how to interpret throughput vs latency results, what a soak test reveals that throughput tests miss, and how to build a repeatable benchmark suite for CI/CD integration.
The Seven Performance Benchmarking Errors
Before running any benchmark, eliminate these seven errors. Each one invalidates results in a specific way.
Error 1: Co-locating the Load Generator with the Broker
The load generator (producer and consumer processes) competes with the broker for CPU, memory, disk I/O, and network. On a 4-core host with a load generator consuming 2 cores, the broker operates at half its available CPU capacity. The measured throughput is the joint capacity of the constrained system, not the broker’s capacity.
Fix: always run the broker on a dedicated host. Run producers and consumers on separate hosts from each other and from the broker. Minimum architecture: three machines (broker, producer host, consumer host).
Error 2: Skipping JVM Warmup
A fresh JVM runs code in interpreted mode until the JIT compiler identifies “hot” code paths and compiles them to native code. For ActiveMQ, the critical hot paths are message marshaling/unmarshaling, KahaDB journal writes, and dispatch logic. JIT compilation of these paths typically takes 30-120 seconds of steady-state operation. Measurements taken before JIT stabilizes can be 2-5× slower than post-JIT steady state.
Fix: always collect measurements starting at least 60 seconds (120 seconds for large, complex configurations) after the broker and load generator have been running under a representative load. Both the Apache ActiveMQ® Maven plugin and the Apache Artemis™ perf CLI support explicit warmup configuration.
Error 3: Measuring Only Throughput, Not Latency Percentiles
Maximum throughput tells you where the system falls over. It does not tell you where the system remains within SLA. The throughput at which p99 latency exceeds your SLA (say, 100ms for a financial processing queue) is the actual production capacity limit. This is typically 60-80% of maximum throughput.
Fix: always measure p50, p90, p99, and p999 latency alongside throughput. Plot latency vs. throughput at multiple load levels. The “knee” in the latency curve, where p99 begins to diverge from p50, is your production operating point.
Error 4: Not Running Long Enough
A 5-minute benchmark does not reveal:
- GC pauses and their effect on p99 latency (requires multiple full GC cycles, which may occur every 20-45 minutes)
- KahaDB journal accumulation from unconsumed messages
- Memory leaks in broker plugins
- Thread starvation from growing destination count
- Disk I/O pattern changes as journal files roll over
Fix: steady-state benchmark = minimum 5 minutes, ideally 15-30 minutes. Soak test = minimum 2 hours, ideally 4-8 hours under representative load.
Error 5: Testing with Non-Representative Message Sizes
The message payload size profoundly affects throughput in two directions. Small messages (< 100 bytes) are network-bandwidth-efficient but CPU-bound for the broker (many messages per second to process). Large messages (> 10KB) are CPU-efficient but network and disk I/O intensive. Testing with 1KB messages when production uses 100KB messages produces a throughput number that may be 10-50× higher than production behavior.
Fix: benchmark with message sizes representative of your actual production message payloads. If you have a distribution of sizes (small command messages + large data messages), benchmark each size class separately.
Error 6: Triggering Producer Flow Control During Measurement
When producer flow control (PFC) activates during a benchmark, producers block. The measured throughput during a PFC event is the consumption rate of the backlog, not the broker’s dispatch capacity. A benchmark that intermittently triggers PFC produces throughput numbers that are wildly inconsistent and unpredictably lower than sustainable capacity.
Fix: monitor MemoryPercentUsage, StorePercentUsage, and TempPercentUsage during the benchmark. If any metric exceeds 70%, PFC may be activated. The benchmark environment needs more resources or a lower load rate. During benchmarking, PFC events appear in the broker log as Stopping producer. Grep for this before trusting any results.
Error 7: Not Matching Production Configuration
Testing a broker with a different configuration from production produces results for a hypothetical system, not your actual system. The most common mismatches:
- Testing non-persistent when production is persistent (throughput difference: 5-20×)
- Testing on local NVMe SSD when production is on network storage (throughput difference: up to 25×)
- Testing with default JVM settings when production has tuned G1GC (GC pause difference: 2-10×)
- Testing with default KahaDB settings when production has tuned journalMaxFileLength and indexCacheSize
Fix: the benchmark environment must mirror the production broker configuration: same JVM flags, same activemq.xml / broker.xml, same storage type, same network infrastructure.
The Built-in Benchmark Tools
Apache ActiveMQ®: ActiveMQ Maven Performance Plugin
The ActiveMQ Maven Performance Module (activemq-perf-maven-plugin) is the canonical benchmark tool for Apache ActiveMQ®. It ships with the ActiveMQ source distribution and is available as a Maven plugin dependency.
| <!– pom.xml — benchmark project setup –> <project> <groupId>com.example</groupId> <artifactId>activemq-benchmark</artifactId> <version>1.0</version> <build> <plugins> <plugin> <groupId>org.apache.activemq.tooling</groupId> <artifactId>activemq-perf-maven-plugin</artifactId> <version>5.18.3</version> <!– Match your broker version –> </plugin> </plugins> </build> </project> Running the benchmark, three-terminal setup: # Terminal 1: Start the broker (dedicated host) # Broker should already be running at tcp://broker-host:61616 # Terminal 2: Producer host — persistent messages, 10 producers, 1KB messages export MAVEN_OPTS=”-Xmx2g -Xms2g” mvn activemq-perf:producer \ -Dfactory.brokerURL=tcp://broker-host:61616 \ -DsysTest.numClients=10 \ -Dproducer.messageSize=1024 \ -Dproducer.deliveryMode=persistent \ -Dfactory.useAsyncSend=false \ -DsysTest.duration=600000 # 10 minutes # Note: first 60-90 seconds is warmup — ignore early output # Terminal 3: Consumer host — matching consumer count export MAVEN_OPTS=”-Xmx2g -Xms2g” mvn activemq-perf:consumer \ -Dfactory.brokerURL=tcp://broker-host:61616 \ -DsysTest.numClients=10 \ -Dconsumer.durable=false \ -DsysTest.duration=600000 # Interpreting output: # System Total Throughput: 562020 ← total messages in test duration # System Average Throughput: 1873.4 ← average msg/sec across all producers # System Average Client Throughput: 74.9 ← average per producer |
Key parameters:
| Parameter | Description | Recommendation |
|---|---|---|
| sysTest.numClients | Number of producer/consumer threads | Match expected production concurrency |
| producer.messageSize | Message payload bytes | Match production message sizes |
| producer.deliveryMode | persistent or nonpersistent | Match production delivery mode |
| factory.useAsyncSend | Async producer send | false for persistent (default); true for non-persistent throughput test |
| sysTest.duration | Test duration in ms | 300,000 (5 min) minimum; 1,800,000 (30 min) for soak |
Apache Artemis™: Built-in perf CLI
The Apache Artemis™ broker instance ships with a perf CLI that provides throughput and latency percentile measurement in a single command.
| # Running on a SEPARATE host from the broker # All-out throughput test — non-persistent, no latency measurement ./artemis perf client queue://BENCH_QUEUE \ –url tcp://broker-host:61616 \ –duration 300 # seconds # Example output during test: # — warmup false — # sent: 86525 msg/sec # blocked: 5734 msg/sec ← Monitor this! Should be << sent # completed: 86525 msg/sec # received: 86556 msg/sec # With latency percentiles (requires confirmationWindowSize) ./artemis perf client queue://BENCH_QUEUE \ –url “tcp://broker-host:61616?confirmationWindowSize=20000” \ –consumer-url tcp://broker-host:61616 \ –show-latency \ –warmup 60 \ –duration 300 # Latency output (send ack time = producer-side latency): # — send ack time: mean: 1056.09 us — # 50.00%: 1003.00 us ← p50 (median): 1ms # 90.00%: 1423.00 us ← p90: 1.4ms # 99.00%: 1639.00 us ← p99: 1.6ms # 99.90%: 4287.00 us ← p999: 4.3ms ← watch for spikes here # max: 19583.00 us ← worst-case: 20ms # Rate-controlled test (30,000 msg/sec target) for realistic latency ./artemis perf client queue://BENCH_QUEUE \ –url “tcp://broker-host:61616?confirmationWindowSize=20000” \ –consumer-url tcp://broker-host:61616 \ –show-latency \ –rate 30000 \ –warmup 60 \ –duration 300 # Asymmetric load: 5 producers, 2 consumers, multiple destinations ./artemis perf client queue://BENCH_QUEUE \ –url “tcp://broker-host:61616?confirmationWindowSize=20000” \ –consumer-url tcp://broker-host:61616 \ –num-producers 5 \ –num-consumers 2 \ –num-destinations 10 \ –show-latency \ –warmup 60 \ –duration 600 |
Interpreting the blocked metric: the blocked rate shows how often the load generator’s send operation was back-pressured by the protocol flow control (producer credits exhausted). When blocked is high relative to sent, the load generator cannot keep up with its own target rate. This indicates either the broker is slower than the target rate, or confirmationWindowSize is too small. The rule: blocked should be < 10% of sent for a valid steady-state measurement.
The Benchmark Scenario Matrix
A complete benchmark suite covers four scenario categories. Run all four before drawing conclusions about broker capacity:
Scenario 1: Peak Throughput (Non-Persistent)
Purpose: establish the ceiling for non-persistent message throughput. Eliminates disk I/O as a variable.
| # Classic Maven — non-persistent, async send mvn activemq-perf:producer \ -Dfactory.brokerURL=tcp://broker:61616 \ -DsysTest.numClients=20 \ -Dproducer.messageSize=1024 \ -Dproducer.deliveryMode=nonpersistent \ -Dfactory.useAsyncSend=true \ -DsysTest.duration=600000 # Artemis perf — non-persistent (default) ./artemis perf client queue://BENCH \ –url tcp://broker:61616 \ –warmup 60 –duration 300 |
Scenario 2: Sustainable Throughput with Latency (Persistent)
Purpose: establish the throughput at which p99 latency is within SLA. The most important scenario for production sizing.
| # Artemis perf — persistent, with latency, rate-controlled ./artemis perf client queue://BENCH \ –url “tcp://broker:61616?confirmationWindowSize=20000” \ –consumer-url tcp://broker:61616 \ –show-latency \ –persistent \ –rate TARGET_RATE \ # Start at 50% of Scenario 1 result; increase until p99 breaches SLA –warmup 120 \ –duration 600 |
Run at increasing rate targets: 25%, 50%, 75%, 90%, 100% of Scenario 1 maximum. Plot p99 latency vs. throughput. The “knee” in the latency curve defines safe operating capacity.
Scenario 3: Multi-Destination Stress Test
Purpose: validate that the broker scales with destination count. Destination count affects heap usage, thread count, and index cache behavior.
| # Artemis perf — multiple destinations, asymmetric load ./artemis perf client queue://BENCH \ –url “tcp://broker:61616?confirmationWindowSize=20000” \ –consumer-url tcp://broker:61616 \ –num-producers 10 \ –num-consumers 5 \ –num-destinations 100 \ –show-latency \ –warmup 120 –duration 600 # Repeat with 500, 1000, 5000 destinations # Watch: does throughput degrade linearly with destination count? # A step-change in throughput at a specific destination count # indicates a resource ceiling (heap, thread pool, etc.) |
Scenario 4: Soak Test (Extended Duration)
Purpose: detect resource leaks, GC degradation, journal growth, and performance regression over time.
| # Soak test: 4 hours, persistent, representative load rate # Target: 60% of maximum sustainable throughput from Scenario 2 ./artemis perf client queue://BENCH \ –url “tcp://broker:61616?confirmationWindowSize=20000” \ –consumer-url tcp://broker:61616 \ –show-latency \ –persistent \ –rate SOAK_RATE \ –warmup 120 \ –duration 14400 # 4 hours in seconds # What to watch during soak: # 1. Throughput stability: should remain within ±5% of initial rate # 2. p99 latency trend: should not increase over time # 3. Broker heap: should have stable GC behavior (not growing cycle to cycle) # 4. KahaDB disk usage: monitor growth rate # 5. JVM GC log: check for increasing full GC frequency or duration |
Throughput vs Latency: What Each Tells You
These two metrics answer different questions about the system’s capacity, and understanding the throughput vs latency trade-off is the core of any credible benchmark:
Throughput answers: how much can the system handle? It measures the volume of work successfully completed per unit time. For a message broker, throughput is messages delivered per second. Throughput is the right metric for sizing: it determines whether the system can absorb your message volume.
Latency answers: how quickly does the system respond? For messaging, latency is the time from a producer sending a message to a consumer receiving it. Latency is the right metric for SLA compliance: it determines whether the system meets your business requirements for message delivery speed.
The relationship: at low load, latency is low and stable. As load increases toward capacity, latency begins to rise. At saturation, latency increases without bound: the queue is growing faster than it is being consumed. The throughput at which latency begins to exceed your SLA is the system’s usable capacity.
Latency percentiles matter more than averages: an average latency of 5ms that includes occasional 2-second spikes is not a 5ms system. It is a system with occasional 2-second failures. p99 latency represents what 99% of your messages experience. p999 (99.9th percentile) represents what 0.1% experience, one message per thousand. In a system processing 10,000 messages/second, p999 = the worst latency every second.
The Apache Artemis™ perf CLI latency fields:
- send ack time: time from producer send() to broker acknowledgment, which measures broker processing speed
- transfer time: time from producer send() to consumer receipt, the end-to-end delivery latency and the metric most relevant to business SLAs
Coordinated Omission: The Most Insidious Benchmark Error
Coordinated omission occurs when the load generator stops sending new requests while waiting for slow responses. This is the default behavior of most naive benchmark implementations. When the broker is slow (due to a GC pause, disk I/O spike, or KahaDB index operation), the load generator pauses. The messages that would have arrived during the slowdown but were never generated are absent from the latency distribution, producing artificially optimistic tail latency numbers.
The Apache Artemis™ blocked metric directly exposes this problem. A high blocked rate means the producer is waiting for the broker to acknowledge previous sends before sending new ones. The load generator is being naturally throttled by protocol flow control. This is coordinated omission in action.
How to avoid coordinated omission:
- Use rate-controlled load tests (–rate in the Apache Artemis™ perf CLI) rather than all-out maximum rate tests. Rate-controlled tests maintain a consistent send rate regardless of response time, so slow responses accumulate appropriately in the latency distribution.
- Use HdrHistogram-compatible tools when measuring latency manually. HdrHistogram (hdrhistogram.org) is designed specifically to record full latency distributions, including the tail that coordinated omission hides.
- Monitor blocked during Apache Artemis™ tests: if it exceeds 10% of sent, the load target is above the sustainable rate.
We covered producer flow control blocking in depth in our Producer Flow Control Troubleshooting post. The same mechanism that causes PFC in production also creates coordinated omission in benchmarks.
Need a Professional Performance Baseline for Your ActiveMQ Deployment?
Monitoring During Benchmarks
Benchmark results without concurrent broker monitoring cannot be interpreted reliably. If throughput was 3,000 msg/s, was that the broker’s capacity or the rate limited by an undetected PFC event? If p99 was 50ms, was that broker processing latency or GC pause latency?
Essential metrics to capture during every benchmark run:
| # Prometheus queries — run these during benchmark, not just before/after # Memory: watch for PFC trigger approach activemq_memory_percent_usage{broker=”prod-broker”} # Store: KahaDB disk usage activemq_store_percent_usage{broker=”prod-broker”} # AverageEnqueueTime: broker-side queue processing latency (leading indicator) activemq_average_enqueue_time{broker=”prod-broker”, destination=”BENCH_QUEUE”} # Connection count stability activemq_total_connections{broker=”prod-broker”} # JVM heap (in GC log, or via JMX) # HeapMemoryUsage.used / HeapMemoryUsage.max |
If MemoryPercentUsage exceeds 70% at any point during the benchmark, the results are invalid for capacity planning. The broker was under resource pressure that would not exist in a properly sized production deployment. Either reduce the load target or increase the broker’s heap and systemUsage limits to match production sizing.
We covered the full monitoring and alerting configuration, including Prometheus PromQL queries, in our Monitoring & Alerting Setup post.
Building a Repeatable Benchmark Suite
A benchmark run is only valuable if it is repeatable. Results that cannot be reproduced cannot be used to validate improvements, detect regressions, or serve as a capacity baseline.
Repeatability Requirements
- Infrastructure as code: document the exact broker configuration (activemq.xml with all tuning), JVM options, OS settings (ulimit values, disk scheduler), and hardware profile used for the benchmark. Version-control this alongside your broker configuration.
- Warmup protocol: define a fixed warmup procedure: exact duration, load rate, and the signal that indicates warmup is complete. For the Apache Artemis™ perf CLI, the –warmup flag handles this. For the Apache ActiveMQ® Maven plugin, note that the first minute of output should be discarded.
- Benchmark duration: use a fixed, sufficiently long measurement window. 5-minute minimum for throughput; 30-minute for latency at multiple percentiles; 4-hour minimum for soak.
- Environment isolation checklist:
□ Broker host has no other significant processes running
□ Load generator hosts have no other significant processes running
□ Network path between load generators and broker is dedicated (not shared)
□ Benchmark is not running during scheduled backup or batch jobs
□ Operating system page cache has been warmed (pre-run or discard first results)
□ KahaDB has been pre-populated with at least one journal file worth of messages
before measurement (to avoid cold-start journal pre-allocation affecting results)
- Results recording template:
| Benchmark Run Report ───────────────────────────────────────────────────── Date: 2026-04-04 Broker version: 5.18.3 / Artemis 2.31.0 JVM flags: [exact ACTIVEMQ_OPTS / JAVA_ARGS] Broker host: [CPU, RAM, disk type] Message size: 1024 bytes Persistence: persistent / non-persistent Scenario: Scenario 2 — Sustainable Throughput with Latency Target rate: 5,000 msg/s Warmup: 120 seconds Measurement duration: 600 seconds Results: Throughput achieved: 5,023 msg/s (±47 msg/s across measurement window) p50 latency: 1.1ms p90 latency: 1.4ms p99 latency: 2.1ms p999 latency: 8.3ms Max latency: 47ms (1 event during GC pause) blocked %: 3.2% of sends MemoryPercentUsage peak: 43% StorePercentUsage peak: 12% Full GC events: 0 Pass/Fail against SLA: p99 < 50ms: PASS Throughput > 4,500 msg/s: PASS ───────────────────────────────────────────────────── |
CI/CD Integration
For teams practicing continuous delivery on broker infrastructure, adding benchmark regression tests to CI/CD pipelines catches performance degradation before it reaches production. A simple gate: after any broker configuration change, run the 5-minute Scenario 2 test and fail the pipeline if p99 latency increases by > 20% or throughput decreases by > 10% compared to the established baseline.
Baseline Your ActiveMQ Performance, Then Monitor Against It
Message Size Effect on Throughput Reference
Message size is the single variable most often omitted from benchmark reports. This reference table shows how throughput scales with message size on a typical production broker (persistent, SSD storage, 10 producers, 10 consumers):
| Message Size | Approx. Throughput | Bottleneck |
|---|---|---|
| 64 bytes | 8,000–15,000 msg/s | CPU (many small messages) |
| 256 bytes | 5,000–10,000 msg/s | CPU + disk write frequency |
| 1 KB | 2,000–6,000 msg/s | Disk sync writes |
| 4 KB | 800–2,500 msg/s | Disk bandwidth |
| 16 KB | 200–600 msg/s | Disk bandwidth |
| 64 KB | 50–150 msg/s | Disk bandwidth + network |
| 256 KB | 10–40 msg/s | Network + disk |
These figures from ActiveMQ community benchmarks and the capacity planning framework from our Capacity Planning post demonstrate that throughput varies by an order of magnitude across the typical enterprise message size range. Always benchmark with your actual production message sizes. If you have a distribution of sizes, benchmark each tier separately.
Measure What Matters, Trust What You Measure
A throughput number without latency percentiles, without a warmup phase, without isolation from load-generator competition, and without ongoing monitoring of PFC and GC events is not a performance measurement. It is the result of an experiment that was not designed to answer the question you think it answered.
The methodology in this guide (isolated hosts, proper warmup, latency percentiles at multiple load targets, soak testing, and documented repeatable conditions) produces ActiveMQ performance benchmarks that you can confidently use for capacity planning, upgrade validation, and SLA commitments.
Need a professional performance assessment for your ActiveMQ deployment? → Request a Performance Assessment
Frequently Asked Questions
Q: How do I benchmark ActiveMQ throughput?
Apache ActiveMQ®: use mvn activemq-perf:producer with sysTest.numClients, producer.messageSize, and producer.deliveryMode configured. Apache Artemis™: use ./artemis perf client. Run producer and consumer on separate hosts from the broker. Warm up for 60–120 seconds before collecting measurements.
Q: What is the difference between throughput vs latency in ActiveMQ benchmarks?
Throughput: messages per second (capacity metric). Latency: time from send to receive (responsiveness metric). The throughput at which p99 latency exceeds your SLA is your actual usable capacity, not maximum throughput.
Q: What is coordinated omission and why does it matter?
When the load generator pauses while waiting for slow responses, it never generates the messages that would have arrived during the slowdown. This produces artificially good latency numbers. The Apache Artemis™ blocked metric directly indicates coordinated omission. Keep blocked below 10% of sent for valid measurements.
Q: How long should an ActiveMQ performance benchmark run?
JVM warmup: 60–120 seconds. Steady-state measurement: 5–30 minutes. Soak test: 4–8 hours. A 5-minute throughput test cannot reveal GC degradation, journal growth, or memory leaks that only emerge over time.
Q: Why do my ActiveMQ benchmark results vary between runs?
Primary causes: JIT compilation state, KahaDB journal cold-start, GC heap fill cycle phase, and competing processes on the benchmark host. Fix with consistent warmup, fixed heap (-Xms = -Xmx), isolated hosts, and pre-warmed KahaDB.