0% completed
Publish-Subscribe
On This Page
- The Incident
- The Obvious Fixes, and Why They Fail
- The Pattern
- Walkthrough with Numbers
- Trade-offs
- When Not to Use It
- Choreography vs. Orchestration
- Real-World Examples
- In the Interview
- Check Yourself
- Related Patterns
- TL;DR
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 have three ongoing 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 redeploy whenever any other team wants order data. Its release calendar has become the whole company's bottleneck.
- Crashes leave teams with different data. Last Tuesday, checkout crashed after writing to three of the five queues. Fulfillment shipped the order. Fraud never saw it. There is no way to make five separate queue writes succeed or fail together, so every crash leaves a random subset of teams without the event.
- Other teams' problems become checkout's problems. When the loyalty team's queue was slow for an hour, checkout's five sequential writes slowed down with it. 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." Consider what that means. The producer of the data has to personally know about, and deliver to, every consumer. Every new consumer adds work, risk, and coordination meetings. That does not scale, and the first thing to break is not the software. It is the teams.
2. The Obvious Fixes, and Why They Fail
"It works. Keep adding queues." Mechanically, yes. But checkout now owns the connections, the retries, and the delivery guarantees for every team's copy of the data. And every added queue is one more chance for a partial crash to leave teams inconsistent.
"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 did not 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 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. (This exact bug appears in the self-check.)
The diagnosis is clear. Five teams do not want five tasks done. They all want to know the same fact: an order was placed. A fact should be published 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 is 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.
The parts, one at a time:
- The topic is the named stream of facts, such as
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 problem is gone, because there is only one write. - A subscription is one team's personal copy of the stream. Internally, 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. If fraud's service is down for an hour, fraud's backlog grows. Email does not notice. Checkout does not either.
- Joining is configuration, not code. When data science wants order events, they create a subscription. Checkout does not deploy, and checkout does not even know. With a broker that keeps history (Kafka is the best-known example), the new subscriber can start by replaying past events, feeding months of history into a brand-new model.
- The event's format is now a public contract. This is the part teams underestimate. Once unknown consumers exist, the fields in your event are an API. Two rules follow. First, events state facts, not commands: "an order was placed, here are its details," never "send a receipt." The publisher reports what happened; each consumer 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 cannot see. Teams typically enforce this with a schema registry, a tool that checks event formats before they ship.
One sentence captures the idea: the producer says what happened, and every consumer decides what it means. Checkout does not know receipts exist, and that is the point.
4. Walkthrough with Numbers
The topic carries 200 orders per second. Compare the old design 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, 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 cycle, and no history at all.
- The failure drill. Fraud's consumer goes down for 40 minutes. Its subscription accumulates 200 x 2,400 seconds = 480,000 events of backlog, then drains when the service returns. Email sent every receipt on time. Checkout never noticed. In the old design, 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 waiting in its dead letter queue.
5. Trade-offs
You gain:
- The producer's cost and risk stay constant no matter how many consumers exist.
- Teams ship on their own schedules. This organizational decoupling is the biggest benefit in practice.
- Failures are isolated per subscription, and with history retention, new consumers can start with the full past.
You pay:
- Your event format is a public API now. Renaming a field breaks consumers you do not know about. 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 did not disappear; it became invisible. Mature teams keep a catalog of who subscribes to what.
- No "all done" signal. Pub/sub cannot tell you "everything that should happen for order X has happened." Each subscription only knows about itself. Workflows that need a completion answer need a different tool (see the comparison below).
- Slow consumers can lose data. History is not kept forever. A subscriber that falls behind the retention window does not just get slow; it loses events permanently. Alert on the age of each subscription's backlog.
6. When Not to Use It
- It is a job, not a fact. "Resize this image" should be done once, by one worker. That is a message queue.
- 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 does not need broadcast infrastructure. A direct queue is simpler to run and to reason about.
- Request-response rebuilt from events. Publishing
user.lookup.requestedand subscribing touser.lookup.completedis a synchronous call rebuilt out of events, with worse latency and much worse debugging. If the event name contains "request," it should have been a call.
The ecosystem-level misuse is topic sprawl: 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 deliberately, with contracts and a catalog.
7. Choreography vs. Orchestration
Pub/sub enables a style called choreography: every service reacts to published events on its own, and the overall behavior emerges from those reactions. The alternative is orchestration: one coordinator explicitly runs the workflow step by step. Interviewers ask about this distinction often.
| Choreography (pub/sub) | Orchestration (a coordinator) | |
|---|---|---|
| Who is 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 | Sequences where each step depends on the last: inventory, payment, shipping |
The test to apply: are the steps truly independent? If nobody cares whether email runs before analytics, choreography works well. But if the steps form a sequence with consequences (reserve inventory, then charge, then ship, and undo completed steps if a later one fails), that is a workflow, and it needs an orchestrator. The saga lesson covers exactly that. Real systems use both: orchestrate the order workflow itself, and have it publish facts at each step for everyone else to react to.
8. Real-World Examples
- AWS SNS feeding SQS queues is this pattern built from managed services: SNS is the topic, and each team's SQS queue is its subscription. Broadcast first, then each team gets its own task list.
- Kafka treats each consumer group as an independent subscription over a stored log, which is why it dominates this space: 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 is fire-and-forget. If a subscriber is offline, the message is gone permanently. "Pub/sub" without durable subscriptions is a much weaker promise with the same name. Always check which one you are being offered.
For AI engineers: the interaction-event topic is the central stream 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 structure: every tool call and decision is published once, and observability, evals, and audit each consume it independently, without slowing down the agent.
9. In the Interview
The direct form of the question 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 producer keeps collecting arrows. There is also a trap form: you are given a genuine workflow (order, payment, shipping) and the interviewer waits to see whether 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. 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 is the isolation working. The real danger is silent: if it falls behind the retention window, it starts losing data. So alert on backlog age, and scale that 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 category.
A mistake 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 has not been understood.
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 is added.
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 show nothing was lost, and checkout's publish count matches order volume exactly. Diagnose the setup 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 new consumer carries the costs (build idempotent processing, watch its backlog), and the ecosystem carries one more party depending on the event schema.
A2. The data coupling survives; it changes shape. Every consumer now depends on the event schema, so renaming one field breaks systems the producer cannot even list, which is worse than a compile error unless it is managed. The discipline that replaces it: 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 does not 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 subset, 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 rule in one line: broadcast across services, compete within a service.
</details>11. Related Patterns
- Message Queue: each subscription effectively is one, and the broadcast-into-queues setup (SNS into SQS) composes the two patterns directly.
- 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; that lesson decides what should travel over it.
- Change Data Capture: turns a database's own change log into the event stream, the disciplined version of "just read my tables."
- Webhooks: the same idea applied across company boundaries, over plain HTTP, with the guarantees renegotiated.
12. TL;DR
| Problem | When the producer must personally deliver to every consumer, each new team means producer deploys, slower fan-out, and partial crashes where some teams get the event and others do not. |
| Mechanism | Publish each fact once to a topic; the broker gives every subscription its own full copy, with its own backlog, retries, and dead letter queue. 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 hard limit for slow consumers. |
| Skip it when | It is a job for one worker (queue), the producer needs the result (request-response or orchestration), or the "events" are really a step-by-step workflow (saga). |
| 30-second answer | Publish 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, deduplicate per subscription, and orchestrate anything that is really a workflow. |
Flashcards Review
What is the publish-subscribe pattern?
On This Page
- The Incident
- The Obvious Fixes, and Why They Fail
- The Pattern
- Walkthrough with Numbers
- Trade-offs
- When Not to Use It
- Choreography vs. Orchestration
- Real-World Examples
- In the Interview
- Check Yourself
- Related Patterns
- TL;DR