Search code examples
javaibm-mq

How can I set the Curser for an IBM MQQueue explicitly?


I'm currently writing an Application for viewing messages in an IBM MQ. Now I am implementing pagination. I have Queues with arround 40000 entries. I wrote code (see shortened example) that just skips the messages I dont want to display and only handles the messages I want to display. However this is very slow (>45 seconds). Therefore my question is: can I set the cursor explicitly for example to position 35900? I could not find anything in the IBM documentation.

import com.ibm.mq.*;

public class IBMMQMessageBrowser {
    private static final String QUEUE_MANAGER_NAME = "QMGR_NAME";
    private static final String QUEUE_NAME = "QUEUE_NAME";
    private static final int START_MESSAGE_NUMBER = 100;
    private static final int END_MESSAGE_NUMBER = 200;

    public static void main(String[] args) {
        try {
            MQQueueManager queueManager = new MQQueueManager(QUEUE_MANAGER_NAME);
            int openOptions = MQC.MQOO_BROWSE | MQC.MQOO_INQUIRE;
            MQQueue queue = queueManager.accessQueue(QUEUE_NAME, openOptions);

            MQMessage message = new MQMessage();
            MQGetMessageOptions gmo = new MQGetMessageOptions();
            gmo.options = MQC.MQGMO_BROWSE_FIRST;

            int messageCounter = 0;

            while (true) {
                try {
                    queue.get(message, gmo); //Even if i set the desired message size to 0 for Messages I want to skip it is very slow.
                    messageCounter++;

                    // Check if we are within the desired range
                    if (messageCounter >= START_MESSAGE_NUMBER && messageCounter <= END_MESSAGE_NUMBER) {
                        // Process the message here (e.g., print its contents)
                        System.out.println("Message: " + message.readStringOfByteLength(message.getMessageLength()));
                    }

                    // Break the loop if we have reached the end of the range
                    if (messageCounter > END_MESSAGE_NUMBER) {
                        break;
                    }

                    gmo.options = MQC.MQGMO_BROWSE_NEXT;
                } catch (MQException mqe) {
                    if (mqe.reasonCode == MQException.MQRC_NO_MSG_AVAILABLE) {
                        break; // No more messages to browse
                    } else {
                        throw mqe; // Handle other exceptions
                    }
                }
            }

            queue.close();
            queueManager.disconnect();
        } catch (MQException mqe) {
            // Handle MQException
            mqe.printStackTrace();
        }
    }
}

Solution

  • IBM MQ has a few quirks. I'll try not to go too technically in-depth but you & future readers need to understand how to deal with these quirks.

    1. There is no ability to move the 'read' cursor forward other than actually getting or browsing a message.

    2. There may be far more MQGETs performed than you actually realize.

    The reason why it takes more than 45 seconds to get to message # 39,500 is because you are pulling all of the data across the network. By that I mean, if the average message size was 100k and you retrieved 39,500 messages, then you dragged a minimum of 3.8GB of data across the network to get to message number 39,500 plus a minimum of 39,500 MQGET API calls.

    Now why do I say "minimum" of 39,500 MQGET API calls? It is because for Java, JMS and .NET applications, the MQ Client library preallocates a 4KB buffer to retrieve the message. So, under the covers, it may take 2 MQGETs to actually retrieve your message if it is larger than 4KB.

    I've written a lot about it on my blog:

    Now to the second part, how to speed up moving the cursor from the current message to say message number 39,500.

    There are 3 get methods for the MQQueue class: https://www.ibm.com/docs/en/ibm-mq/latest?topic=java-mqqueue

    You should be using the get method that takes 3 parameters for when you are simply wanting to move the cursor forward.

    public void get(MQMessage message,
                    MQGetMessageOptions getMessageOptions,
                    int maxMsgSize)
             throws MQException
    

    Therefore, in the loop for moving the cursor forward, you change:

    queue.get(message, gmo); 
    

    to

    queue.get(message, gmo, 0); 
    

    There used to be a weird bug if you set the max message length to zero but it appears that it has been fixed. I still use 1 just to make sure I don't hit that issue.

    And you need to add the MQGMO_ACCEPT_TRUNCATED_MSG option to GMO options. i.e.

    gmo.options = CMQC.MQGMO_BROWSE_NEXT + CMQC.MQGMO_FAIL_IF_QUIESCING + CMQC.MQGMO_ACCEPT_TRUNCATED_MSG;
    

    Finally, you should ALWAYS have "Fail if Quiescing" options added to MQ API calls like MQOPEN, MQGET, MQPUT, etc.

    When you set the max. message length to zero & add the MQGMO_ACCEPT_TRUNCATED_MSG option, the MQGET call will throw an MQ exception of 2079 - MQRC_TRUNCATED_MSG_ACCEPTED which is perfectly normal. MQ will return the MQMD of the message along with an empty message buffer. You just handle the MQ exception and loop again for the next message.

    Hence, the time it takes to move the cursor to message number 39,500 should now be under 5 seconds, maybe even less than 1 second depending on your network traffic.