Skip to content

Work Queues

A work queue (also called a task queue or competing consumers) is the workhorse of RabbitMQ. One producer publishes tasks — resize this image, send this email, generate this report — and a pool of workers shares the load. Each task goes to exactly one worker.

This is how background jobs scale. Too much work? Start more workers on the same queue. They compete for messages, and RabbitMQ hands each message to one of them.

flowchart LR
  p["Producer
(publishes tasks)"] --> q["Queue: tasks"]
  q --> w1["Worker 1"]
  q --> w2["Worker 2"]
  q --> w3["Worker 3"]
One queue, many competing workers

By default RabbitMQ dispatches messages round-robin: worker 1, worker 2, worker 3, worker 1, and so on — without looking at how busy each worker is. That’s fine when every task takes the same time. But if worker 1 gets a stream of slow tasks and worker 2 gets fast ones, round-robin still feeds them evenly, and worker 1 falls behind while worker 2 sits idle.

The fix is prefetch (basic.qos): tell RabbitMQ “don’t give a worker a new message until it has acked the last one.” With a prefetch of 1, a worker only ever holds one unacked task, so a slow worker simply pulls its next task later — busy workers naturally get less. This turns round-robin into fair dispatch.

const channel = await conn.createChannel();
await channel.assertQueue('tasks', { durable: true });
// Fair dispatch: at most one unacked message per worker.
await channel.prefetch(1);
channel.consume('tasks', async (msg) => {
if (!msg) return;
await doWork(msg.content.toString()); // process the task
channel.ack(msg); // only now ask for the next
}, { noAck: false });

Notice noAck: false / manual ack above. This is the second half of a reliable work queue. Because the worker acks only after the task finishes, a worker that crashes mid-task never acked — so RabbitMQ redelivers that task to another worker. Nothing is silently lost.

Pair that with a durable queue and persistent messages (from the reliability module) and your task queue survives both a worker crash and a broker restart.

Prefetch of 1 is the safest and gives the fairest dispatch, but it adds a round-trip per message. For fast, uniform tasks a higher prefetch (say 10–50) keeps workers fed and throughput high. The trade-off:

PrefetchEffect
1Fairest dispatch, lowest throughput, safest for slow/uneven tasks
Moderate (10–50)Good throughput for fast tasks; some unfairness
Unlimited (0)Max throughput but one worker can hoard the backlog and others starve
What does a work queue give you?
Why set prefetch (basic.qos) to a low value?
A worker crashes after receiving a task but before acking. What happens?
What is the downside of an unlimited prefetch?