Skip to content
intermediate Phase 8 · Messaging and Event-Driven Architecture

Pub/Sub vs Point-to-Point

Match fan-out broadcasting to topics and work distribution to queues.

45m
0 problems
Topic Progress 0%

Work Distribution: Why Only One Competing Consumer Should Get Each Job

You have 200 thumbnail-generation jobs in a queue and eight workers pulling from it. The requirement is that each image is processed once — if all eight workers were subscribers to every job, you would render 200 images eight times and pay 8x compute for the privilege. Queues solve this by construction: a message published to a queue is delivered to exactly one of its competing consumers. The broker tracks the in-flight message and holds it from others until the consumer acks, fails, or times out.

The semantics that make this safe, concretely:

publish → QUEUE (one logical list)
           ├─ consumer A  ─┐
           ├─ consumer B   ├─ COMPETING: broker delivers each message
           ├─ consumer C   │  to ONE; ack removes it; crash/timeout
           └─ ...          ┘  returns it for redelivery
⇒ scaling workers scales THROUGHPUT, not duplicate delivery

Three properties define the point-to-point contract:

Property Queue behavior Why it matters
Cardinality one consumer per message work is not multiplied; workers are interchangeable
Visibility/ack broker holds the message while in flight crash before ack ⇒ redelivery to a consumer, not all
Fan-out none by design a second queue (or a fan-out bridge) is required for other teams' needs

The last row is where teams get burned. A payments team puts payment.settled on a queue because 'a queue is for messages'. The fraud team also wants those events — so they attach a second consumer to the same queue and now every settlement is either consumed by fraud or by the ledger, never both: two subscribers competing for one stream is a routing bug that looks like an outage weeks later (one consumer silently starves the other; the missing data shows up as a reconciliation gap, not an error). The fix is structural: one queue per competing work pool, and independent delivery per interested party.

On which broker carries this: RabbitMQ queues (one message, one consumer, ack-based redelivery), SQS standard/FIFO queues (identical model; FIFO adds per-MessageGroupId ordering and content-based deduplication), and — this trips people up — Kafka partitions behave like queues for messages with the same key but only when a single consumer group is attached. One consumer group per work pool: within the group, partitions are split among members so each record goes to one member; that is your competing-consumers pool. (More in chapter 2.)

Sizing rules of thumb for a work queue: number of concurrent workers should be bounded by downstream capacity (a queue hides bursts; it must not be permission to overrun the database), redelivery timeout must exceed job duration, and work must be idempotent because at-least-once is the default everywhere (Message Queues and Brokers covers the ack mechanics). In interviews, the one-line discriminator to say first: a queue answers 'who should do this work?', a topic answers 'who needs to know this happened?' One follow-up worth being ready for: how many workers should a queue have? Size it from downstream capacity, not from CPU headroom — a queue absorbs bursts, it does not license an unbounded number of concurrent database writers — and remember that adding workers never adds per-message ordering; the ordering key lives in the data, not in the pool.

Fan-Out: One Publish, Five Subscriptions, Each Getting Its Own Copy

order.placed fires once, and five teams care: the warehouse reserves stock, analytics increments revenue, the recommendation model trains, email sends a receipt, and fraud scores the order. Wire these as competing consumers and each event triggers only one of the five — inventory would randomly miss orders while email 'sends receipts' for shipments it never saw. The publish-subscribe pattern exists for exactly this: one publish, one copy delivered to every active subscription.

How each broker realizes fan-out differs, and the differences matter operationally:

Broker Unit of subscription Independent progress Consumer crash behavior
Kafka consumer group (group id = subscription) offsets per group per partition group resumes at its own committed offset; other groups unaffected
RabbitMQ queue bound to an exchange each queue has its own backlog unacked message requeued to that queue only
Pub/Sub (GCP) subscription ack deadline per subscription redelivers to that subscription only
SNS / EventBridge subscriber endpoint fan-out at publish time retry policy per subscriber with DLQ

The key insight for Kafka users: a topic is inherently publish-subscribe; the queue semantics emerge from how many consumer groups you attach. orders topic with one group invoice-workers = work queue. The same topic with groups warehouse, analytics, email = five independent streams, each replaying from its own offset. This dual behavior is Kafka's most misunderstood property — and it is why adding a new consumer group can backfill months of retained history for free, while adding a member to an existing group only redistributes partitions.

