20. Distributed Message Queue (Build Kafka) Advanced + Distributed Systems Capstone

A. Realistic Interview Prompt

"Every design in this document that needed to decouple a producer from a consumer — Notification System's ingest-and-fan-out pipeline, Ad Click Aggregation's firehose of raw clicks — reached for a message queue and moved on, treating it as a trustworthy black box that simply works. Now design that black box itself: a distributed message queue, in the shape of Kafka, that a fleet of independent producer and consumer services could actually be built on top of, at a sustained throughput of hundreds of thousands of messages per second, durable across broker failures, with multiple independent teams able to read the identical stream of events at their own pace without stepping on each other."

Every prior problem that used a queue got to assume the answer to the question this problem exists to actually answer: what does "durable, ordered, fan-out-capable, at-least-once" concretely require underneath the API call. The central technical decision this design is built around is a topic modeled not as a single traditional queue but as a set of independent, ordered, append-only, replicated logs called partitions; producers choose a partition per message (by key hash or round-robin) to get real write parallelism while preserving order for anything that needs it; consumers are organized into independent consumer groups that each track their own position (offset) in the log rather than the broker deleting a message once it's been read; each partition is served by one leader broker plus follower replicas kept in sync via a replication protocol structurally similar to the leader-election and consensus machinery in Prerequisites E and Prerequisites F; and the system is honest about defaulting to at-least-once delivery, explicitly pairing that default with the idempotency pattern (Prerequisites O, generalized in Patterns Playbook E) as the only real way to get effectively-once processing, rather than pretending the broker alone can hand you exactly-once for free. A candidate who reaches for "a table with a processed boolean column" here isn't missing an optimization — they're rebuilding, badly, the one structure every other problem in this document has been quietly relying on.

B. Difficulty

Advanced, and deliberately the hardest, most synthesis-heavy problem in this document. Nothing in this problem is conceptually novel in isolation — partitioning is Prerequisites C's sharding applied to a log instead of a database; replication is Prerequisites C's leader-follower model applied per-partition; leader failover is Prerequisites F's leader election; the delivery-semantics honesty is Prerequisites O's idempotency argument stated at its origin instead of at one of its many destinations. What makes this the capstone is that it requires holding all of those previously-separate ideas in the same design simultaneously, and recognizing that this is the one problem in the document where "the message queue" isn't a dependency to wave at — it is the entire system.

C. What This Problem Tests

D. Requirements

Functional

  1. createTopic(name, partition_count, replication_factor) — provision a new topic with a fixed number of partitions and a target replication factor
  2. produce(topic, key, value, headers) → {partition, offset} — append one record to a topic, letting the producer or the broker choose which partition it lands on (R)
  3. subscribe(consumer_group_id, topics) + poll() → a batch of unconsumed records from whichever partitions this consumer instance has been assigned within its group (P)
  4. commitOffset(consumer_group_id, topic, partition, offset) — durably record how far a given consumer group has progressed through a given partition (P)
  5. Support many independent consumer groups reading the same topic concurrently, each entirely at its own pace and its own committed offset, with no coordination required between groups (P)

Non-Functional

