rabbitmqamqpphp-amqplib

Distribute messages from RabbitMQ to consumers running on Heroku dynos as a 'round robin'


I have a RabbitMQ setup in which jobs are sent to an exchange, which passes them to a queue. A consumer carries out the jobs from the queue correctly in turn. However, these jobs are long processes (several minutes at least). For scalability, I need to be able to have multiple consumers picking a job from the top of the queue and executing it.

The consumer is running on a Heroku dyno called 'queue'. When I scale the dyno, it appears to create additional consumers for each dyno (I can see these on the RabbitMQ dashboard). However, the number of tasks in the queue is unchanged - the extra consumers appear to be doing nothing. Please see the picture below to understand my setup.

RabbitMQ setup

Am I missing something here?

  1. Why are the consumers showing as 'idle'? I know from my logs that at least one consumer is actively working through a task.
  2. How can my consumer utilisation be 0% when at least one consumer is definitely working hard.
  3. How can I make the other three consumers actually pull some jobs from the queue?

Thanks

EDIT: I've discovered that the round robin dispatching is actually working, but only if the additional consumers are already running when the messages are sent to the queue. This seems like counterintuitive behaviour to me. If I saw a large queue and wanted to add more consumers, the added consumers would do nothing until more items are added to the queue.


Solution

  • To pick out the key point from the other answer, the likely culprit here is pre-fetching, as described under "Consumer Acknowledgements and Publisher Confirms".

    Rather than delivering one message at a time and waiting for it to be acknowledged, the server will send batches to the consumer. If the consumer acknowledges some but then crashes, the remaining messages will be sent to a different consumer; but if the consumer is still running, the unacknowledged messages won't be sent to any new consumer.

    This explains the behaviour you're seeing:

    1. You create the queue, and deliver some messages to it, with no consumer running.
    2. You run a single consumer, and it pre-fetches all the messages on the queue.
    3. You run a second consumer; although the queue isn't empty, all the messages are marked as sent to the first consumer, awaiting acknowledgement; so the second consumer sits idle.
    4. A new message arrives in the queue; it is distributed in round-robin fashion to the second consumer.

    The solution is to specify the basic.qos option in the consumer. If you set this to 1, RabbitMQ won't send a message to a consumer until it has acknowledged the previous message; multiple consumers with that setting will receive messages in strictly round-robin fashion.