Now the cost side of fan-out, because interviews should include it:

  • Storage multiplies per subscription lag. Retention is on the log, but each slow group builds its own backlog; a group stalled for 6 days on a 7-day retention topic loses records when retention evicts them — silently, with no consumer error.
  • Slow subscribers do not block publishers (that is the point), but they do accumulate cost: Kafka with 12 groups means 12x the reads and the slowest group dictates nothing except its own staleness.
  • Fan-out depth is a coupling graph. Ten topics × eight subscriptions = 80 edges to reason about when a schema changes (Schema Evolution is where that bill comes due). A topic map drawn on one page is a design artifact worth having.
  • Ordering is per partition per group. Two groups may process the same events in different wall-clock orders (each follows its own lag), so cross-team invariants cannot rely on 'everyone saw X before Y'.

RabbitMQ's shape deserves one extra sentence: producers publish to an exchange, and bindings decide which queues receive copies — one exchange with N bound queues is classic fan-out, while a direct exchange with one queue per routing key gives work distribution per key. The exchange-plus-bindings layer is what lets RabbitMQ do both patterns without changing the producer.

Interview formulation: name the cardinality question first — how many interested parties must receive this message? One, and it is a queue (or one consumer group). Many, and it is a topic with one subscription per party. Getting this backwards produces one of two symptoms: duplicate work (everyone processed it) or missing consumers (nobody in particular processed it) — the failure catalog in the final chapter.

Wrong Pattern, Wrong Symptom: How a Miswired Consumer Shows Up Weeks Later

The bug reports look unrelated. Team A reports that invoices are missing for 3% of orders; Team B reports processing the same shipment twice and double-charging freight; Team C reports a consumer that has processed nothing since Tuesday while its dashboard stays green. All three trace to one mistake each: A attached a second consumer to a queue that already had one, B let every worker create its own consumer group on the shared topic, and C subscribed a consumer group that no producer ever publishes under. Messaging errors are diagnosable once you know which symptom each wiring produces.

The failure catalogue:

COMPETING CONSUMERS ON A FAN-OUT NEED          → MISSING CONSUMER
  payments queue: ledger and fraud both pull   → each settlement goes to ONE
  symptom: partial data, no errors, reconciliation gaps found downstream

EACH WORKER IN ITS OWN GROUP                   → DUPLICATE WORK
  order topic, 5 workers, 5 group ids           → every order handled by all 5
  symptom: 5x processing cost, double-reserved stock, racing updates

ORPHAN SUBSCRIPTION                            → SILENT STALL
  group id 'analytics-v2' but producers publish only to 'events.v1'
  symptom: consumer healthy, lag 0, no data — because there is no data

PER-SUBSCRIBER FAILURE                         → CROSS-TEAM CONTAGION
  one webhook subscriber down on a shared topic → its retries absorb broker capacity,
  its DLQ fills, and unrelated groups inherit latency

A decision procedure you can run in a design review, taking one minute:

  1. Who are the recipients — one or many? If you must enumerate the interested parties today and expect a sixth next quarter, it is fan-out (topic/exchange with one subscription each). If recipients are interchangeable workers of one service, it is a queue (or one consumer group).
  2. What happens if two parties get it? Duplication tolerated (idempotent projection)? Then a topic with multiple groups is safe by definition — each group sees it exactly once (per its own offsets). Not tolerated (money movement)? Then ownership must be exclusive: a queue, or a single-writer consumer group with a lock.
  3. What happens if a party misses it? If missing is unacceptable, the subscription needs its own retention-based replay and a lag alarm — a bound exchange with no queue (RabbitMQ transient bindings) delivers only to currently attached consumers, which is the classic 'we weren't running at publish time' loss.
  4. Is the delivery contract written down? Per group: at-most-once, at-least-once, ordering key, retention vs max lag. Undocumented contracts are how a schema change later breaks a consumer you never knew existed.

Note the asymmetry in those answers: a queue fails closed (if no worker is up, messages accumulate; if a worker is up, someone owns each message) while a fire-and-forget subscription fails open (if the subscriber is down at publish time with transient delivery, the message is simply gone). That is why the same product may use a topic to announce payment.settled and a queue to dispatch settle-payment work — announce once to many, assign work to one.

In an interview you can demonstrate this whole model in three sentences: state the cardinality question, name the two symptoms (duplicate work vs missed consumers), and point out that Kafka supports both over one topic depending on consumer groups — then say which one your feature uses and why. From here the roadmap generalizes the producer/consumer side into Event-Driven Architecture Patterns.

Quiz

1. A payments queue has two consumer groups attached, 'ledger' and 'fraud'. What happens to a newly published payment?

Question 1 options

2. Three services must each receive every 'order.placed' event, and each processes at its own pace. Which wiring is correct?

Question 2 options

3. Your analytics consumer group has processed zero messages for two days while reporting healthy and lag 0. What is the most likely cause?

Question 3 options

4. An order-processing work pool was accidentally built with each worker creating its own Kafka consumer group id instead of sharing one. What happens?

