---
title: "ActiveMQ Capacity Planning: The Complete Framework"
date: 2026-07-14
author: "TheFrameGuy"
featured_image: "https://www.meshiq.com/wp-content/uploads/blog_activeMQ-capacity-planning_071426.jpg"
categories:
  - name: "Apache ActiveMQ®"
    url: "/sort-by/active-mq.md"
  - name: "Devops"
    url: "/sort-by/devops.md"
  - name: "General"
    url: "/sort-by/general.md"
  - name: "Messaging"
    url: "/sort-by/messaging.md"
  - name: "Middleware"
    url: "/sort-by/middleware.md"
  - name: "Monitoring"
    url: "/sort-by/monitoring.md"
  - name: "MQ"
    url: "/sort-by/mq.md"
  - name: "Observability"
    url: "/sort-by/observability.md"
  - name: "SaaS"
    url: "/sort-by/saas.md"
tags:
  - name: "devops"
    url: "/sort-by/tag/devops.md"
  - name: "middleware"
    url: "/sort-by/tag/middleware.md"
  - name: "monitoring"
    url: "/sort-by/tag/monitoring.md"
  - name: "Observability"
    url: "/sort-by/tag/observability.md"
---

# ActiveMQ Capacity Planning: The Complete Framework

This guide provides the **ActiveMQ capacity planning framework**: the five workload dimensions that determine broker sizing, specific formulas for JVM heap, disk capacity, and IOPS requirements, connection and destination scaling limits, the built-in benchmark tool for validating storage, capacity trigger thresholds that define when expansion is needed, and a horizontal vs. vertical scaling decision framework.

## **The Five Capacity Dimensions**

ActiveMQ broker capacity is determined by five workload dimensions. Before sizing any resource, such as heap, disk, CPU, or connections, these five dimensions must be characterized:

### **Dimension 1: Message Throughput**

Throughput is measured in messages per second (msg/s) at peak load. The critical qualifier is the message payload size – 10,000 msg/s of 100-byte messages is a fundamentally different resource profile from 10,000 msg/s of 10KB messages.

**Required inputs**:

- Peak messages per second (not average, size for peak)
- Average message payload size in bytes
- Persistence mode: persistent (default) or non-persistent

**Why persistence mode dominates throughput**: persistent messages require the broker to write the message to the KahaDB journal and receive a filesystem sync confirmation before acknowledging to the producer. For non-persistent messages, no fsync occurs, the message is written to memory and acknowledged immediately. The throughput difference is typically 5-20×:

**Message Mode****Typical Throughput****Bottleneck**Non-persistent, async send20,000–50,000+ msg/sCPU, network bandwidthNon-persistent, sync send10,000–20,000 msg/sNetwork round-tripPersistent on NVMe SSD5,000–15,000 msg/sDisk sync writesPersistent on SATA SSD2,000–8,000 msg/sDisk sync writesPersistent on spinning HDD200–2,000 msg/sDisk seek + syncPersistent on NFS mount76–500 msg/sNetwork filesystem syncThese figures from documented benchmarks (Red Hat Engineering, JBoss A-MQ performance testing) illustrate why the storage subsystem must be validated before any other sizing decision.

### **Dimension 2: Queue Depth (Peak In-Flight Messages)**

Queue depth represents the maximum number of messages that will be simultaneously held in the broker’s persistent store, waiting for consumption. This is distinct from throughput, a broker handling 10,000 msg/s with consumers keeping up has near-zero queue depth; a broker with a slow consumer may accumulate millions of messages.

**Queue depth drives disk capacity**: each message persisted to KahaDB occupies journal storage. A 1 million-message backlog of 1KB messages requires approximately 1GB of journal storage plus index overhead.

**Queue depth drives JVM heap**: messages that are ready for dispatch to consumers are held in memory by the broker’s message cursor. When queue depth exceeds the cursorMemoryHighWaterMark (70% of memoryUsage by default), the broker pages messages from memory to disk, but this itself requires disk I/O and consumes tempUsage.

### **Dimension 3: Connection Count**

Each client connection (producer or consumer) to the broker consumes:

- A file descriptor from the OS’s file descriptor table
- Approximately 1-2MB of JVM heap for connection state (session, producers, consumers, transaction state)
- Thread stack memory (if UseDedicatedTaskRunner=true)

