It is Friday, 20:00, and a popular show just dropped. In sixty seconds, two million people press
"buy." Your checkout service can comfortably charge fifty cards a second. If every "buy"
button reached straight into the payment code — a synchronous
The trick is to stop making the two services talk directly. Instead, the web servers drop each order into a queue — a buffer in the middle — and reply "got it!" instantly. The payment service then pulls orders out of the queue at its own steady pace of fifty a second. The queue absorbs the flood; the spike becomes a backlog that drains over the next few minutes. This is asynchronous messaging, and the humble queue in the middle is one of the most important structures in all of distributed systems.
In a synchronous call, the caller sends a request and blocks — it waits, doing nothing useful, until the reply comes back. The two parties are coupled tightly: they must both be alive at the same instant, and the caller runs no faster than the callee. That is exactly the socket/RPC model, and it is perfect when you need an answer right now (what's my balance?).
In asynchronous messaging, the sender (the producer) hands a message to a broker and moves on immediately, without waiting for it to be processed. Later — milliseconds or minutes later — a consumer picks the message up and acts on it. The queue between them is a simple idea with three superpowers:
The picture below is load levelling itself. The bursty curve is the arrival rate — a quiet baseline with a big spike when the show drops. The flat line is what a single consumer can actually process per second (drag the slider to change its capacity). While arrivals sit above the line, the gap is not lost — it piles up in the queue as backlog; when arrivals fall below the line, the consumer eats through that backlog. The queue turns a spike you cannot handle into a delay you can.
There are two fundamentally different messaging patterns, and choosing the wrong one is a common design error. They differ in a single question: when a message is delivered, does exactly one recipient get it, or do many?
A message queue faces the same partial-failure problem as
This gives at-least-once delivery: no message is ever lost, but a message whose ack was
lost (not the message — the ack) gets processed twice. Just as with
You will see "exactly-once" on marketing pages, and it is almost a fib. True exactly-once delivery over an unreliable network is impossible — the ack can always be the thing that gets lost, forcing a redelivery. What systems like Kafka actually provide is exactly-once processing: at-least-once delivery, plus deduplication (each message carries an ID the consumer records) or transactional writes that make a duplicate a no-op. The effect happens once even though the message may be delivered twice. It is the same trick as RPC's exactly-once effect — and the same reason your consumer still has to be idempotent underneath.
Do messages arrive in the order they were sent? Often you want them to — "account created" before "account updated." But strict global ordering fights against scale: if every message must be processed in one sequence, you can only have one consumer working at a time, which kills the parallelism that competing consumers gave you. The standard resolution is partitioning: split the stream into independent partitions by a key (say, the customer ID). Order is guaranteed within a partition but not across partitions — so all of one customer's events stay in order, while different customers are processed in parallel. Ordering and throughput are traded off by the granularity of the key.
Two influential broker designs sit at different points on this landscape:
Let's make load levelling and competing consumers concrete. Below, a producer emits a big burst of messages up front and then goes quiet, while consumers each process a fixed number per tick. We track the queue backlog every tick and watch it build, then drain — and we see how adding a second consumer halves the time to clear the flood.
With one consumer the backlog towers up to the 30s and takes many ticks to melt away; with two competing consumers the same flood clears in roughly half the time. The producer never changed — we just widened the outlet. That is the entire economic argument for competing consumers.
The queue's magic tempts a dangerous belief: "just put a queue in front of it and we can handle any load." No. A queue smooths a temporary burst on the assumption that, on average, the consumer keeps up. If arrivals exceed the consumer's capacity for a sustained period, the backlog does not smooth — it grows without bound. Two things then blow up: latency (a message added to a million-deep queue waits behind all million, so end-to-end delay climbs into minutes or hours) and memory/disk (the broker must store the ever-growing backlog until it runs out and starts dropping or rejecting messages). A queue converts an overload into delay, and delay is only survivable if the overload is brief. Monitor queue depth as a first-class health metric; if it trends up and never comes down, you have a capacity problem no queue can hide. And remember the other half: because delivery is at-least-once, the same message will occasionally be delivered twice — so every consumer must be idempotent. Processing "charge card" twice because an ack got lost is not a hypothetical; it is Tuesday.