Prefetch & QoS
The problem: one worker eats everything
Section titled “The problem: one worker eats everything”Put two consumers on a queue and RabbitMQ hands out messages round-robin — one to each, in turn. That sounds fair, but it’s fair by count, not by work. If it blindly pushes messages as fast as it can, a consumer that grabbed ten slow messages is buried while another sits idle. Worse, with manual ack and no limit, the broker can shove hundreds of unacked messages at one consumer, blowing up its memory.
The fix is prefetch, set via basic.qos.
What prefetch does
Section titled “What prefetch does”Prefetch caps how many unacknowledged messages a consumer may hold at once. Set it to 1 and RabbitMQ won’t give a consumer a second message until it acks the first. Set it to 50 and a consumer can have up to 50 in flight.
This turns round-robin into fair dispatch: a busy consumer stops receiving new work until it catches up, so messages naturally flow to whoever is actually free.
flowchart TB
subgraph rr["No prefetch limit — round-robin by count"]
q1["queue"] --> w1["worker A (busy,
buried in slow tasks)"]
q1 --> w2["worker B (idle,
nothing queued to it)"]
end
subgraph fd["prefetch = 1 — fair dispatch"]
q2["queue"] --> w3["worker A
(1 at a time)"]
q2 --> w4["worker B
(gets the next free one)"]
end Choosing a value
Section titled “Choosing a value”There’s a trade-off:
- prefetch = 1 — the fairest, and the safest for long or uneven tasks. The cost is a little latency: a consumer waits for the ack round-trip before getting the next message. Great for slow jobs (image processing, emails).
- Higher prefetch (e.g. 10–100) — more throughput for fast, uniform tasks, because the consumer always has a buffer ready and isn’t stalled waiting for the next delivery. The cost is less even distribution and more memory per consumer.
A good default for typical background jobs is a small number (1 for slow tasks, 10–50 for fast ones), tuned by watching queue depth and consumer utilisation. Never leave it unbounded in production.
Setting prefetch
Section titled “Setting prefetch”// at most 1 unacked message per consumer at a timeawait channel.prefetch(1);channel.consume('orders', handler, { noAck: false });# at most 1 unacked message per consumer at a timechannel.basic_qos(prefetch_count=1)channel.basic_consume(queue="orders", on_message_callback=handler, auto_ack=False)// prefetchCount=1, prefetchSize=0, global=falsech.Qos(1, 0, false)msgs, _ := ch.Consume(q.Name, "", false, false, false, false, nil)