MQ slow? App keeps crashing? CPU unhappy?

The choices you make when you develop an app can make your life easier… or much harder.

In this article, you’ll learn three common traps that developers frequently fall into and how you can avoid them. The code for this example was written for the JMS 2.0 API, with our queue manager running in a Docker container. For more info on that, have a look at this tutorial. For more on the JMS 2.0 API with IBM MQ, have a look here.

Let’s get started!

MQ running slow? Check how you’re connecting

One very common pitfall appears when connecting to a queue manager to send messages. It’s easy to think that every message you send might need its own session and connection (together called a context in the JMS 2.0 specification), producer and destination objects… seems sensible? We don’t need to do this. We can reuse these objects to send multiple messages.

But if it works, it works, right? Not necessarily… let’s say you want to send 5000 messages to a queue. Creating 5000 sets of connection objects means that sending that many messages takes a long time.

Let’s have a look. This code shows part of a JMS 2.0 app for sending some messages with MQ.

for (int i = 1; i <= 5000; i++) {
    context = cf.createContext();
    destination = context.createQueue("queue:///" + QUEUE_NAME);
    TextMessage message = context.createTextMessage("This is message number " + i + ".\n");
    producer = context.createProducer();
    producer.send(destination, message);
    context.close();
}

The app puts the process of creating a new context (session and connection), producer and message into a loop to send 5000 messages.

Not a good idea. The output is shown here:

Sent all messages!
Total time to send all messages: 46.52 seconds
SUCCESS

The program takes 46.52 seconds to send everything. Seems pretty slow? It gets much worse: if you don’t remember to close the context (with context.close()) between creating each set of connection variables, you might get some seriously nasty errors.

Let’s make it better. We rewrite the code here:

context = cf.createContext();
destination = context.createQueue("queue:///" + QUEUE_NAME);
producer = context.createProducer();
for (int i = 1; i <= 5000; i++) {
    TextMessage message = context.createTextMessage("This is message number " + i + ".\n");
    producer.send(destination, message);
}
context.close();

This time we have one session, connection and producer to send all 5000 messages. Let’s have a look at the output this time:

Sent all messages!
Total time to send all messages: 9.28 seconds
SUCCESS

Now the entire process takes just 9.28 seconds, so more than 5 times faster.

Now your messages are sent more efficiently and everything happens faster.




CPU too high? Try smart message receiving methods

Now you’ve sent your messages, it’s time to get them off the queue. The way to do this in JMS 2.0 is using a method defined on the MessageConsumer object, receive(). However, if receive() isn’t set up to block the thread until a message arrives, you’ll run into some serious performance problems.

This code shows part of a JMS 2.0 app which tries to receive messages indefinitely. The developer who wrote this wants messages on the queue to be taken off immediately so they have set the timeout to 1 millisecond.

while (true) {
    Message receivedMessage = consumer.receive(1); // wait time of 1ms
    getAndDisplayMessageBody(receivedMessage); // refactored for clarity
}

The while loop executes every 1 millisecond. This means the app uses a lot of resources to check for new messages.

Let’s have a look at our CPU usage. Our developer’s application is the only program running in a docker container so we can look at the CPU by running a docker stats command in a new terminal window:

MQClient$ docker stats --format "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}"
CONTAINER ID        NAME                CPU %
f1537712cda5        pensive_johnson     58.38.%

Without the app running, the container uses 1-3% of the total CPU. So this app is using >55% of the entire container CPU. That’s a lot for just one app!

Let’s try a different approach:

while (true) {
    Message receivedMessage = consumer.receive();
    getAndDisplayMessageBody(receivedMessage); // refactored for clarity
}

The app now waits indefinitely for new messages (the default receive() configuration). However, when a message is received it immediately makes a new request. Our CPU is much happier too:

MQClient$ docker stats --format "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}"
CONTAINER ID        NAME                CPU %
f1537712cda5        pensive_johnson     2.95%

So adding a smarter receive() is extremely good for your CPU!

If we want to receive more than one message, blocking handles this well. The receive waits until a message arrives. When one arrives, a new request is made instantly for the next message.

