MQ JMS and Spring Boot – improved efficiency

The efficiency of MQ JMS is now improved when used in a Spring Boot application.

The Spring Framework provides simple ways for Java programs to use a variety of interfaces. Its JMS component includes classes that help a program wait for new messages, similar to a Message Driven Bean. The default behaviour of the Spring implementation is known to be non-optimal when working with IBM MQ and I wanted to improve the efficiency.

This article shows recent improvements to Spring Boot and the corresponding MQ JMS Spring Boot component. They remove the need for application developers to know about, and to write code to deal with that inefficiency.

For more information about the MQ Spring Boot Starter, see this article.

The problem

Paul wrote more information about the behaviour of the polling listener in this article. But in summary, the Spring DefaultMessageListenerContainer implementation uses a polling approach to receive JMS messages. Essentially it is doing

  while (no message received) {
    MQGET(wait for receiveTimeout value)
    see if anything else needs to be done
  }

The receiveTimeout setting determines how fast the polling loop goes. The value doesn’t matter for responsiveness to processing an arriving message – whatever it is set to, the message is read as soon as it is available on the input queue. But setting a high value  will mean that other things that might need to be handled (the Spring Framework might be looking for shutdown requests, for example) are delayed until expiry of the timer. So it is important to tune the value.

The default value is 1 second which can mean far more MQGETs being issued than is desirable.

Tuning in application code

Application developers can insert a call to setReceiveTimeout() on the created listener object in their code. That has been the only way to override that 1s value. The developer must know to do it, as well as picking a suitable value. There has been no mechanism in Spring to change the timer without changing the application code and recompiling the program.

A better solution

It is possible to set many properties of a Spring application externally, with a file containing values that is “outside” of the application code, although deployed with it. For example, you might have set ibm.mq.queueManager=QM1or java.naming.factory.initial=com.sun.jndi.fscontext.RefFSContextFactory.

But up to now, the listener’s receiveTimeout could only be set programmatically.

Changes to Spring Boot

A change was accepted into Spring Boot version 2.2.0 that makes the JMS Listener capable of using external configuration for the timer. In order to keep the behaviour unchanged for all JMS providers, the default value is still 1s. But it is now possible to put spring.jms.listener.receive-timeout=30s in a properties file to change the value (unless the application had previously been coded to set it explicitly).

The property is in Duration format – a suffix indicates the units. Most likely you’d want to set it to some number of seconds (eg 30s), but a small number of minutes (eg 2m) may be appropriate. The default unit for a Duration is ms (milliseconds) so not giving the unit suffix might make the polling loop much faster than you expect!

Changes to MQ JMS package

Alongside the changes to Spring Boot, a further change was made to the MQ Spring Boot classes in (what is coincidentally the same number) version 2.2.0.

That change detects whether there is an external configuration of the spring.jms.listener.receive-timeout property. If found, then that value is respected. If it is not set, then the value is automatically changed from 1s to 30s. And of course, if the application code sets the value later, then that takes precedence instead.

Demonstration of changes

To show the changes are effective, I can use the sample program provided with the MQ Spring Boot package. And I can also use the Application Activity Trace to see the real MQI calls being made to the queue manager under the covers.

In one window I run the sample program:

$ cd github.com/ibm-messaging/mq-jms-spring/samples
$ ./RUNME.sh
2020-03-27 10:42:28.301  INFO 2451661 --- [           main] sample1.Application                      : Starting Application
 2020-03-27 10:42:28.304  INFO 2451661 --- [           main] sample1.Application                      : No active profile set, falling back to default profiles: default
 2020-03-27 10:42:29.270  INFO 2451661 --- [           main] sample1.Application                      : Started Application in 1.264 seconds (JVM running for 1.577)
 ========================================
 MQ JMS Sample started. Message sent to queue: DEV.QUEUE.1
 ========================================
 MQ JMS Listener started for queue: DEV.QUEUE.1
 NOTE: This program does not automatically end - it continues to wait
       for more messages, so you may need to hit BREAK to end it.
 ========================================
 Received message is: Hello from IBM MQ at Fri Mar 27 10:42:29 GMT 2020
 <=========----> 75% EXECUTING [17s]
   :bootRun

In another window I subscribe to information about the application’s behaviour:

$ export TOPIC='$SYS/MQ/INFO/QMGR/QM1/ActivityTrace/ApplName/sample1.Application'
$ amqsevt -t $TOPIC -m QM1 -o json |\
    jq  '.eventData.activityTrace[] | 
         select(.operationId == "Get") |
         .operationTime'

The jq filter pulls out just the timestamps associated with an MQGET operation.

Before the new capability

Changing the sample application’s build.gradle file to refer to the MQ spring-boot-starter 2.1.4 release (the last version before these changes), and leaving the program to run for a while, we get the output

"10:50:12"
"10:50:13"
"10:50:14"
"10:50:15"
"10:50:16"
"10:50:17"
"10:50:18"
"10:50:19"
"10:50:20"
"10:50:21"
"10:50:22"
"10:50:23"
...

You can see the 1s loops.

With the new capability

Reverting the build.gradle file to refer to the current mq-jms-spring-boot-starter release, and with no other changes, the output looks like

"10:54:17"
"10:54:17"
"10:54:47"
"10:55:17"

where we can see the behaviour is now a 30s loop. Which has significantly reduced the number of MQGET calls. The first MQGET in this short sequence is the one where a message was returned; the rest of the calls receive MQRC_NO_MSG_AVAILABLE (2033).

Explicitly configuring a timer

For the final test, we add the line spring.jms.listener.receive-timeout=10sto the application.properties file in the sample directory. The output now appears like

"10:59:02"
"10:59:02"
"10:59:12"
"10:59:22"
"10:59:32"
"10:59:42"
"10:59:52"

so we can see the 10s non-default value took effect.

Summary

If you use Spring Boot with MQ JMS, you now get the benefit of better use of MQ resources without needing to make application changes. Existing applications automatically get this enhancement by simply building with a current version of the mq-jms-spring-boot-starter.

This post was last updated on March 30th, 2020 at 04:41 pm

5 thoughts on “MQ JMS and Spring Boot – improved efficiency”

    1. The underlying Spring code for the listener is not using onMessage which might map to calling MQCB. They’re simply calling the JMS receive method which maps fairly directly to MQGET. Spring does have alternative Listener interfaces but the DefaultMessageListenerContainer is the most commonly-used (note the ‘Default’ in its name) and the easiest to access in Spring Boot. There are also drawbacks to some of the other Spring listener interfaces because of things like thread and transaction management.

  1. Hi Mark,

    So in previous versions of Spring Boot, what is the behavior if an application specified receiveTimeout in a properties file for the DefaultMessageListenerContainer without any additional code changes? We thought that simply specifying the property would reduce the MQGETs (or fruitless gets as we call them).

    1. If you have the property in a properties file, it’s simply ignored by older versions of Spring so you get the 1 second timer. I find that editing the properties file in Eclipse shows unknown properties as a syntax warning but it’s not highlighted at runtime.

      There is a separate similarly-named property called spring.jms.template.receive-timeout and that is recognised by older Springs but it does something very different.

  2. The defaults in Spring cause so many issues as developers with barely any MQ knowledge connect a pool of listeners all merrily polling away. A disaster when the QM is remote and even more so when it’s a critical and expensive mainframe one..

Leave a Reply

Your email address will not be published. Required fields are marked *