**OS file descriptor limit**: the default ulimit -n on many Linux systems is 1,024. For a broker with 500 connections and standard OS tools consuming file descriptors, this is insufficient. Set ulimit -n to at least 4× the expected peak connection count.

**Connection heap footprint**: 1,000 connections × 1.5MB average = 1.5GB of heap consumed by connection state alone, before any messages are in the system.

### **Dimension 4: Destination Count**

Each destination (queue or topic) in Apache ActiveMQ® creates:

- An in-memory dispatcher and message cursor
- Memory tracking objects reporting to the broker’s SystemUsage
- A dedicated thread (if UseDedicatedTaskRunner=true)
- Advisory topic subscriptions (if advisorySupport=true)

At scale, destination count becomes a significant heap consumer. A broker with 10,000 destinations and UseDedicatedTaskRunner=true creates 10,000 threads, each consuming thread stack memory from the OS’s virtual address space, independent of the JVM heap.

### **Dimension 5: Persistence Ratio**

The ratio of persistent to non-persistent messages in your workload determines the disk I/O profile. For a workload that is 100% persistent, the disk becomes the primary bottleneck. For a workload that is 90% non-persistent, heap and CPU dominate.

## **Memory Sizing: The Formula**

### **JVM Heap Sizing**

JVM heap (-Xmx) = max( peak\_in\_flight\_messages × avg\_message\_size\_bytes × 1.5, indexCacheSize\_footprint) ÷ 0.70

The ÷ 0.70 accounts for memoryUsage percentOfJvmHeap=”70″, the broker uses 70% of the JVM heap for message buffering; the remaining 30% is JVM overhead (Metaspace, GC, thread stacks). This means the heap must be sized so that 70% of it accommodates the peak in-flight message volume.

**Example calculation**:

- Peak in-flight: 500,000 messages
- Average size: 2KB (2,048 bytes)
- Peak in-memory footprint: 500,000 × 2,048 = 1,024MB = 1GB
- With 1.5× safety factor: 1.5GB
- With ÷ 0.70: 2.14GB → round to 4GB (-Xmx4g)