Out of scope: cross-datacenter/geo-replication between independent clusters (a real, separate problem — mirroring an entire cluster's topics to another region, briefly named in AA); a schema registry or schema-evolution enforcement for record payloads; end-to-end exactly-once processing of a side effect that reaches outside the message queue itself (deliberately addressed at the consumer via idempotency instead of promised by the broker, Q); tiered/cold-storage offload of expired segments to object storage (a real production feature, structurally similar to P16-K's cold-storage archival, but not this problem's central concern).

E. Clarifying Questions

#QuestionWhy It MattersDesign Decision Affected
1Is ordering required globally across an entire topic, or only relative to some natural key (a specific user, a specific order)?Determines whether a single giant partition is ever actually necessary, or whether key-based partitioning safely preserves the ordering that's actually neededDeep Dive 1 (M), Consistency Model (V)
2What's the acceptable durability-versus-latency trade for an acknowledged write — is a leader-only acknowledgment enough, or must every in-sync replica confirm first?Directly sets the producer's acks level and the resulting write-latency/durability curveDeep Dive 3 (O)
3Do multiple independent downstream systems need to read the exact same event stream, each at its own pace, or is this genuinely a single producer-to-single-consumer handoff?The entire justification for retaining messages after they're read instead of deleting them on consumptionDeep Dive 4 (P), Basic Design (J)
4Is true exactly-once processing of the resulting side effect actually required end-to-end, or is at-least-once delivery paired with an idempotent consumer acceptable?Determines whether the design needs to lean on Kafka's own transactional API (Kafka-to-Kafka only) or on consumer-side idempotency (works for any side effect, anywhere)Deep Dive 5 (Q)
5Does this topic's data need to remain replayable as a transient event stream, or does it represent the latest current state of some keyed entity that should never fully disappear?Decides between time/size-based retention and log compaction as the topic's retention strategyDeep Dive 7 (S)

F. Assumptions

G. Capacity Estimation

MetricValue / CalculationWhy It Matters
Peak cluster-wide produce throughputIllustratively 1,000,000 messages/sec cluster-wide, average message size ~1 KB ≈ ~1 GB/sec raw ingestThe aggregate write volume every partition, broker, and disk in the cluster collectively has to absorb without the ingest path itself becoming the bottleneck
Partitioning for parallelismOne large topic split into 200 partitions across a 50-broker cluster ≈ 4 leader partitions/broker on average ≈ ~5,000 msgs/sec per partition on averageA single partition's realistic sequential-write ceiling is tens of thousands of 1 KB messages/sec on modern disk hardware (N) — 200 partitions gives real headroom and, just as importantly, upper-bounds any one consumer group's parallelism at 200 concurrent consumers (P)
Storage growth (retention × replication)Raw daily ingest ≈ 1 GB/s × 86,400s ≈ 86.4 TB/day; × replication factor 3 ≈ ~259 TB/day written across the cluster; × 7-day default retention ≈ ~1.8 PB resident on disk cluster-wideConfirms storage, not raw write throughput, is the resource that most directly bounds this design at scale — and why retention (S) is a first-class, per-topic decision rather than an afterthought
Replication write amplificationEvery record physically written 3× (once by the leader, then fetched and written again by each of 2 followers)The direct, honest cost of durability (O) — replication factor 3 doesn't just add storage, it triples the total disk-write and network-replication load the cluster carries for every single record
Independent consumer fan-outIllustratively 3 independent consumer groups (e.g. a notification pipeline, an aggregation pipeline, an audit/archival consumer) each reading the same topic in full ≈ ~3 GB/sec aggregate read throughput cluster-wideConfirms this is a read-amplifying system by design — every additional independent consumer group is free to add at the broker's write side (P), because none of them require a second copy of the data or coordination with each other, which is precisely what a traditional delete-on-consume queue could not offer (J)

H. Core Entities

EntityPurposeKey Fields
TopicA named logical stream, divided into a fixed number of partitions at creation timetopic_name, partition_count, replication_factor, retention_policy (S)
PartitionOne independent, strictly ordered, append-only log — the actual unit of parallelism, ordering, and replication in this designpartition_id, leader_broker_id, replica_broker_ids, in_sync_replica_set (O), high_watermark
RecordOne unit of data appended to a partition — the atomic thing every produce/consume call actually moveskey, value, headers, partition, offset (its position within that partition's log), timestamp
BrokerOne server in the cluster, hosting the leader and/or follower replicas of some subset of partitions across all topicsbroker_id, hosted_partition_replicas
ConsumerGroupA named set of cooperating consumer instances that split a topic's partitions among themselves, independently of every other group reading the same topicgroup_id, member_ids, partition_assignment (P)
ConsumerGroupOffsetThe durably committed position of one consumer group within one partition — the entire mechanism that replaces "the broker deletes it once it's read"group_id, topic, partition, committed_offset — itself stored as a keyed record in an internal, compacted topic (S)

I. API Design

POST /topics                          { name, partition_count, replication_factor }  -> { topic_name }         -- provisioning (K)
POST /topics/{topic}/records          { key, value, headers }  -> { partition, offset }                       -- append to a partition's leader (M, N)
(client library) subscribe(group_id, topics); poll(timeout_ms)  -> [ { partition, offset, key, value } ]      -- pull-based fetch from assigned partitions (P)
(client library) commitOffset(group_id, { topic, partition, offset })  -> ack                                 -- durably advances the group's position (P)
GET  /topics/{topic}/partitions/{p}/watermarks  -> { low, high }                                              -- internal/admin: retention boundaries (S)

Why produce returns {partition, offset} directly, inline with the write: a producer that cares about ordering or replay position needs to know exactly where its record landed — returning it as part of the same acknowledgment (computed off the same append the write just performed, O) avoids a separate round trip for information the producer almost always wants immediately, the same reasoning P19-I applies to returning a leaderboard rank inline with a score update.

Why commitOffset is a separate call from poll, rather than the broker automatically advancing the offset the instant a batch is fetched: automatically committing on fetch would mean a consumer that crashes mid-processing has already told the broker it's done with records it never actually finished — silently losing them (at-most-once, by accident). Making the commit an explicit, separate call lets the consumer control exactly when its progress becomes durable relative to when its own processing actually completes, which is the entire lever Deep Dive 5 (Q) turns to get at-least-once instead of an accidental, weaker guarantee.

J. Basic / Naive High-Level Design (V1)

The simplest thing that satisfies the functional requirement in isolation: a single-node, in-memory queue, using the traditional broker model described in Prerequisites G — a producer appends a message to an in-memory list; a consumer pulls the next message off the front of that list and, once it acknowledges having processed it, the message is deleted from the list entirely.

flowchart LR Producer["Producer"] --> App["Single App Server
(in-memory list)"] App -- "consumer pulls next message,
message deleted on ack" --> Consumer["Consumer"]

V1 — one process, one in-memory list, one consumer removing each message the moment it's handled. Correct for a single producer talking to a single consumer, and every one of its simplifying assumptions is exactly what the rest of this problem exists to remove.

Why this breaks on multiple, independent, structural fronts at once, not just "under load": (1) it has no persistence at all — a crash of the one app server loses every message still sitting in that in-memory list, acknowledged-to-nobody or not; (2) it has no parallelism — one list, appended and drained by single-threaded access, cannot exceed one machine's throughput no matter how it's tuned; but most importantly, (3) the delete-on-consume step itself is the actual design flaw, not an implementation detail — the instant a second, entirely independent consumer (a second team's analytics pipeline, say) also wants to read the same stream of events, there is nothing left for it to read, because the first consumer's acknowledgment already erased it. This is precisely the traditional-queue model Prerequisites G contrasts with Kafka's log-based one, and it's the single assumption that has to be discarded before anything else in this problem can be solved — not sharded, not replicated, not scaled: replaced, with a model where reading a message and deleting it are no longer the same event.

K. Data Model & Store Choice

The core structure: a partition is not a queue in the traditional sense at all — it's a durable, append-only log, physically backed by a sequence of segment files on disk. A new record is always appended to the current active segment's tail; once a segment reaches a configured size or age, it's sealed and a new active segment is opened. A separate, sparse index file per segment maps a subset of offsets to their exact byte position in the segment, so locating any given offset for a read is a cheap index lookup plus a short scan forward, never a full scan of the segment from the beginning.

Why this specific structure, and not a general-purpose database table keyed by message id: every operation this design actually needs — append a record, read forward sequentially from a given offset — is naturally served by a structure whose entire layout is the append order, the same "let the data structure's native shape answer the actual access pattern" reasoning P19-K applies to a sorted set for ranked reads. A relational table with a monotonic id column can technically express the same thing, but every insert pays for index maintenance and every read pays for an index traversal; a log's "next record" is always physically adjacent to the one before it, so both operations are close to the cheapest thing a disk can do at all — this is the identical reasoning P16-M gives for landing raw ad clicks in Kafka instead of a relational database, examined here from the broker's own implementation rather than from a consumer's choice to use it.

Sequential I/O and the OS page cache, stated concretely: a sequential append is, mechanically, one contiguous write to the end of a file — no index to update, no row lock to acquire, no risk of a page split. Reads benefit the same way: because a partition's recent segments are also its most recently written ones, they tend to already be sitting in the operating system's page cache, and many broker implementations can serve a consumer fetch via a zero-copy transfer straight from that page cache to the network socket, without ever copying the bytes through user-space memory at all. Both the write path and the read path are, deliberately, doing close to the least amount of work a general-purpose database could ever do for the same operation.

L. End-to-End Flow — A Producer Publishes a Message, It's Replicated to Followers, and a Consumer Group Reads and Commits It

sequenceDiagram participant P as Producer participant L as Partition Leader (Broker A) participant F1 as Follower (Broker B) participant F2 as Follower (Broker C) participant C as Consumer (Group G) P->>L: produce(topic, key, value) L->>L: append to active segment, assign offset N (K) L->>F1: replicate record N L->>F2: replicate record N F1-->>L: fetched + written, ack F2-->>L: fetched + written, ack L->>L: advance high watermark to N (O) L-->>P: { partition, offset: N } Note over C,L: some time later, independently of the produce path C->>L: poll() -- fetch from last committed offset L-->>C: records up to high watermark (never beyond it) C->>C: process record N C->>L: commitOffset(group=G, partition, offset=N) L-->>C: ack

The producer's write is acknowledged only once the record has actually reached the in-sync replica set (O), not merely the leader's own disk. The consumer's fetch and offset commit happen on an entirely independent schedule — nothing about the produce path waits for any consumer, and nothing about the consumer path deletes the record once read (P), which is exactly what lets a second, unrelated consumer group replay the identical record days later.

M. Deep Dive 1 — Partitioning a Topic for Parallelism

Requirement this addresses: horizontal scalability and high sustained throughput (D) — the direct fix for V1's single-list ceiling (J), and the structural decision that makes every later deep dive in this problem possible at all.

Why a topic is a set of partitions, not one shared log: a single append-only log, however well-implemented, is still fundamentally served by one leader machine's disk and network interface — exactly the same ceiling Prerequisites C names generally for any single-machine data store. Splitting a topic into many independent partitions, each with its own leader (potentially on a different broker), spreads both the write load and the storage across the cluster, and — just as importantly — gives a consumer group real parallelism: since each partition within a group is consumed by exactly one member of that group at a time (P), the partition count is the hard upper bound on how many consumer instances in one group can usefully work in parallel at all.

Key-based partitioning, and the ordering guarantee it actually provides: a producer that supplies a key has that key hashed and mapped, deterministically, to one specific partition — partition = hash(key) % partition_count — which means every record sharing that key always lands on the same partition, in the order it was produced, and is therefore read by a consumer in that same relative order. This is a deliberately narrower guarantee than "the whole topic is ordered": two records with different keys can land on different partitions and be consumed in either relative order, or even concurrently, with no guarantee either way (E's clarifying question 1, V).

Why changing the partition count later is a genuinely disruptive operation, unlike consistent hashing's minimal-disruption property (Prerequisites J): hash(key) % partition_count is a plain modulo, not a consistent-hashing ring — increasing partition_count changes the result of that modulo for the overwhelming majority of existing keys, meaning a key's future records can start landing on a different partition than its past records did. This silently breaks the exact per-key ordering guarantee this deep dive exists to provide, for any key whose new partition assignment differs from its old one. This is precisely why partition count is chosen deliberately upfront, sized for projected growth (G), rather than treated as a value that can be casually increased the way a stateless service's instance count can — the same "the shard key and its stability matter more than convenience" lesson Prerequisites C and Patterns Playbook G draw generally, sharpened here into a concrete, avoidable failure mode.

N. Deep Dive 2 — Durable, Append-Only Log Storage per Partition

Requirement this addresses: durability and high sustained write throughput simultaneously (D) — making K's data-structure choice concrete in terms of actual disk and network cost.

The direct, quantitative reason sequential writes win at this workload: a spinning or solid-state disk's sustained sequential write throughput is dramatically higher than its random-write throughput, because a sequential write never requires a seek and rarely requires more than appending to the same open file handle; a relational insert with even a couple of secondary indexes pays for scattered, random-access index maintenance on every single write. At hundreds of thousands of writes/sec cluster-wide (G), that per-write random-access overhead is exactly what would make a general-purpose indexed database infeasible here and what makes a segment-file log tractable — the identical trade-off P16-M names for exactly the same reason, one level up the stack.

Producer-side batching, compounding the same benefit: rather than issuing one network round trip and one disk append per individual record, a well-tuned producer briefly buffers several records destined for the same partition and sends them as a single batch (a small, configurable linger delay in exchange for meaningfully larger, more efficient batches) — both the network call and the resulting disk append amortize their fixed overhead across many records instead of paying it per record, the same batching-for-efficiency trade P19-R's per-player write coalescing makes, applied here to raw throughput rather than to redundant intermediate values.

The honest cost of the default flush policy, named rather than glossed over: a broker does not fsync every single append to physical disk before acknowledging it by default — doing so would collapse throughput to whatever the disk's raw fsync latency allows. Instead, the operating system's own page cache absorbs writes and flushes them to disk on its own schedule, while durability against a process crash (not a full machine power-loss event) is covered by the fact that the data has already left the broker process and is sitting in the OS's memory, and durability against a full machine failure is covered separately, by replication (O) rather than by the local disk's flush timing at all. This is a deliberate division of labor: replication is what actually protects a record from loss; local disk flush timing is tuned purely for throughput.

O. Deep Dive 3 — Replication of Partitions

Requirement this addresses: fault tolerance without silent data loss or extended unavailability (D) — Prerequisites C's leader-follower replication model, applied at the granularity of one partition rather than one whole database.

The model: every partition has exactly one leader replica, hosted on one broker, and some number of follower replicas, each hosted on a different broker. Followers replicate by continuously pulling new records from the leader (a pull-based design, not the leader pushing to followers) and appending them to their own local copy of the same log, in the same order. The in-sync replica set (ISR) is the subset of a partition's replicas — the leader plus whichever followers have not fallen behind the leader by more than a configured lag bound — that are considered genuinely caught up right now.

Acknowledgment levels, and the direct durability/latency trade each one makes:

Producer acks settingWhat "acknowledged" meansTrade-off
acks=0Fire-and-forget — the producer doesn't wait for any broker confirmation at allLowest possible latency, but a record can be silently lost if the leader never even receives or persists it — acceptable only for traffic where an occasional silent loss is genuinely tolerable
acks=1The partition leader has appended the record to its own local logFast, but if the leader crashes before any follower replicated that record, it's lost the instant a follower is promoted (below) — a real, if narrow, data-loss window
acks=all (chosen default for anything that must not be lost)Every member of the current in-sync replica set has replicated the recordHigher latency (waits on the slowest ISR member, not just the leader), but a record survives the loss of any single replica in the ISR, including the leader itself — the correct default whenever E's clarifying question 2 answers "durability matters more than shaving off a few milliseconds"

The high watermark, and why a consumer never sees an under-replicated record: the leader tracks the highest offset that has been confirmed replicated to every current ISR member — the high watermark — and only ever exposes records up to that offset to consumers, even though the leader's own local log may already physically contain slightly newer, not-yet-fully-replicated records. This is the same "don't expose a write until it's actually safe" discipline a consensus protocol's commit index enforces (Prerequisites E's ZAB/Raft mention) — it guarantees a consumer can never read a record that could still vanish if the leader fails one instant later.

What happens on leader failure, and the one unsafe shortcut it must never take: a cluster controller — itself a singleton role elected the same lease-based way Prerequisites F describes generally, backed by the same kind of consensus quorum Prerequisites E names for ZooKeeper/etcd — detects a leader's failure via a missed heartbeat, and promotes a new leader exclusively from the current ISR. Promoting a replica that was not in the ISR (one that had fallen meaningfully behind) would silently truncate the partition's history to whatever that lagging replica happened to have — a real, quiet data-loss event dressed up as a successful failover. Allowing exactly this ("unclean leader election") is a real, explicit, and dangerous configuration knob in production systems, named here deliberately as a bad-design candidate (Z) rather than a reasonable default.

P. Deep Dive 4 — Consumer Groups & Offset Tracking

Requirement this addresses: the exact requirement V1's delete-on-consume model failed outright (D, J) — multiple independent consumer groups reading the same topic, each at its own pace, without coordinating with each other at all.

Why the broker never deletes a record on consumption: a record, once committed (O), simply stays in the log until it's removed by retention or compaction (S) — entirely independent of whether, or how many times, or by whom, it's been read. "Reading" a record is nothing more than a consumer advancing its own bookmark forward through an otherwise untouched log. This single decision is what let P12's per-channel worker pools and P16's aggregation, fraud-detection, and cold-storage-archival consumers all independently read the identical stream of events without any of them needing to know the others existed — the exact fan-out those two problems assumed already worked, built here for real.

Offsets, concretely: each ConsumerGroup (H) tracks, for every partition it consumes, the offset of the next record it hasn't yet processed. That committed offset isn't kept in application memory alone — it's durably persisted as a keyed record (group_id, topic, partition → committed_offset) in an internal topic that is itself just an ordinary Kafka topic, using log compaction (S) so only the latest committed offset per key is ever retained. A consumer that crashes and restarts simply reads its own last committed offset back out of that internal topic and resumes exactly where it left off — no re-scan of the entire topic history required.

Partition assignment, and the parallelism ceiling it implies: within one consumer group, each partition is assigned to exactly one member at a time — two members of the same group never process the same partition concurrently, which is precisely what keeps per-partition ordering (M) meaningful to a consumer at all. This also means a group's real, useful parallelism is capped at the topic's partition count (M) — adding an 11th consumer to a group consuming a 10-partition topic simply leaves that 11th consumer idle.

Commit strategy, and the direct trade Q inherits from it: committing an offset after a record has been fully processed is the safe default — a crash between processing and committing simply causes that record to be redelivered and reprocessed on restart (at-least-once, the honest default, Q). Committing before processing starts inverts the risk: a crash mid-processing leaves the offset already advanced past a record that was never actually finished, silently skipping it (at-most-once, chosen only when losing an occasional record is genuinely more acceptable than ever reprocessing one).

Rebalancing, when group membership changes: when a consumer joins, leaves, or is judged dead (a missed heartbeat), the group coordinator reassigns partitions among the group's remaining members. A naive ("eager") rebalance revokes every partition from every member before reassigning anything, pausing the entire group's consumption briefly even for members whose own assignment doesn't actually change; incremental/cooperative rebalancing narrows that pause to only the specific partitions actually being reassigned, letting unaffected members keep consuming throughout.

Q. Deep Dive 5 — Delivery Semantics: At-Least-Once by Default

Requirement this addresses: honest, configurable delivery semantics (D) — the single most consequential piece of intellectual honesty this entire design has to get right, and the reason every prior problem in this document that consumed from a queue was already quietly building on top of it.

Why end-to-end exactly-once is, in the most literal sense, not achievable for an arbitrary external side effect: there are at least two independent places duplication can be introduced. On the produce side, a network timeout after a successful append but before the producer receives its acknowledgment leaves the producer unable to distinguish "it worked but the ack was lost" from "it never happened" (Prerequisites O's exact framing) — a naive retry can produce a duplicate record. On the consume side, a consumer that crashes after fully processing a record's side effect but before committing its offset (P) will, on restart, be redelivered that same already-processed record, because its last committed offset still points to before it. Kafka's own idempotent-producer feature (deduplicating retried produce attempts via a per-partition producer id and sequence number) and its transactional API (genuinely exactly-once for a read-process-write pipeline that stays entirely within Kafka) both close real gaps — but only up to Kafka's own boundary. The instant a consumer's side effect leaves that boundary — charging a card, sending a push notification, writing to a completely different external database — no broker-side mechanism, however sophisticated, can make that external side effect exactly-once by itself, because the broker has no visibility into, or control over, what happens after the message leaves it.

The honest, achievable substitute, and where this document has already been relying on it: pair at-least-once delivery (this problem's actual, honestly-stated guarantee) with an idempotent consumer (Prerequisites O, generalized as its own named pattern in Patterns Playbook E) — a stable key attached to each unit of work, checked before the side effect runs, so a redelivered record produces a duplicate delivery but never a duplicate side effect. This is not a new idea introduced here for the first time; it's the exact mechanism P12's Deep Dive P already named explicitly — "the queue itself only guarantees at-least-once delivery to a worker... a worker can crash after calling a provider but before acknowledging the queue message, causing the same enqueued message to be redelivered" — and the exact reason P16 deduplicates click events using a key established at click-generation time, before they ever reach the aggregation layer. Both of those designs assumed this problem's guarantee already existed and built the correct defense against it. Effectively-once, in practice, is at-least-once delivery plus an idempotent consumer — it is not, and cannot be, a property the broker hands anyone for free, on its own.

R. Deep Dive 6 — Producer Partitioning Strategy

Requirement this addresses: giving a producer real control over the ordering-versus-load-balance trade (D, M) — the concrete mechanics behind M's "producers choose a partition" decision.

StrategyHowTrade-off
Key-hash (default when a key is supplied)partition = hash(key) % partition_count — deterministic, same key always → same partitionPreserves per-key ordering (M), the correct choice whenever any downstream consumer needs "everything about this one entity, in order" — but a single disproportionately hot key floods one partition while every other partition sits comparatively idle, the identical hot-key skew P16-R already solves for a viral ad's click volume by salting the key before it ever reaches the partitioner
Round-robinSuccessive records (with no key, or deliberately ignoring any key) are spread evenly across every partition in turnNear-perfectly uniform load balance across partitions and brokers — but provides no ordering guarantee whatsoever between any two records, even ones that are logically related, since consecutive messages for the same real-world entity can land on, and be consumed from, entirely different partitions in any relative order
Sticky (batch-aware) partitioningFor keyless traffic, stay on one partition for a short batching window (matching N's producer-side batch), then rotate to the next partitionMeaningfully larger, more efficient batches per partition than pure round-robin (which spreads even a tiny burst across every partition immediately, preventing any one batch from growing large) — at the cost of a brief, bounded skew toward whichever partition is "current," self-correcting on the next rotation, and still provides no ordering guarantee, since it exists purely for keyless traffic

S. Deep Dive 7 — Retention & Compaction

Requirement this addresses: bounding storage growth (G) without discarding data a topic's actual use case still needs (D) — the design has to answer "when does a record actually get deleted," and the honest answer is that it depends entirely on what the topic represents.

StrategyHowCorrect Use Case
Time/size-based retention (default for most topics)A partition's oldest segments are deleted once they age past a configured retention window (e.g. 7 days) or the partition exceeds a configured size cap, whichever limit is hit firstTransient event streams where only recent history has ongoing value — raw click events, application logs, metrics — the same "old raw data has no further use once it's been processed and, if needed, archived elsewhere" posture P16-K already applies when moving aged-out click data to cold object storage
Log compaction (opt-in, per topic)Instead of deleting by age, a background compaction thread periodically rewrites a partition's segments, keeping only the single most recent record for each distinct key and discarding every superseded prior value for that same keyA "changelog" topic representing the latest current state of a keyed entity, not a stream of independent events — the internal offset-tracking topic (P) is exactly this case, since only the most recently committed offset per (group_id, topic, partition) key is ever meaningful; older committed offsets for the same key are pure dead weight the instant a newer one exists

Why these two strategies aren't interchangeable, and picking the wrong one for a given topic is a real correctness gap, not just a storage-efficiency one: applying time-based retention to a changelog topic would eventually delete the only record of a key's current value once it aged out, even though nothing superseded it — a silent, wrong answer to "what is this key's value right now." Applying compaction to a genuine event stream would collapse every historical event for a given key down to just its latest one, destroying exactly the full history an analytics or audit consumer (P16) is reading the topic for in the first place. The two strategies protect two genuinely different promises, and a topic gets to choose, deliberately, which one it's making.

T. Full Solution at Scale — Final Architecture

flowchart TB Producers(["Producers"]) -- "produce(topic, key, value) (I, R)" --> Partitioner["Partitioner
key-hash / round-robin / sticky (R)"] Partitioner --> L1[("Partition 1 Leader
Broker A")] Partitioner --> L2[("Partition 2 Leader
Broker B")] Partitioner --> L3[("Partition N Leader
Broker C")] L1 -- "pull-replicate (O)" --> F1a[("Follower
Broker B")] L1 -- "pull-replicate" --> F1b[("Follower
Broker C")] L2 -.-> F2a[("Followers, other brokers")] L3 -.-> F3a[("Followers, other brokers")] Controller["Cluster Controller
(leader election, ISR tracking, O)"] -. "monitors + promotes on failure" .-> L1 Controller -. "monitors" .-> L2 Controller -. "monitors" .-> L3 L1 -- "records up to high watermark (O)" --> G1["Consumer Group 1
(e.g. notification workers, P12)"] L2 -- "records up to high watermark" --> G1 L1 -- "records up to high watermark" --> G2["Consumer Group 2
(e.g. aggregation pipeline, P16)"] L2 -- "records up to high watermark" --> G2 L1 -- "records up to high watermark" --> G3["Consumer Group 3
(e.g. audit/archival)"] G1 -- "commitOffset (P)" --> Offsets[("__consumer_offsets
compacted internal topic (S)")] G2 -- "commitOffset" --> Offsets G3 -- "commitOffset" --> Offsets Retention["Retention / Compaction
background threads (S)"] -.-> L1 Retention -.-> L2 Retention -.-> L3

Every partition's leader and followers, the controller's failure-detection path, and each independent consumer group's own offset bookkeeping all operate on separate, decoupled schedules — a slow or crashed consumer group never blocks a producer, and a follower's replication never blocks a consumer reading from an already-committed high watermark.

U. Design Evolution

VersionWhat ChangedWhy
V1Single-node, in-memory queue using the traditional delete-on-consume model (J)Simplest possible thing that satisfies the functional requirement for one producer and one consumer
V2Replaced the in-memory list with a durable, append-only log of segment files per partition (K, N)Survives a broker crash and turns every append/read into close to the cheapest operation a disk can do
V3A topic split into many partitions, with key-hash or round-robin assignmentRemoves the single-machine throughput ceiling and gives a consumer group real, bounded parallelism (M, R)
V4Leader + follower replicas per partition, with an in-sync replica set and configurable acksSurvives a broker failure without losing already-acknowledged data or requiring unclean, data-losing leader promotion (O)
V5Delete-on-consume replaced entirely by consumer groups tracking their own durably committed offsetsLets any number of independent consumer groups read the same topic at their own pace — the fix for V1's most fundamental flaw (P)
V6Idempotent producer (dedup retried produces) layered underneath, and consumer-side idempotency keys named as the required complement, not an optional add-onCloses the produce-retry duplication gap within Kafka's own boundary, and honestly hands the remaining, unavoidable gap to the consumer (Q)
V7Per-topic retention policy made explicit — time/size-based retention or log compaction, chosen deliberately per topicBounds storage growth correctly for a topic's actual use case instead of one uniform default that's wrong for at least one real use case (S)

What stays stable throughout: the minimal produce/poll/commitOffset contract from I — every refinement underneath changes how many partitions a topic has, how many copies of each record exist, and how a consumer's progress is tracked, never the basic shape of what a producer or consumer actually calls.

V. Consistency Model

This design makes one deliberately narrow ordering promise and is explicit about everything it does not promise on top of it. Ordering is guaranteed strictly within a single partition — a consumer reading one partition sees records in exactly the order the leader appended them, never reordered, never skipped ahead of an unreplicated record (O's high-watermark discipline). There is no ordering guarantee across partitions, and by extension no global ordering guarantee across an entire topic — two records with different keys, on different partitions, carry no meaningful relative order at all, by design (M), not by omission.

The delivery-semantics half of the model is equally explicit, and is the part most designs get wrong by implying more than they deliver: this system is an at-least-once system, full stop — a produce retry can duplicate a record at the edge of Kafka's own boundary (mitigated, not eliminated, by the idempotent producer), and a consumer crash between processing and committing an offset will redelivery-and-reprocess a record it already handled (P). "Exactly-once" is not a property this broker, or any broker, hands a consumer for free — it is a cooperative illusion, correctly achieved only by pairing this system's honest at-least-once guarantee with an idempotent consumer (Q, Prerequisites O, Patterns Playbook E). A design that claims "exactly-once" as a broker-level property, without naming the consumer-side idempotency work that guarantee actually depends on, has misdescribed its own system.

The direct contrast worth naming explicitly: unlike Real-Time Leaderboard's read-your-own-writes-versus-approximate-global-view split (P19-V), which tunes two different guarantees for two genuinely different queries, this design doesn't offer a consistency dial at all — ordering-within-a-partition and at-least-once delivery are fixed, structural properties of the log itself, not configurable trade-offs a caller opts into per request.

W. Failure Scenarios

X. Bottlenecks & Scaling Boundary

StageActual BottleneckFix Applied
V1's single in-memory listOne process, no persistence, no parallelism, and delete-on-consume breaks multiple independent readers outrightA durable, partitioned, replicated log with consumer-tracked offsets (K, M, O, P)
One key's traffic overwhelming one partitionKey-hash partitioning deterministically concentrates a hot key's entire volume onto a single partition/brokerDetect and salt the hot key, spreading it across several partitions temporarily, merged back downstream by whatever's consuming it (mirroring P16-R) (R)
Replication under a sustained write burstAn in-sync replica that can't keep up with the leader's write rate risks falling out of the ISR, shrinking effective durabilityFollower fetch parallelism and back-pressure tuned to sustain the target write rate; a persistently lagging replica is dropped from the ISR rather than allowed to stall every acks=all write (O)
Too many partitions cluster-wideEvery partition carries fixed per-partition overhead — open file handles, controller metadata, replication traffic — that grows linearly with partition count regardless of how much data any one partition actually holdsPartition count sized deliberately for projected parallelism and throughput (G, M), not maximized indiscriminately — a real, named trade-off (Y), not a free lever
A consumer group's parallelismHard-capped at the topic's partition count — additional consumer instances beyond that sit idleNamed explicitly as a scaling boundary (P) rather than discovered by surprise; partition count is chosen with the largest anticipated consumer group already in mind

Y. Design Trade-Offs

DecisionOption AOption BTrade-off
Broker modelTraditional delete-on-consume queueRetained, partitioned log, chosenDelete-on-consume is simpler and needs no offset bookkeeping at all, but permanently forecloses replay and multiple independent consumer groups; a retained log adds offset tracking and retention/compaction policy in exchange for both (J, K, P)
Write acknowledgmentacks=1 (leader only)acks=all (full ISR), chosen default for critical dataLeader-only acking is faster but risks losing an acknowledged record if the leader fails before any follower replicates it; full-ISR acking is slower (bounded by the slowest in-sync replica) but a record survives losing any single ISR member (O)
Producer partitioningRound-robinKey-hash, chosen when per-key ordering mattersRound-robin balances load perfectly but guarantees no ordering at all; key-hash preserves per-key ordering at the cost of a real hot-key skew risk that has to be actively managed (R)
Retention strategyTime/size-based deletionLog compaction, chosen for changelog-shaped topicsTime-based retention is simpler and works for most event streams, but would silently lose a changelog key's only current value once it aged out; compaction correctly keeps only the latest value per key indefinitely, at the cost of a background rewrite process and being wrong for topics that actually need full history (S)
Delivery-semantics honestyAdvertise "exactly-once" as a broker-level propertyAdvertise at-least-once, explicitly paired with consumer-side idempotency, chosenClaiming broker-level exactly-once is a more attractive-sounding guarantee but is not actually achievable for a side effect outside the broker's own boundary; the honest at-least-once-plus-idempotency framing is less flattering but is the version that's actually true and actually defensible under a follow-up question (Q)

Z. Common Bad Designs

AA. Interviewer Follow-Ups

Follow-UpDesign ChangeComponents ChangedComponents Unchanged
"How would you get genuinely exactly-once processing for a pipeline that reads from one topic, transforms, and writes to another topic, entirely within this system?"Name Kafka's own transactional API as the real, narrower answer — atomically committing a consumer's offset advance together with its downstream produce, so a crash mid-transaction rolls both back together instead of committing one without the other; explicitly scope this as Kafka-to-Kafka only, not a general external-side-effect solution (Q)A transaction coordinator spanning the read-offset-commit and the write-produce as one atomic unitPartitioning, replication, and the consumer-group/offset model for every non-transactional topic
"One specific key is receiving wildly disproportionate traffic compared to every other key on the topic — what happens?"The same hot-key problem P16-R already names for a viral ad's click volume, here at the partition level — detect the skew and salt that one key's writes across several partitions temporarily, merging results back downstream (R)A hot-key detector and salting layer ahead of the partitionerThe partitioning, replication, and consumer-group machinery for every other, normally-distributed key
"How would you mirror an entire topic to a second, geographically distant cluster?"Name this as a genuinely separate, harder problem this design doesn't solve out of the box — a dedicated cross-cluster replication process reading from the source cluster as an ordinary consumer group and re-producing into the destination cluster, inheriting all of this design's own at-least-once and ordering caveats rather than escaping themAn additional cross-cluster replication component, added on topThe single-cluster partitioning, replication, and consumer-group design entirely
"A topic's partition count was undersized at launch and now needs to grow — what actually happens to existing keys?"Acknowledge the real cost named in M directly — increasing partition count changes the hash-to-partition mapping for most existing keys going forward, breaking strict continuity of per-key ordering across the change; the honest mitigation is sizing generously upfront (G) and treating a later increase as a rare, disruptive operation, not a routine oneNone, structurally — this is a named limitation, not a component to addEverything else about the partitioning and replication model

AB. Interviewer Evaluation Rubric

AC. Interview Simulation

Interviewer: Design a distributed message queue — essentially, build Kafka.
Candidate: Before anything else, I want to name the one assumption a traditional queue makes that this design has to break: a normal broker deletes a message once a consumer acknowledges it, which is fine for exactly one consumer, but falls apart the moment a second, independent system also needs to read the same events. So the core structure here is a log, not a queue — each topic is split into partitions, each partition is a durable, append-only sequence of records that's never deleted just because something read it, and consumers track their own position — their offset — in that log instead of the broker tracking it for them.
Interviewer: Why partitions at all — why not one big log per topic?
Candidate: Two reasons, both about parallelism. One log means one leader machine's disk and network cap the entire topic's throughput, the same ceiling any single-node data store hits. And on the read side, within one consumer group each partition is owned by exactly one consumer at a time — so partition count is literally the ceiling on how many consumers in a group can do useful work in parallel. I'd partition by a key hash when ordering for that key matters, since that guarantees everything for the same key lands on the same partition in order — with the honest caveat that there's no ordering guarantee across different partitions at all, by design.
Interviewer: How do you make sure a partition survives a broker crash?
Candidate: Replication — one leader per partition, plus follower replicas that continuously pull new records from the leader. The in-sync replica set is whichever followers are actually caught up right now, and I'd default to requiring every ISR member to have the record before acknowledging a write, not just the leader — that's slower, but it means the record survives losing any one replica, including the leader itself. And critically, a consumer never sees a record until it's been replicated to the whole ISR — there's a watermark for that — so a consumer can never read something that could still disappear if the leader fails a moment later. If the leader does fail, the controller promotes a new leader only from the current ISR, never from a replica that had already fallen behind, because that would silently truncate history.
Interviewer: So is this exactly-once?
Candidate: No, and I want to be direct about that rather than oversell it. This is an at-least-once system. A producer retry after a lost acknowledgment can duplicate a record — Kafka's idempotent-producer feature mitigates that within its own boundary — and a consumer that crashes after processing a record but before committing its offset will get that same record redelivered on restart. Neither of those is a bug; they're the honest cost of operating over an unreliable network. The fix isn't a broker feature — it's pairing this at-least-once guarantee with an idempotent consumer, a stable key checked before the side effect runs, so a duplicate delivery never becomes a duplicate charge or a duplicate notification. That's actually the same pattern I'd have used earlier if I were designing something like a notification system or an ad-click pipeline on top of a queue like this one — those systems assume exactly this guarantee and build the idempotency check on their own end, because the queue never promised more than at-least-once in the first place.
Interviewer: Last one — how do old messages ever get cleaned up?
Candidate: Depends what the topic actually is. For a normal event stream — clicks, logs — I'd age out old segments by time or size, since only recent history has ongoing value once it's been processed. But for a topic that represents the current state of some key — the offset-tracking topic itself is the clearest example — I'd use log compaction instead, which keeps only the latest record per key forever and throws away superseded ones. Using the wrong one for a given topic is a real correctness bug, not just a storage question — time-based retention on a changelog would eventually delete a key's only value with nothing having superseded it.

AD. Online Solution Comparison

SourceApproachStrengthWeaknessWorth Learning
Kreps, Narkhede & Rao — "Kafka: a Distributed Messaging System for Log Processing" (LinkedIn, NetDB 2011)The original paper introducing Kafka's core reframing of a message broker as a partitioned, distributed commit log, motivated explicitly by the exact problem this section opens with — one event needing to feed multiple independent downstream consumer systems at LinkedInThe primary, first-party source for the entire central decision this problem is built around — partitions as ordered logs, consumer-tracked offsets instead of broker-tracked deletion, and the throughput case for sequential log I/O over a traditional broker's per-message bookkeepingReflects Kafka's original 2011 design; the paper predates the transactional/exactly-once-within-Kafka API (Q) and today's incremental cooperative rebalancing (P), both later additionsThe essential primary reference for this entire problem — the log-as-broker reframing in this paper is the single idea everything else in this section is downstream of
Apache Kafka — official documentation, "Replication"The first-party specification of the in-sync replica set, leader/follower replication, and the controller's role in leader election on broker failure — the exact mechanism this section's Deep Dive O is built fromDefinitive, current source for ISR semantics, acks level behavior, and the unclean-leader-election configuration this section names explicitly as a bad-design default (Z)Documents the single-cluster replication protocol only — says nothing about cross-cluster/geo-replication, which this problem deliberately scopes out (D, AA)Read this alongside the original paper to see how the specific durability/failover mechanics evolved into their current, production-hardened form
RabbitMQ — official documentation, "Consumer Acknowledgements and Publisher Confirms"The clearest first-party specification of the traditional broker model this section's V1 and Prerequisites G both contrast against — a message is delivered to a consumer and removed from the queue once that consumer acknowledges itConfirms that the delete-on-acknowledge model isn't a strawman invented for this document — it's a real, deliberate, well-documented design used in production by an entirely different, still widely-used class of brokerSays nothing about the log-retention, replay, or multi-independent-consumer-group concerns this problem is centrally about — a genuinely different broker model solving a genuinely different set of problems, not a worse version of the same oneThe single best contrast document for understanding, concretely, what Kafka's log model gives up (per-message delete-on-ack simplicity) and what it gains (replay, independent consumer groups) relative to a traditional broker

AE. Interview Cheat Sheet

ProblemDistributed Message Queue (Build Kafka)
DifficultyAdvanced — the deliberate capstone of this document, synthesizing sharding, replication, leader election, and idempotency into a single system
Core requirementsCreate a topic with partitions and a replication factor; produce a record to a partition; a consumer group polls and commits its own offset; many independent consumer groups read the same topic concurrently — ~1M msgs/sec cluster-wide, ~1.8 PB resident storage at 7-day retention and RF=3
Central decisionA topic as a set of ordered, replicated, append-only log partitions rather than a traditional delete-on-consume queue; consumer groups track their own offsets; a leader-plus-ISR-followers replication model per partition; an honest at-least-once delivery default explicitly paired with consumer-side idempotency
Why not a traditional queueDelete-on-consume permanently forecloses replay and every additional independent consumer group the moment a second one appears — the exact fan-out P12 and P16 both already depended on (J, P)
StorageAppend-only segment files per partition; sequential writes and OS-page-cache/zero-copy reads, for the same reason P16-M chose a log over a relational insert (K, N)
ReplicationLeader + follower replicas per partition; an in-sync replica set and a high watermark gate what consumers can see; new leaders are promoted only from the current ISR, never an out-of-sync replica (O)
Delivery semanticsAt-least-once by default, honestly stated; effectively-once is achieved only by pairing it with an idempotent consumer (Prerequisites O, Patterns Playbook E) — never a broker-alone guarantee (Q, V)
PartitioningKey-hash for per-key ordering (with the same hot-key salting fix as P16-R), round-robin or sticky for uniform load balance with no ordering guarantee (M, R)
RetentionTime/size-based deletion for event streams; log compaction (keep only the latest value per key) for changelog-shaped topics like the internal offset-tracking topic itself — picking the wrong one per topic is a correctness bug, not just a storage one (S)
Common mistakesPresenting a traditional delete-on-consume queue as equivalent; one partition per topic; allowing unclean leader election by default; advertising broker-level "exactly-once" with no mention of consumer-side idempotency; using the wrong retention strategy for a topic's actual shape
What NOT to over-engineerCross-cluster geo-replication (a real, separate problem); a schema registry; end-to-end exactly-once for a side effect that reaches outside the queue (handled correctly via idempotency instead, not by chasing a broker guarantee that doesn't exist)
Closing the loopEvery prior problem that reached for a queue — Notification System (P12)'s per-channel workers, Ad Click Aggregation (P16)'s aggregation/fraud/archival consumers — was, without spelling it out, already relying on the exact mechanism built here: a retained log, independent consumer groups, and at-least-once delivery paired with idempotency. Having now sat on both sides of that abstraction — as a consumer of a message queue, and as the engineer who designed the queue itself — is the intended final vantage point of this document: the building blocks in Section 0 were never a fixed list of tools to memorize, they were always systems with their own internal trade-offs, worth understanding all the way down at least once.