0% completed
Publish-Subscribe
On This Page
- The Incident
- The Naive Fix, and Why It Fails
- The Pattern
- Walkthrough with Numbers
- Trade-offs: What You Pay
- When NOT to Use It
- Pattern Face-Off: Choreography vs Orchestration
- In the Wild
- The Interview Lens
- Check Yourself
- Composes With
- TL;DR Card
1. The Incident
The message queue fixed your flash sale: checkout puts fulfillment work on a queue and confirms instantly. It worked so well that other teams noticed.
The email team asks for order events, to send receipts. Then analytics asks, for dashboards. Then fraud, then loyalty. Each request gets solved the same way: someone opens a pull request against checkout and adds one more "send to this team's queue" call. Eighteen months later, checkout writes to five queues, one after another, and you're living with three chronic problems:
- Every new consumer means a checkout deploy. Checkout is the most revenue-critical service in the company. And it now has to change, and re-deploy, whenever any other team wants order data. Its release calendar has become the whole company's bottleneck.
- Crashes leave teams with different truths. Last Tuesday, checkout crashed after writing to three of the five queues. Fulfillment shipped the order. Fraud never saw it. There's no way to make five separate queue-writes succeed or fail together, so every crash is a lottery: some teams get the event, some don't.
- Other teams' problems become checkout's problems. When the loyalty team's queue had a bad hour, checkout's five-writes-in-a-row slowed down with it. The company's checkout latency now depends on the health of a loyalty queue.
Then the data science team asks for order events to train a model, and the honest answer is: "open a PR against checkout and wait for the next release." Think about what that means. The producer of the data has to personally know and deliver to every consumer. Every new consumer adds work, risk, and coordination meetings. That doesn't scale, and the thing that breaks first isn't the software. It's the teams.
2. The Naive Fix, and Why It Fails
"It works. Keep adding queues." Mechanically, yes. But checkout has become the company's mail room: it owns the wiring, the retries, and the delivery guarantees for every team's copy of the data. And every added queue buys one more ticket in the partial-crash lottery.
"Let teams read the orders database directly." Now five teams are coupled to your exact table layout. The schema migration you need next quarter will silently break three dashboards and a fraud model you didn't even know existed. Their constant polling also adds load to your production database. (There is a disciplined version of this instinct, called change data capture, where the database's own change log becomes the event stream; it gets its own lesson later in the course. Ad hoc table-reading is that pattern without the safety contract.)
"One shared queue that everyone reads." This one sounds right and is subtly disastrous. Remember how a queue works: each message goes to exactly one worker. Point fraud, email, and analytics at the same queue, and the broker hands each order to just one of them. Each team ends up seeing a random third of the orders. Nothing errors. No alarms fire. Every team quietly works with incomplete data and doesn't know it. (This exact bug is Q3.)
Step back and the diagnosis is clear. Five teams don't want five tasks done. They all want to know the same fact: an order was placed. A fact needs to be announced once, to everyone. It should not be hand-delivered five times.
3. The Pattern
The producer publishes a fact to a named channel, called a topic, exactly once, without knowing who's listening. The broker then delivers a separate copy to every subscription. Each subscriber processes its copy at its own pace, with its own failure handling.
This is the group announcement from the module intro. Post the news once on the company announcement channel. Every team reads it in their own time. The announcer doesn't keep a list of readers, and a team joining the channel doesn't require the announcer to do anything.
The moving parts, one at a time:
- The topic is the named stream of facts, like
order.placed. Checkout's entire job is now: publish here, once, and move on. One durable write, always the same cost, no matter how many teams listen. The partial-crash lottery is gone, because there's only one write. - A subscription is one team's personal copy of the stream. Under the hood, each subscription works like that team's own message queue: it has its own backlog, its own position in the stream, its own acknowledgments and redeliveries, and its own dead letter queue. Everything the queue lesson established applies per subscription. So if fraud's service is down for an hour, fraud's backlog grows. Email doesn't notice. Checkout doesn't either.
- Joining is configuration, not code. When data science wants order events, they create a subscription. Checkout doesn't deploy, and honestly, checkout doesn't even know. With a broker that keeps history (Kafka is the famous one), the new subscriber can even start by replaying past events, feeding months of history into a brand-new model.
- The event's format is now a public promise. This is the part teams underestimate. Once unknown consumers exist, the fields in your event are an API. Two disciplines follow. First, events state facts, not commands: "an order was placed, here are its details," never "send a receipt." The announcer reports news; each reader decides what to do about it. Second, changes must be additive only: you may add new optional fields, but renaming or removing a field breaks subscribers you can't see. Teams typically enforce this with a schema registry, a tool that checks event formats before they ship.
One sentence captures the philosophy: the producer says what happened, and every consumer decides what it means. Checkout doesn't know receipts exist. That ignorance is the feature.
4. Walkthrough with Numbers
The topic carries 200 orders per second. Compare the old world and the new one:
- Publishing cost. Old: five queue-writes in a row, about 25 ms, growing with every new team. New: one durable write, about 5 ms, forever, no matter how many subscribers exist.
- The data science request. They create a subscription in an afternoon. Zero checkout changes. With 30 days of history retained on the topic, they replay roughly 500 million past events to train on before switching to live traffic. The old answer was a PR, a release train, and no history at all.
- The failure drill. Fraud's consumer goes down for 40 minutes. Its subscription quietly accumulates 200 × 2,400 seconds = 480,000 events of backlog, then drains when the service returns. Email sent every receipt on time. Checkout never felt a thing. In the old world, this same outage either slowed checkout down or made fraud silently miss 40 minutes of orders.
- The audit that now passes. Ask "which teams saw order X?" and every subscription has a precise answer: processed, still in backlog, or parked in its dead letter queue. No more lottery.
5. Trade-offs: What You Pay
You gain:
- The producer's cost and risk stay constant no matter how many consumers exist.
- Teams ship on their own calendars. This organizational decoupling is the quiet superpower.
- Failures isolate per subscription, and (with history) new consumers can be born already knowing the past.
You pay:
- Your event format is a public API now. Renaming a field breaks consumers you've never met. You traded deploy coupling for contract discipline: schema registries, additive-only changes, deprecation windows.
- Duplicates, multiplied. Each subscription is at-least-once on its own. Five subscribers means five places that must handle the same event twice (idempotency, always).
- Invisible coupling. Without deliberate bookkeeping, nobody can list who consumes a topic. The dependency didn't disappear; it went quiet. Mature teams keep a catalog of who subscribes to what.
- No "all done" signal. Pub/sub can't tell you "everything that should happen for order X has happened." Each subscription only knows about itself. Workflows that need a completion answer want a different tool (see the face-off).
- Slow consumers can fall off the edge. History isn't kept forever. A subscriber that lags past the retention window doesn't get "slow": it gets data loss. Alert on how old each subscription's backlog is.
6. When NOT to Use It
- It's a job, not a fact. "Resize this image" should be done once, by one worker. That's a message queue, full stop.
- The producer needs the result. Checkout must know whether the payment authorized before it can continue. Publishing
payment.requestedand hoping someone handles it is not a design. Paths where the caller's next step depends on the outcome stay request-response, or use an orchestrated workflow. - Two services, low traffic. One producer with one consumer doesn't need broadcast plumbing. A direct queue is simpler to run and to reason about.
- Fake request-response. Publishing
user.lookup.requestedand then subscribing touser.lookup.completedis a phone call rebuilt out of announcements, with worse latency and much worse debugging. If the event name contains "request," it wanted to be a call.
The big-ecosystem misuse is event soup: hundreds of topics, no owners, no schema rules, and consumers you only discover when something breaks. Pub/sub removes the natural pressure that forced teams to talk to each other. Mature organizations replace that pressure on purpose, with contracts and a catalog.
7. Pattern Face-Off: Choreography vs Orchestration
Pub/sub enables a style called choreography: every service reacts to announcements on its own, like dancers who each know their part, and the overall behavior emerges. The alternative is orchestration: one coordinator explicitly runs the workflow step by step, like a conductor pointing at each musician. Interviewers love this distinction.
| Choreography (pub/sub) | Orchestration (a coordinator) | |
|---|---|---|
| Who's in control | Nobody central; behavior emerges | The coordinator, explicitly |
| Adding a step | New subscription, no coordination | Update the coordinator's workflow |
| "Is order X fully processed?" | No single place knows | The coordinator knows, by design |
| Failure handling | Each consumer handles its own | The coordinator retries, undoes, escalates |
| Best for | Independent reactions: analytics, email, indexing | Real sequences with stakes: inventory, payment, shipping |
The test to apply: are the steps truly independent? If nobody cares whether email runs before analytics, choreograph and enjoy the freedom. But if the steps form a sequence with stakes (reserve inventory, then charge, then ship, and undo things if a step fails), that's a workflow wearing an event costume, and it needs an orchestrator: the saga lesson picks up exactly there. Real systems use both: orchestrate the order workflow itself, and let it announce facts along the way for everyone else to react to.
8. In the Wild
- AWS SNS feeding SQS queues is this pattern drawn with managed services: SNS is the topic, and each team's SQS queue is its subscription. Broadcast first, then each team's own to-do list.
- Kafka treats each "consumer group" as an independent subscription over a stored log, which is why it dominates: broadcast, replay, and per-team queue behavior in one system.
- Google Cloud Pub/Sub is the same model, fully managed, with subscriptions as first-class objects.
- Redis Pub/Sub is the cautionary example: it's fire-and-forget. If a subscriber is offline, the message is gone, permanently. "Pub/sub" without durable subscriptions is a much weaker promise wearing the same name. Always check which one you're being offered.
For AI engineers: the interaction-event topic is the circulatory system of an ML platform. One user.interacted stream feeds the analytics warehouse, the feature store, the job that keeps vector embeddings fresh, and the recommender's real-time features. Four consumers, one publish, and all of it replayable when the next model needs history. Agent systems are adopting the same shape: every tool call and decision gets published once, and observability, evals, and audit each consume it independently, none of them slowing down the agent itself.
9. The Interview Lens
How it's asked: the direct form is "Five teams need order data. How do they get it?" The hidden form appears in any microservices design: you draw arrows between services, and the interviewer asks what happens when a sixth service needs the same data. Candidates without this pattern answer by drawing another arrow from the producer, and the interviewer watches the producer collect arrows like a pincushion. There's also a trap form: you're given a genuine workflow (order, payment, shipping) and the interviewer waits to see if you choreograph what should be orchestrated.
The 30-second answer: "Checkout publishes an
order.placedfact to a topic, once. Every interested team gets its own subscription with its own backlog, retries, and dead letter queue, so a slow or crashed consumer affects nobody else. Adding a consumer is configuration, not a checkout deploy, and with a log-based broker like Kafka the new consumer can replay history. The costs I manage: the event schema is now a public API, so changes are additive-only and checked against a registry; every subscription needs idempotent processing; and I alert on backlog age so a slow consumer never falls past the retention window. And if the steps are really a workflow that needs completion tracking, I orchestrate that part instead."
Likely follow-ups:
- "One subscriber processes at half the speed of the others. What happens?" Its own backlog grows; everyone else is untouched. That's the isolation working. The real danger is silent: if it lags past the retention window, it starts losing data. So alert on backlog age, and scale that one consumer by adding workers within its subscription (the queue lesson's competing consumers, applied inside one subscription).
- "How do you change an event's schema without breaking consumers you don't know about?" Additive changes only: new optional fields, never renames or removals. Enforce it with a schema registry that fails the build on breaking changes. For a truly breaking change, publish version 2 alongside version 1, give consumers a deprecation window, and keep a catalog so "consumers you don't know about" stops being a real category.
Red flag that fails candidates: pointing several different services at one shared queue and calling it broadcast (each service just gets a random sample), or building request-response out of event pairs. Both mean the jobs-versus-facts distinction hasn't landed.
10. Check Yourself
Q1 (recall). Explain the difference between a topic and a subscription. Then state exactly what it costs the producer when a sixth consumer team shows up.
Q2 (trade-off). "Pub/sub decouples services." Argue the opposite for two minutes: what coupling survives, and what new discipline replaces the old deploy-time coordination?
Q3 (scenario). Fraud reports scoring only about 20% of orders. Email reports sending about 30% of receipts. The broker's metrics swear nothing was lost, and checkout's publish count matches order volume exactly. Diagnose the wiring and fix it.
<details> <summary>Answers</summary>A1. The topic is the single, durable stream of published facts. A subscription is one consumer's independent feed from that stream, tracking its own position, with its own backlog, acknowledgments, and dead letter queue: effectively a private queue filled from the topic. A sixth consumer costs the producer nothing: no code, no deploy, no added latency, no awareness. The costs land on the new consumer (build idempotent processing, watch its backlog) and on the ecosystem (one more party depending on the event schema).
A2. The data coupling survives; it just changes shape. Every consumer now depends on the event schema, so renaming one field breaks systems the producer can't even list, which is worse than a compile error unless it's managed. The replacing discipline: treat the schema as an API (registry, additive-only changes, deprecation windows), keep a catalog of consumers so dependencies stay visible, and give each subscription an operational owner. Pub/sub doesn't delete coupling. It moves coupling out of the deploy calendar and into the contract, which is cheaper only if someone actually governs the contract.
A3. All three services were pointed at the same subscription (or the same shared queue). That made them competing consumers: the broker delivered each event to exactly one of the three, so each service saw a random slice, sized by its worker count and speed. Nothing was lost and nothing errored, which is why it went unnoticed. The fix: one subscription per service, so fraud, email, and fulfillment each receive a full copy, and use competing consumers only inside a single subscription to scale that service up. The wiring rule in one line: broadcast across services, compete within a service.
</details>11. Composes With
- Message Queue: each subscription effectively is one, and the broadcast-into-queues setup (SNS into SQS) composes the two patterns openly.
- Idempotency: every subscription is at-least-once on its own, so N consumers means N chances to double-apply.
- Event-Driven Architecture: the next lesson; pub/sub is the transport, and that lesson decides what should travel over it.
- Change Data Capture: turns a database's own change log into the event stream, the disciplined descendant of "just read my tables."
- Webhooks: this same shape stretched across company boundaries, over plain HTTP, with the guarantees renegotiated.
12. TL;DR Card
| <div style="width:85px">Problem</div> | When the producer must personally deliver to every consumer, each new team means producer deploys, slower fan-out, and crash lotteries where some teams get the event and others don't. |
| Mechanism | Publish each fact once to a topic; the broker gives every subscription its own full copy, with its own backlog, retries, and DLQ. Subscribing is configuration, and log-based brokers add replay. |
| Costs | The event schema becomes a public API (additive-only, registry-enforced), duplicates per subscription, coupling that goes invisible without a catalog, no completion signal, and retention as a cliff for slow consumers. |
| Skip it when | It's a job for one worker (queue), the producer needs the result (request-response or orchestration), or the "events" are secretly a step-by-step workflow (saga). |
| 30-second answer | Announce facts once to a topic; every subscriber consumes independently, at its own pace, with its own failure handling. Adding consumers costs the producer nothing, and history replays for newcomers. Govern the schema like the API it now is, dedupe per subscription, and orchestrate anything that's really a workflow. |
On This Page
- The Incident
- The Naive Fix, and Why It Fails
- The Pattern
- Walkthrough with Numbers
- Trade-offs: What You Pay
- When NOT to Use It
- Pattern Face-Off: Choreography vs Orchestration
- In the Wild
- The Interview Lens
- Check Yourself
- Composes With
- TL;DR Card