KahaDB indexCacheSize footprint: with the default 10,000 entries and an average index entry of 20-50KB, the index cache can consume 200MB-500MB of heap. For brokers with many destinations and large persistent message volumes, this cache is a significant heap consumer (covered in our **[JVM Memory &amp; GC Tuning](https://www.meshiq.com/blog/activemq-jvm-memory-gc-tuning/)** post as a specific OOM root cause).

### **Apache Artemis™ Heap Sizing**

Apache Artemis™ documentation provides an explicit formula: -Xmx should be at least 5× global-max-size. If global-max-size=1GB (the Artemis limit for all addresses combined), then -Xmx should be at least 5GB. The multiplier accounts for GC overhead under high load, with many objects being allocated and collected, the heap needs significant headroom above the working set to avoid continuous full GC.

&lt;!– broker.xml — Artemis global memory limit aligned with JVM heap –&gt;&lt;configuration&gt; &lt;core&gt; &lt;!– All addresses combined cannot exceed 2GB in-memory –&gt; &lt;global-max-size&gt;2147483648&lt;/global-max-size&gt; &lt;!– -Xmx should be at least 5x = 10GB –&gt; &lt;/core&gt;&lt;/configuration&gt;

### **systemUsage Configuration Aligned with Heap**

Once the heap is sized, configure memoryUsage in activemq.xml to match:

&lt;!– activemq.xml — systemUsage aligned with -Xmx4g JVM heap –&gt;&lt;systemUsage&gt; &lt;systemUsage&gt; &lt;!– 70% of 4GB heap = 2.8GB for message buffering –&gt; &lt;memoryUsage&gt; &lt;memoryUsage percentOfJvmHeap=”70″/&gt; &lt;/memoryUsage&gt;  
 &lt;!– storeUsage: 80% of available disk space –&gt; &lt;!– On 500GB disk: 400GB limit –&gt; &lt;storeUsage&gt; &lt;storeUsage limit=”400 gb”/&gt; &lt;/storeUsage&gt;  
 &lt;!– tempUsage: for non-persistent message spooling –&gt; &lt;!– Size &gt;= 2x journalMaxFileLength minimum –&gt; &lt;tempUsage&gt; &lt;tempUsage limit=”50 gb”/&gt; &lt;/tempUsage&gt; &lt;/systemUsage&gt;&lt;/systemUsage&gt;

We covered the interactions between JVM heap, memoryUsage, and producer flow control in our **[Producer Flow Control Troubleshooting](https://www.meshiq.com/blog/activemq-producer-flow-control-troubleshooting/)** post. The key connection: memoryUsage is the capacity trigger for PFC. Sizing the heap correctly ensures PFC triggers at the right threshold and not prematurely due to an undersized memoryUsage limit.

## **Disk Sizing: IOPS is the Critical Variable**

### **Why Sync Write Speed Determines Persistent Throughput**

KahaDB with enableJournalDiskSyncs=true (the default) issues an fsync() call after writing each journal record batch. The broker cannot acknowledge a persistent message to the producer until the fsync completes. This means persistent message throughput is fundamentally limited by the number of fsync operations per second the storage system can perform.

A JBoss A-MQ performance case study documented this precisely: on a GFS2 filesystem over a SAN, the sync write speed was 9.7 MB/sec, producing only 76 transactions per second. After switching to preallocation with zeros and optimizing the filesystem, performance improved to approximately 2,000 TPS on the same hardware – a 25× improvement without changing hardware.

### **Measuring Storage Capacity: ActiveMQ DiskBenchmark**

Apache ActiveMQ® ships with a built-in disk benchmark tool that measures the actual sync write throughput of your storage system from Java (which uses the same Java NIO APIs as KahaDB):

\# Run the DiskBenchmark before sizing any persistent messaging workload  
\# This measures actual KahaDB-equivalent write performance on your storage  
  
java -cp /opt/activemq/lib/optional/kahadb-\*.jar \\ org.apache.activemq.store.kahadb.disk.util.DiskBenchmark \\   
–file /opt/activemq/data/kahadb/disk-benchmark.dat \\   
–bs 4096 \\   
–count 50000 \\   
–verbose  
  
\# Sample output to evaluate:  
\# Sync Writes: 2485 writes/second at 9.7 MB/sec ← CRITICAL BOTTLENECK  
\# Reads: 853901 reads/second at 3335 MB/sec  
  
\# For persistent messaging: you need Sync Writes &gt;&gt; (target\_msg\_per\_sec)  
\# Rule: Sync Writes/sec should be &gt;= 2x target persistent msg/s

**Interpreting results**:

**Sync Writes/sec****Storage Type****Persistent Throughput Ceiling**&lt; 100Spinning HDD, NFS, network SAN&lt; 100 msg/s – unacceptable for production100–1,000SATA SSD, good SAN50–500 msg/s – acceptable for low-volume workloads1,000–5,000NVMe SSD500–2,500 msg/s – typical production range5,000–20,000Local NVMe, enterprise SSD2,500–10,000 msg/s – high-throughput range&gt; 20,000Ramdisk, enterprise NVMe&gt; 10,000 msg/s – very high throughput

### **Disk Capacity Sizing Formula**

Disk required = (peak\_queue\_depth × avg\_message\_size × 1.5)

\+ (journal\_overhead)  
\+ (backup\_storage if on same volume)

Journal overhead = journalMaxFileLength (32MB default) × active\_journal\_count  
 ≈ 32MB × 10 = 320MB minimum for active journals

**Practical rule**: provision at least 3× the daily message volume in bytes as disk capacity, with a minimum of 100GB for a production broker. Set storeUsage limit to 80% of available disk, leaving 20% for journal overhead and operational margin.

**Disk type recommendation**:

- Persistent messaging production: NVMe or enterprise SSD — spinning disk is a bottleneck, not a cost optimization
- Non-persistent messaging or KahaDB on Kubernetes: SSD StorageClass with ReadWriteOnce (see our **[Kubernetes Deployment](https://www.meshiq.com/blog/activemq-kubernetes-deployment/)** post for StorageClass selection)
- Never: NFS-backed storage for KahaDB with enableJournalDiskSyncs=true, the network filesystem latency for fsync is prohibitive

## ******Getting the Sizing Right Before It Becomes an Incident******

Capacity planning errors don’t announce themselves, they manifest as PFC events, OOM crashes, and connection rejections under peak load. meshIQ’s team reviews ActiveMQ deployments and can assess whether your current broker is sized for your current load, your projected growth, and your SLA requirements.

[********Request a Capacity Review********](https://www.meshiq.com/apache-activemq/enterprise-support/)



## **Connection and Destination Scaling**

### **Connection Limits Configuration**

&lt;!– activemq.xml — connection limit configuration –&gt;  
&lt;transportConnectors&gt;  
 &lt;!– NIO transport: more efficient for high connection counts than TCP –&gt;  
 &lt;!– NIO uses thread pools rather than dedicated threads per connection –&gt;  
 &lt;transportConnector name=”openwire”  
 uri=”nio://0.0.0.0:61616   
 ?maximumConnections=2000   
 &amp;amp;wireFormat.maxFrameSize=104857600″/&gt;  
&lt;/transportConnectors&gt;

**OS-level file descriptor configuration**:

\# /etc/security/limits.conf (Linux) — set before broker startup  
activemq soft nofile 65536  
activemq hard nofile 65536

\# Verify the limit is applied to the broker process  
cat /proc/$(pgrep -f activemq)/limits | grep “open files”

**Connection memory budget**:

**Target Connection Count****JVM Heap for Connections****Minimum -Xmx**100 connections150-200MB2GB500 connections750MB-1GB4GB1,000 connections1.5-2GB6GB2,000 connections3-4GB8GB5,000 connections7-10GB16GBThese estimates assume 1.5MB average connection state per connection. Use NIO transport for high connection counts, NIO uses a shared thread pool rather than one thread per connection, dramatically reducing thread stack memory overhead.

### **Destination Count Scaling**

Destination count scaling is less commonly planned for, but becomes critical at enterprise scale. With UseDedicatedTaskRunner=true (Apache ActiveMQ® default), every destination holds a dedicated thread for message dispatch. At 10,000 destinations:

- 10,000 threads × ~512KB default stack = ~5GB of native memory for thread stacks alone
- Additionally: 10,000 advisory topics (if advisorySupport=true), each with its own subscription overhead

**Recommendations for high destination count brokers**:

\# In ACTIVEMQ\_OPTS — switch from per-destination threads to thread pool  
-Dorg.apache.activemq.UseDedicatedTaskRunner=false

&lt;!– activemq.xml — disable advisory support for very high destination counts –&gt;  
&lt;!– Advisory messages are required for dynamic NoB, only disable on standalone brokers –&gt;  
&lt;broker advisorySupport=”false” …&gt;

With UseDedicatedTaskRunner=false, Apache ActiveMQ® uses a shared thread pool for all destination dispatching. The performance impact is minimal for most workloads, dispatch is already largely asynchronous. The memory impact is significant: from O(destinations) threads to O(1) thread pool.

**Destination count reference**:

**Destination Count****Configuration Required**&lt; 100Default configuration adequate100–1,000Monitor heap; consider UseDedicatedTaskRunner=false1,000–10,000UseDedicatedTaskRunner=false required; monitor thread count&gt; 10,000Consider mKahaDB sharding; advisorySupport=false for standalone; monitor OS thread limits

## **Network Bandwidth Sizing**

Network bandwidth is rarely the primary bottleneck for ActiveMQ, but it can become so for high-volume, large-message workloads. The formula is straightforward:

Required bandwidth (Mbps) = (peak\_msg\_per\_sec × avg\_message\_size\_bytes × 8)  
 × protocol\_overhead\_factor  
 ÷ 1,000,000

OpenWire protocol overhead factor: ~1.05 (5% overhead — binary, efficient)  
AMQP protocol overhead factor: ~1.10 (10% overhead — AMQP framing)  
STOMP protocol overhead factor: ~1.20 (20% overhead — text encoding)

**Example**: 5,000 msg/s × 2KB messages × 8 bits × 1.05 OpenWire factor = 84 Mbps. A standard 1Gbps network interface provides ~12× headroom.

Where bandwidth becomes a concern: large messages (&gt;64KB) at high throughput, or a Network of Brokers topology where bridge connections carry the full combined message volume of all connected brokers.

## **Capacity Trigger Thresholds**

Capacity planning is not a one-time event, it is an ongoing process of monitoring capacity signals and acting before thresholds are crossed. The following thresholds, configured as alerts in your monitoring system (see our [**Monitoring &amp; Alerting Setup**](https://www.meshiq.com/blog/activemq-monitoring-alerting-setup/) post), define when capacity expansion is needed:

**Metric****Warning Threshold****Critical Threshold****Meaning**MemoryPercentUsage&gt; 70%&gt; 85%Approaching PFC trigger (100%)StorePercentUsage&gt; 70%&gt; 85%Approaching disk-PFC trigger (100%)TempPercentUsage&gt; 70%&gt; 85%Non-persistent spooling pressureTotalConnectionsCount&gt; 80% of maximumConnections&gt; 90%Connection capacity approachingAverageEnqueueTime trendIncreasing 7-day trend&gt; 2× baselineThroughput capacity is being saturatedQueueSize sustained growthGrowing for 24 hoursGrowing for 72 hoursConsumer capacity insufficient

**Warning → Critical response**: When a metric crosses the Warning threshold, the action is to plan expansion and schedule it within the next maintenance window. When a Critical threshold is crossed, the response must be immediate. A critical threshold means you are one load spike away from a service-affecting event.

## **Vertical vs. Horizontal Scaling Decision Framework**

### **Vertical Scaling: More Resources on the Same Broker**

Add CPU, RAM, faster disk, or network bandwidth to the existing broker.

**When to choose vertical scaling**:

- The bottleneck is a single resource (disk IOPS, heap, connections) that can be addressed by a hardware upgrade
- Horizontal scaling would require a significant configuration change (Network of Brokers setup, client-side connection distribution logic)
- The workload is not yet at a scale where a single broker creates a reliability risk

**Vertical scaling limits**:

- JVM heap: practical ceiling is ~30-32GB for G1GC (beyond this, GC pause times grow significantly). Consider ZGC for heaps &gt; 32GB.
- Disk: NVMe SSDs deliver ~3-4GB/s sequential write and ~500,000 IOPS, this is rarely the ceiling
- Connections: NIO transport scales to ~5,000-10,000 connections on a 16GB heap broker

### **Horizontal Scaling: Multiple Brokers**

Add additional broker nodes via Network of Brokers (Apache ActiveMQ®) or Apache Artemis™ clustering.

**When to choose horizontal scaling**:

- Message throughput exceeds what a single broker can handle on available hardware
- Connection count exceeds what a single broker can accommodate on the available heap
- Business continuity requires geographic distribution of message processing
- Different queues/topics have very different throughput/latency characteristics that benefit from isolation

**Horizontal scaling options**:

- Apache ActiveMQ® Network of Brokers: store-and-forward mesh; good for geographic distribution and load isolation
- Apache Artemis™ clustering: symmetric cluster with shared store or replication; good for HA with load distribution
- mKahaDB destination sharding: distribute high-volume destinations across separate KahaDB instances on the same broker, a middle ground between vertical and horizontal

We covered Network of Brokers configuration and topology patterns in our [**Network of Brokers Configuration**](https://www.meshiq.com/blog/activemq-network-of-brokers-configuration/) post, and HA/clustering architecture in our [**High Availability Architecture Guide** ](https://www.meshiq.com/blog/activemq-high-availability-architecture/).

## ******Continuous Capacity Visibility Across Your ActiveMQ Fleet******

meshIQ Console tracks MemoryPercentUsage, StorePercentUsage, AverageEnqueueTime, connection counts, and queue depth trends across all brokers, providing the continuous capacity intelligence that turns capacity planning from a periodic event into an ongoing operational posture.

[********See It in Action********](https://www.meshiq.com/request-a-demo/)



## **Capacity Planning Worksheet**

Use this worksheet to estimate broker sizing for a new deployment or to validate existing sizing:

**Step 1: Characterize the workload**

**Variable****Your Value****Notes**Peak messages/second\_\_\_\_\_Use peak, not averageAverage message size (bytes)\_\_\_\_\_Include headers (~500B overhead)Persistent percentage (%)\_\_\_\_\_JMS default is 100%Maximum queue depth (messages)\_\_\_\_\_Max backlog during consumer lagPeak connection count\_\_\_\_\_Producers + consumersDestination count\_\_\_\_\_Active queues + topics**Step 2: Calculate resource requirements**

**Resource****Formula****Example (5,000 msg/s, 2KB, 50% persistent)**Required disk sync writes/secpeak\_persistent\_msg/s2,500 writes/secRequired JVM heappeak\_queue\_depth × avg\_size × 1.5 ÷ 0.70500K × 2KB × 1.5 ÷ 0.70 = ~4GBConnection heappeak\_connections × 1.5MB1,000 × 1.5MB = 1.5GBTotal JVM heapheap + connections + 30% overhead4GB + 1.5GB × 1.3 = ~7GB → **8GB**Disk capacitymax\_queue\_depth × avg\_size × 3500K × 2KB × 3 = 3GB minimumNetwork bandwidthpeak\_msg/s × avg\_size × 8 × 1.05 ÷ 1,000,0005,000 × 2048 × 8 × 1.05 ÷ 1M = 86 Mbps**Step 3: Validate storage**

Run DiskBenchmark and confirm sync writes/sec ≥ 2× peak persistent messages/second.

**Step 4: Size the system**

**Resource****Calculated Need****Recommended Provision (with headroom)**JVM heap8GB8GB (50% of host RAM on 16GB host)Disk sync writes/sec2,500SSD delivering 5,000+ sync writes/secDisk capacity3GB minimum200GB (operational headroom + backups)Network86 Mbps1Gbps (sufficient)maximumConnections1,0002,000 (with growth headroom)## **Size for Peak, Monitor for the Trigger**

Capacity planning errors fall into two categories: sizing for average load (which produces incidents at peak) and sizing without monitoring (which produces surprises). The framework in this guide addresses both: size for peak load using the formulas, then configure capacity trigger thresholds in your monitoring system to alert before the next peak exhausts the headroom you built in.

The DiskBenchmark tool is the step that most organizations skip and most regret. A broker that looks correctly sized on paper can be limited to 76 messages/second by a filesystem that cannot deliver adequate sync write throughput. Measure the storage first — everything else follows from that constraint.

**Get your ActiveMQ deployment’s capacity profile reviewed by our team → [Request a Capacity Review](https://www.meshiq.com/apache-activemq/enterprise-support/)**

## **Frequently Asked Questions**

**Q: How many messages per second can ActiveMQ handle?**

Non-persistent: 20,000–50,000+ msg/s. Persistent on NVMe SSD: 5,000–15,000 msg/s. Persistent on SATA SSD: 2,000–8,000 msg/s. Persistent on spinning disk or NFS: 76–2,000 msg/s. The bottleneck is always the disk sync write speed for persistent messages. Use DiskBenchmark to measure your actual storage before sizing.

**Q: How much memory does ActiveMQ need?**

Formula: -Xmx ≥ (peak in-flight messages × avg size × 1.5) ÷ 0.70 + connection heap (connections × 1.5MB). For Artemis: -Xmx ≥ 5× global-max-size. Start with 2–4GB for typical workloads, 8GB+ for high-volume or high-connection deployments.

**Q: How much disk space does ActiveMQ need?**

Formula: max\_queue\_depth × avg\_message\_size × 3 for operational storage, plus KahaDB journal overhead (~320MB minimum). Provision at least 3× daily message volume as disk capacity with a minimum of 100GB. Set storeUsage limit to 80% of available disk.

**Q: What are the connection limits for ActiveMQ?**

No hard system limit, practical ceiling is file descriptors (ulimit -n, default 1024 — increase to 4× expected connections) and JVM heap (1–2MB per connection). Use NIO transport for &gt;500 connections. Set maximumConnections on each transport connector to prevent unbounded growth.

**Q: How do I know when ActiveMQ needs more capacity?**

Monitor five capacity triggers: MemoryPercentUsage &gt; 70% (warning), StorePercentUsage &gt; 70%, connection count &gt; 80% of maximumConnections, AverageEnqueueTime trending upward, and QueueSize sustained growth. Any metric crossing the warning threshold means expansion should be planned for the next maintenance window.