Friday, January 4, 2013

JmsTemplate is not evil

A while back, I watched a presentation on JMS messaging where the presenter (Mark Richards) declared that Spring’s JmsTemplate is “evil,” and he had the benchmarks to prove it. Since Mark was kind enough to provide the source code (based on ActiveMQ), I decided to dig a little deeper to determine the cause. It turns out that the apparent poor performance of JmsTemplate was not due to anything intrinsic to this class, and you can indeed get message sending performance with it comparable to using the standard JMS API. It’s not evil. Really.

Read on if you want the details.

The issue was that using JmsTemplate to send messages was much, much slower than using the normal JMS calls. That is, instead of doing the following in plain JMS:

QueueConnection connection = ...;
QueueSession session = connection.createQueueSession(...);
QueueSender sender = session.createSender(session.createQueue(...));
for (int i = 0; i < 1000; i++) {
    sender.send(session.createTextMessage("blah"));
}

You could do something like:

JmsTemplate jmsTemplate = ...;
for (int i = 0; i < 1000; i++) {
    jmsTemplate.convertAndSend("blah");
}

JmsTemplate is pretty convenient in that it eliminates a lot of setup and boilerplate code, but seemed to come with a heavy performance penalty. Having read this far, your first inclination is probably to slap me on the head and tell me to use Spring’s CachingConnectionFactory. And indeed I should: if you’re not using a JCA ConnectionFactory, each call to JmsTemplate.convertAndSend will create a new connection, session and message producer, and discard them. If you wrap a JMS ConnectionFactory in a CachingConnectionFactory, then the latter will cache all those resources so they don’t get created 1000 times. This optimization is well documented, but the problem is that even with this optimization JmsTemplate is slower. How much slower? Here are my execution times to send 1000 text messages over ActiveMQ, using JmsTemplate from Spring Framework 3.2.0:

Plain JMS: 702 ms
JmsTemplate using a plain ConnectionFactory: 13962 ms
JmsTemplate using CachingConnectionFactory: 3229 ms

As you can see, CachingConnectionFactory does indeed speed things up, but plain JMS is still much faster. Naturally, the caching logic does have some overhead but I was surprised that the overhead was that high. So I put the code through the profiler to try and figure out the cause. In the process, I noticed a curious behavior: with the vanilla JMS code, the messages were being sent using ActiveMQConnection.asyncSendPacket. With JmsTemplate, the messages were being sent using ActiveMQConnection.syncSendPacket.

Here is the smoking gun. ActiveMQ sends messages in async mode by default. But for whatever under JmsTemplate ActiveMQ uses sync sending, which means it waits for the broker to return an acknowledgement before completing the send. This resulted in the dramatic performance difference. You might actually want to use sync sending for a stronger delivery guarantee. But if you don’t, you can make JmsTemplate use async sending too:

<bean id="cf" class="org.apache.activemq.ActiveMQConnectionFactory"
    p:brokerURL="tcp://localhost:61616"
    p:useAsyncSend="true"/>

Setting the useAsyncSend property to true means JmsTemplate will use async sends for message delivery. The result is dramatically better performance:

JmsTemplate using CachingConnectionFactory, async sends: 735 ms

In other words, the performance difference between using ordinary JMS code and JmsTemplate is negligible. The earlier poor performance was due to a difference in default sending mode (async vs sync) specific to ActiveMQ rather than anything intrinsic to JmsTemplate. If you like JmsTemplate feel free to continue using it without guilt. You have my blessing.

6 comments:

  1. Thanks!
    Lifesaver!

    ReplyDelete
  2. Do you have an idea how sync througput (with msg received ack) performs on other broker middleware eg. Rabbitmq in comparison.Making it async may drop messages which is not reliable

    ReplyDelete
    Replies
    1. Sorry, but these benchmarks are specific to ActiveMQ. Obviously there is a price to pay for a stronger delivery guarantee, but I don't have the benchmarks to quantify them. It's up to you to run the benchmarks, and decide whether you can design your application to tolerate a slightly larger number of delivery failures that async sends would imply.

      Delete
  3. Can the same be done for IBM Websphere MQ?

    ReplyDelete