If we want to get messages from a queue in an event-driven way, we can use a MessageListener object. This links to a MessageConsumer and listens on a separate thread for messages from a specified destination. This gives us a lot of flexibility as our app can continue running whilst the listener listens in the background. For a full code example of a MessageListener object in use, have a look here.

Now you can save your CPU the hard work and get all the messages your heart desires.




Lost messages? App keeps crashing? Catch exceptions to keep your program happy

In the JMS 1.1 specification, most of the API methods we use are declared as Throwable, so you need to catch exceptions or the program won’t compile.

In the JMS 2.0 specification, many API methods aren’t declared as Throwable, which means you don’t have to catch JMSExceptions. If a RuntimeException is thrown, your program will terminate. What’s more, issues with connecting, sending or getting messages also cause your app to throw an exception and die.

This is a way messages can get lost: they’re not getting through.

Here’s an example from a JMS 2.0 app where a developer wants to put an important message onto a queue:

TextMessage message = context.createTextMessage("This important message MUST be sent");
producer.send(destination, message);
System.out.println("Sent message!");

If the queue is full, the program terminates with this (abbreviated) error message:

com.ibm.msg.client.jms.DetailedInvalidDestinationRuntimeException: Failed to send a message to destination 'DEV.QUEUE.1'.
JMS attempted to perform an MQPUT or MQPUT1; however IBM MQ reported an error.
    ... (abbreviated for clarity)
    at com.ibm.mq.samples.jms.MyJmsApplication.main(MyJmsApplication.java:56)
Caused by: com.ibm.mq.MQException: IBM MQ call failed with compcode '2' ('MQCC_FAILED') reason '2053' ('MQRC_Q_FULL').
    ... 13 more (abbreviated again)

This is bad. The important message isn’t going to be sent. This could happen because

  • A consuming application died or is running too slowly, allowing messages to fill up the queue.
  • Many apps could be sending messages to the same queue and filling it before a consuming application can take them off the queue.

The developer anticipates that the consuming application might struggle if there is heavy traffic on the queue. They write code to catch a “queue full” exception:

Boolean sent = false;
while (!sent){
    try {
        TextMessage message = context.createTextMessage("This important message MUST be sent");
        producer.send(destination, message);
        System.out.println("Sent message!");
        sent = true;
    } catch (Exception ex) {
        ex.printStackTrace();
        Throwable causeex = ex.getCause();
        if ((causeex instanceof MQException) && (MQConstants.MQRC_Q_FULL == ((MQException) causeex).getReason())) {
            try{
                Thread.sleep(1000);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        } else {
            throw ex;
        }
    }
}

The exception handling works as shown in this flowchart:

Exception handling in the code. If there is a queue full error, the app sleeps for 1 second then retries. Otherwise, the app terminates and the stack trace is printed.

Now the “queue full” error is handled properly. The application can keep running until the message is sent, even if it has to wait for the consuming application to clear the queue. Here, we see the “queue full” error displayed before the queue clears and the message finally gets through:

com.ibm.msg.client.jms.DetailedInvalidDestinationRuntimeException: Failed to send a message to destination 'DEV.QUEUE.1'.
JMS attempted to perform an MQPUT or MQPUT1; however IBM MQ reported an error.
    ... (abbreviated)
Caused by: com.ibm.mq.MQException: IBM MQ call failed with compcode '2' ('MQCC_FAILED') reason '2053' ('MQRC_Q_FULL').
    ... 13 more (abbreviated)
Sleeping for 1 second.
Sent message!

Catching and dealing with this error properly means the application can continue running and send an important message.

Now you’re catching exceptions, you have greater control of your application and the power to make it resilient to external problems.




Useful links

If you want to learn how to connect to a queue manager, click here.

If you want to learn more about using the JMS 2.0 API, have a look here.

Getting some funky errors? Try this debugging guide.

If you want to learn more about IBM MQ’s performance and read some official performance reports, click here.

If you want to see a working example of a MessageListener, have a look here.