Question 4 options

Flashcards

Question

Point-to-point queue vs publish-subscribe topic: the one question that decides

Answer

Ask 'how many interested parties must receive this message?'. One (interchangeable workers doing the job) → queue/competing consumers: exactly one consumer gets each message. Many (independent teams/services each needing a copy) → topic with one subscription per party: every subscriber gets its own copy with independent progress.

Question

How does Kafka express both patterns over one topic?

Answer

Consumer groups are the subscription unit. One group with several members = competing consumers (partitions split, each record to one member) = queue semantics. Several groups = fan-out (each group reads all records from its own offsets) = pub/sub semantics. Offsets are per group per partition, so subscriptions never interfere.

Question

What two classic symptoms come from choosing the wrong pattern?

Answer

Competing consumers attached where subscribers were needed → missed consumers: each message handled by only one party, showing up as partial data and reconciliation gaps with no errors. Subscribers attached where competitors were needed → scattered/duplicate work: overlapping or unfilled processing depending on membership, with orders randomly owned by nobody in particular.

Question

Why does RabbitMQ need an exchange and bindings to support both patterns?

Answer

Producers publish to an exchange; bindings decide which queues receive copies. One queue bound = work distribution (competing consumers on that queue). N queues bound to one exchange = N independent subscriptions with their own backlogs and acks. Without bindings there is no fan-out — and transient bindings fail open, dropping messages published while no subscriber is attached.

Question

What is the risk of a slow consumer group on a Kafka topic with 7-day retention?

Answer

Retention is independent of consumption, so if the group's lag exceeds 7 days its unread records are deleted underneath it — data disappears without a consumer error, surfacing only as a gap in its output. Fix: alarm on lag age (not just offset count), extend retention for critical topics, or reprocess from a backup/archive.

Revision Notes

Key Takeaways

  • 1. The pattern question is cardinality: one owner of the work versus many interested parties — answer it before choosing a broker feature
  • 2. Kafka implements both patterns through consumer groups, which is why 'is this topic a queue?' is unanswerable without naming the groups
  • 3. Wrong-pattern bugs fail silently: missing consumers look like reconciliation gaps and miswired work pools look like randomly owned orders
  • 4. Fan-out subscriptions each own their lag, and lag that exceeds retention deletes data without a consumer error
  • 5. Queues fail closed (backlog accumulates) while transient subscriptions fail open (messages gone at publish time) — pick per your loss tolerance

Interview Tips

  • • "Queue or topic for this feature?" — answer with the recipient question, then the double-processing test (if two parties must not both act, ownership must be exclusive) and the miss test (if missing is unacceptable, durable subscription plus replay)
  • • "How do you scale order processing horizontally?" — competing consumers in one group/partition assignment, idempotent handlers, and lag-based autoscaling; add that adding members rebalances rather than duplicates
  • • "Why does my consumer show lag 0 but no data?" — orphan subscription: group or topic mismatch; show you check producer publish targets before suspecting the broker
  • • "Explain Kafka's dual behavior" — one group = queue semantics, several groups = pub/sub, offsets per group per partition, and a new group can replay retained history — a capability interviewers like to probe

Cheat Sheet

Pub/Sub vs Point-to-Point

The deciding question: who must receive it?

ONE (work)      → QUEUE / competing consumers: broker delivers each message to exactly
                  one consumer; ack removes; crash ⇒ redeliver to A consumer
                  ⇒ scaling workers scales throughput, not duplication
MANY (notify)   → TOPIC / subscriptions: every subscription gets its own copy with
                  independent offsets/backlog and independent lag
Broker Subscription unit Per-subscriber state
Kafka consumer group offsets per group per partition (one group = queue, many = fan-out)
RabbitMQ queue bound to exchange per-queue backlog + acks
GCP Pub/Sub subscription per-subscription ack deadline
SNS/EventBridge subscriber endpoint publish-time fan-out + per-subscriber retries/DLQ

Symptom → diagnosis:

missing downstream data (no errors)  → competitors where subscribers were needed
stalled/duplicated work               → subscribers where competitors were needed
green consumer, lag 0, no data        → orphan subscription (topic/group mismatch)
one subscription's outage hurting all → shared broker capacity + unbounded retries

Decision procedure: (1) enumerate recipients today and next quarter → many = topic · (2) double-processing unacceptable → exclusive ownership: queue or single group with locking · (3) missing unacceptable → durable subscription + replay + lag alarm (transient bindings fail open) · (4) write down the delivery contract per subscriber: ack mode, ordering key, retention vs max lag.

Kafka gotchas: new group backfills retained history for free · lag beyond retention = silent data loss · ordering per partition per group · one hot key = one partition.

Continue Learning