Contents

Architecture & System Design › Cloud Design Patterns · also in Queues & Async Processing

Competing Consumers

Several workers pulling from one queue to share the load.

Also known as: competing consumers, competing consumer pattern, parallel consumers

Competing consumers scale message processing horizontally: multiple instances pull from the same queue, each message handled by exactly one — throughput rising with consumer count, no coordination beyond the broker’s delivery. The pattern behind worker fleets, order processors and notification senders everywhere.

[queue] → worker A, worker B, worker C (each message → exactly one)
scale: add workers until the bottleneck moves elsewhere

Ordering, duplicates and poison messages are the design surface: competing consumers process concurrently (order only per-partition where supported), redeliver on failure (idempotence required), and need dead-lettering plus autoscaling driven by queue depth.

The classic mistakes:

  • Assuming order. Concurrent consumers process out of order by nature; order-dependent work needs partitioning (key-affine consumers) or single-consumer queues.
  • Non-idempotent handlers. Redelivery plus competing retries double-process; every handler idempotent, tested with duplicates.
  • Scaling workers past downstream. Ten consumers hammering one database connection pool just move the queue into the database. Scale to the bottleneck, not past it.
  • Poison-message stalls. One toxic message redelivered forever occupies consumers and delays everything behind it (on ordered queues, blocks entirely). Dead-letter after bounded attempts.
  • Autoscaling on CPU. Consumer CPU idles while queues grow (I/O-bound waits); scale on queue depth and age, not processor load.
  • Visibility timeout mismatch. Locks expiring before slow processing finishes duplicate work across competitors. Timeouts beyond p99 processing, with heartbeats for long tasks.
  • Deploying all consumers at once. Rolling restarts rebalancing partitions cause duplicate processing storms; stagger, drain, and keep handlers idempotent through transitions.

How to run them: idempotent handlers, dead-lettering, depth-driven autoscaling, ordering only where partitioned. Competing consumers are horizontal scale made simple — simple, not thoughtless.