Amazon MQ for ActiveMQ best practices
Use this as a reference to quickly find recommendations for maximizing performance and minimizing throughput costs when working with ActiveMQ brokers on Amazon MQ.
Never Modify or Delete the Amazon MQ Elastic Network Interface
When you first create an Amazon MQ broker, Amazon MQ provisions an elastic network interface in the Virtual Private Cloud (VPC) under your account and, thus, requires a number of EC2 permissions. The network interface allows your client (producer or consumer) to communicate with the Amazon MQ broker. The network interface is considered to be within the service scope of Amazon MQ, despite being part of your account's VPC.
Warning
You must not modify or delete this network interface. Modifying or deleting the network interface can cause a permanent loss of connection between your VPC and your broker.
Always Use Connection Pooling
In a scenario with a single producer and single consumer (such as the Getting started: Creating and connecting to an ActiveMQ broker
tutorial), you can use a single ActiveMQConnectionFactory
// Create a connection factory.
final ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(wireLevelEndpoint);
// Pass the sign-in credentials.
connectionFactory.setUserName(activeMqUsername);
connectionFactory.setPassword(activeMqPassword);
// Establish a connection for the consumer.
final Connection consumerConnection = connectionFactory.createConnection();
consumerConnection.start();
However, in more realistic scenarios with multiple producers and consumers, it can
be costly and inefficient to create a large number of connections for multiple
producers. In these scenarios, you should group multiple producer requests using the
PooledConnectionFactory
Note
Message consumers should never use the
PooledConnectionFactory
class.
// Create a connection factory.
final ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(wireLevelEndpoint);
// Pass the sign-in credentials.
connectionFactory.setUserName(activeMqUsername);
connectionFactory.setPassword(activeMqPassword);
// Create a pooled connection factory.
final PooledConnectionFactory pooledConnectionFactory = new PooledConnectionFactory();
pooledConnectionFactory.setConnectionFactory(connectionFactory);
pooledConnectionFactory.setMaxConnections(10);
// Establish a connection for the producer.
final Connection producerConnection = pooledConnectionFactory.createConnection();
producerConnection.start();
Always Use the Failover Transport to Connect to Multiple Broker Endpoints
If you need your application to connect to multiple broker endpoints—for example,
when you use an active/standby deployment mode or when you migrate from an on-premises message broker to
Amazon MQ—use the Failover
Transport
failover:(ssl://b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9-1.mq.us-east-2.amazonaws.com:61617,ssl://b-9876l5k4-32ji-109h-8gfe-7d65c4b132a1-2.mq.us-east-2.amazonaws.com:61617)?randomize=true
Avoid Using Message Selectors
It is possible to use JMS
selectors
In general, avoid letting consumers route messages because, for optimal decoupling of consumers and producers, both the consumer and the producer should be ephemeral.
Prefer Virtual Destinations to Durable Subscriptions
A durable subscription
If using Amazon VPC peering, avoid client IPs in CIDR range 10.0.0.0/16
If you are setting up Amazon VPC peering between on-premise infrastructure and your Amazon MQ broker, you must not configure client connections with IPs in CIDR range 10.0.0.0/16
.
Disable Concurrent Store and Dispatch for Queues with Slow Consumers
By default, Amazon MQ optimizes for queues with fast consumers:
-
Consumers are considered fast if they are able to keep up with the rate of messages generated by producers.
-
Consumers are considered slow if a queue builds up a backlog of unacknowledged messages, potentially causing a decrease in producer throughput.
To instruct Amazon MQ to optimize for queues with slow consumers, set the
concurrentStoreAndDispatchQueues
attribute to false
.
For an example configuration, see concurrentStoreAndDispatchQueues.
Choose the Correct Broker Instance Type for the Best Throughput
The message throughput of a broker instance type depends on your application's use case and the following factors:
-
Use of ActiveMQ in persistent mode
-
Message size
-
The number of producers and consumers
-
The number of destinations
Understanding the relationship between message size, latency, and throughput
Depending on your use case, a larger broker instance type might not necessarily improve system throughput. When ActiveMQ writes messages to durable storage, the size of your messages determines your system's limiting factor:
-
If your messages are smaller than 100 KB, persistent storage latency is the limiting factor.
-
If your messages are larger than 100 KB, persistent storage throughput is the limiting factor.
When you use ActiveMQ in persistent mode, writing to storage normally occurs when there are either few consumers or when the consumers are slow. In non-persistent mode, writing to storage also occurs with slow consumers if the heap memory of the broker instance is full.
To determine the best broker instance type for your application, we recommend
testing different broker instance types. For more information, see Broker instance types and
also Measuring the Throughput for Amazon MQ using the JMS Benchmark
Use cases for larger broker instance types
There are three common use cases when larger broker instance types improve throughput:
-
Non-persistent mode – When your application is less sensitive to losing messages during broker instance failover (for example, when broadcasting sports scores), you can often use ActiveMQ's non-persistent mode. In this mode, ActiveMQ writes messages to persistent storage only if the heap memory of the broker instance is full. Systems that use non-persistent mode can benefit from the higher amount of memory, faster CPU, and faster network available on larger broker instance types.
-
Fast consumers – When active consumers are available and the concurrentStoreAndDispatchQueues flag is enabled, ActiveMQ allows messages to flow directly from producer to consumer without sending messages to storage (even in persistent mode). If your application can consume messages quickly (or if you can design your consumers to do this), your application can benefit from a larger broker instance type. To let your application consume messages more quickly, add consumer threads to your application instances or scale up your application instances vertically or horizontally.
-
Batched transactions – When you use persistent mode and send multiple messages per transaction, you can achieve an overall higher message throughput by using larger broker instance types. For more information, see Should I Use Transactions?
in the ActiveMQ documentation.
Choose the correct broker storage type for the best throughput
To take advantage of high durability and replication across multiple Availability Zones, use Amazon EFS. To take advantage of low latency and high throughput, use Amazon EBS. For more information, see Storage.
Configure Your Network of Brokers Correctly
When you create a network of brokers, configure it correctly for your application:
-
Enable persistent mode – Because (relative to its peers) each broker instance acts like a producer or a consumer, networks of brokers don't provide distributed replication of messages. The first broker that acts as a consumer receives a message and persists it to storage. This broker sends an acknowledgement to the producer and forwards the message to the next broker. When the second broker acknowledges the persistence of the message, the first broker deletes the message.
If persistent mode is disabled, the first broker acknowledges the producer without persisting the message to storage. For more information, see Replicated Message Store
and What is the difference between persistent and non-persistent delivery? in the Apache ActiveMQ documentation. -
Don't disable advisory messages for broker instances – For more information, see Advisory Message
in the Apache ActiveMQ documentation. -
Don't use multicast broker discovery – Amazon MQ doesn't support broker discovery using multicast. For more information, see What is the difference between discovery, multicast, and zeroconf?
in the Apache ActiveMQ documentation.
Avoid slow restarts by recovering prepared XA transactions
ActiveMQ supports distributed (XA) transactions. Knowing how ActiveMQ processes XA transactions can help avoid slow recovery times for broker restarts and failovers in Amazon MQ
Unresolved prepared XA transactions are replayed on every restart. If these remain
unresolved, their number will grow over time, significantly increasing the time needed
to start up the broker. This affects restart and failover time. You must resolve these
transactions with a commit()
or a rollback()
so that
performance doesn't degrade over time.
To monitor your unresolved prepared XA transactions, you can use the
JournalFilesForFastRecovery
metric in Amazon CloudWatch Logs. If this number is
increasing, or is consistently higher than 1
, you should recover your
unresolved transactions with code similar to the following example. For more
information, see Quotas in Amazon MQ.
The following example code walks through prepared XA transactions and closes them with
a rollback()
.
import org.apache.activemq.ActiveMQXAConnectionFactory; import javax.jms.XAConnection; import javax.jms.XASession; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; public class RecoverXaTransactions { private static final ActiveMQXAConnectionFactory ACTIVE_MQ_CONNECTION_FACTORY; final static String WIRE_LEVEL_ENDPOINT = "tcp://localhost:61616";; static { final String activeMqUsername = "
MyUsername123
"; final String activeMqPassword = "MyPassword456
"; ACTIVE_MQ_CONNECTION_FACTORY = new ActiveMQXAConnectionFactory(activeMqUsername, activeMqPassword, WIRE_LEVEL_ENDPOINT); ACTIVE_MQ_CONNECTION_FACTORY.setUserName(activeMqUsername); ACTIVE_MQ_CONNECTION_FACTORY.setPassword(activeMqPassword); } public static void main(String[] args) { try { final XAConnection connection = ACTIVE_MQ_CONNECTION_FACTORY.createXAConnection(); XASession xaSession = connection.createXASession(); XAResource xaRes = xaSession.getXAResource(); for (Xid id : xaRes.recover(XAResource.TMENDRSCAN)) { xaRes.rollback(id); } connection.close(); } catch (Exception e) { } } }
In a real-world scenario, you could check your prepared XA transactions against your
XA Transaction Manager. Then you can decide whether to handle each prepared transaction
with a rollback()
or a commit()
.