What Is a Message Queue?
The problem with calling services directly
Section titled “The problem with calling services directly”Imagine an order service that, when an order is placed, must: charge a card, send a confirmation email, update inventory, and notify the warehouse. The obvious approach is to call each service directly and wait for each to finish.
That works until it doesn’t:
- The email service is slow. Now placing an order is slow, because you’re blocked waiting on email.
- The warehouse service is down. Now the whole order fails — even though the payment succeeded.
- A flash sale hits. Ten thousand orders per second slam every downstream service at once, and the slowest one falls over.
The root problem: direct calls couple the caller’s fate to the callee’s. If the callee is slow, you’re slow. If it’s down, you fail. If you spike, it spikes.
What a queue changes
Section titled “What a queue changes”A message queue puts a buffer in the middle. The order service publishes a message (“order 123 placed”) and immediately returns. Each downstream service consumes those messages on its own schedule.
flowchart TB
subgraph direct["Direct calls — coupled fate"]
o1["order service"] -->|blocks| e1["email (slow)"]
o1 -->|fails if down| w1["warehouse"]
end
subgraph queued["Via a queue — decoupled"]
o2["order service"] -->|publish, return| q["queue (buffers)"]
q --> e2["email consumer
(own pace)"]
q --> w2["warehouse consumer
(catches up later)"]
end Three things fall out of that one change:
- Decoupling. The producer doesn’t know or care who consumes, how many consumers there are, or whether they’re up right now.
- Buffering (load leveling). A spike of 10,000 messages sits safely in the queue; consumers drain it at a steady rate instead of being crushed.
- Resilience. If a consumer is down, messages wait in the queue. When it comes back, it catches up — nothing is lost.
The trade-off: you gain async, you lose “immediately”
Section titled “The trade-off: you gain async, you lose “immediately””Queues are not free. In exchange for decoupling you accept:
- Eventual, not immediate. The email is sent soon, not before the HTTP response returns. Your UX and data model must tolerate that gap.
- More moving parts. The broker is now critical infrastructure you must run, monitor, and secure.
- Harder debugging. A request no longer has one linear stack trace; it fans out across producers, queues, and consumers.
When to use a queue (and when not to)
Section titled “When to use a queue (and when not to)”Reach for a message queue when:
- Work can happen asynchronously — the caller doesn’t need the result to respond.
- You need to absorb spikes or smooth out bursty load.
- Services should be decoupled so one failing doesn’t cascade.
- You want to fan work out to many workers or broadcast an event to many subscribers.
Stay synchronous (plain HTTP/gRPC) when:
- The caller needs the answer now to continue (e.g. “is this username taken?”).
- The interaction is a simple, fast request/response with no spikes.
- Adding a broker’s operational weight isn’t worth it for the problem at hand.