Designing Kafka Itself: The Distributed Log, ISR Replication, and Compaction
"Design a message queue" used to mean in-memory queues and consumer acks. Then Kafka reframed the problem: what if the queue is just a replicated append-only file that consumers read at their own pace? Most candidates can use Kafka; the interview that matters asks you to build it. Working through that design teaches you why the log — not the queue — turned out to be the right primitive for data infrastructure.
The core bet: sequential I/O and dumb brokers
A queue that tracks per-message delivery state (delivered? acked? redelivery timer?) holds mutable state per message per consumer — that state machine is the bottleneck. Kafka's move: the broker stores an immutable, append-only log per partition, and consumers own their position (an offset — just an integer). Delivery state for a million consumers is a million integers, stored by the consumers.
This buys three things at once: writes are sequential appends (disks, even spinning ones, love this — hundreds of MB/s); reads are sequential scans served straight from the OS page cache; and replay is free — rewind your offset. Add zero-copy (sendfile() from page cache to socket, bytes never enter user space) and one broker saturates the NIC without breaking a sweat.
partition = [seg0][seg1][seg2 (active)]
each segment: log file + sparse offset index
consumer A offset: 41,022 consumer B offset: 12,904,551
Segments (say 1GB each) exist so retention is rm old_segment — deleting from the middle of a file is exactly the random I/O the design refuses to do.
Partitioning: the scaling and ordering contract
A topic is N partitions; each partition is an independent totally-ordered log living on some broker. Ordering is guaranteed within a partition only, so the partition key is a product decision: key by user_id and one user's events are ordered; hot users create hot partitions — the same skew trade every sharded system makes. Throughput scales by adding partitions; too many partitions bloats metadata, recovery time, and open file handles. (Modern Kafka moved cluster metadata from ZooKeeper to an internal Raft quorum — KRaft — precisely because partition counts kept growing.)
Replication: ISR and the high-watermark
Each partition has a leader and followers. Kafka's replication design is the part worth whiteboarding — it is neither Raft nor primary-backup, and its vocabulary shows up in incident reviews:
- Followers pull from the leader. Followers that are caught up (within a lag bound) form the ISR — in-sync replica set.
- A write with
acks=allis acknowledged when every ISR member has it. - The high-watermark = the last offset replicated to the whole ISR. Consumers can only read up to it — you never read a message that could vanish in a failover.
- Leader dies → controller elects a new leader from the ISR → nothing acked is lost.
The subtle knob: if replicas fall behind, they are ejected from the ISR, and the ISR can shrink to just the leader — acks=all then means "one machine has it." min.insync.replicas=2 is the guardrail: below two in-sync copies, the partition refuses writes. That is CAP played out in one config key — refuse availability rather than silently weaken durability. The pathological alternative, unclean.leader.election (let a stale replica lead), trades data loss for availability and is off in any system that bills anyone.
leader log: m1 m2 m3 m4 m5
follower1: m1 m2 m3 m4 (in ISR)
follower2: m1 m2 (lagging -> ejected)
high-watermark = m4 -> consumers see up to m4
Exactly-once, the honest version
Producer retries duplicate messages; the fix is an idempotent producer — a producer ID plus per-partition sequence numbers lets the broker drop replays. Transactions extend this: writes to multiple partitions plus the consumer's offset commit become atomic (offsets are themselves a compacted internal topic — the design eats its own primitive). What transactions do not cover is a side effect into an external system; there you are back to idempotent sinks. Same lesson as every exactly-once story: it is exactly-once within the log's transaction boundary, at-least-once plus idempotency beyond it.
Log compaction: the log as a table
Retention has two modes. Time/size retention drops old segments — fine for event streams. Compaction instead keeps the latest value per key, scrubbing older duplicates in the background. A compacted topic is a materialized changelog: replay it and you rebuild a table (this is how Kafka Streams state stores and the consumer-offsets topic recover). Deletes are tombstones — (key, null) — retained long enough for consumers to observe, then scrubbed; if that sounds like Dynamo tombstones and CRDT deletes, it is — deletion in distributed systems is always a message, never an absence.
What the interviewer is actually probing
| Question | The design answer |
|---|---|
| Why is it so fast? | Sequential appends + page cache + zero-copy; the broker does no per-message bookkeeping |
| Message vanished after failover? | Read past high-watermark can't happen; check ISR shrink + acks config |
| Consumer group rebalancing storms? | Offsets external to broker make consumers restartable; sticky/cooperative assignment limits churn |
| Queue vs log, when? | Per-message routing/priority/TTL → queue (SQS/Rabbit); replay, ordering, fan-out to many readers, stream processing → log |
The transferable idea is bigger than Kafka: state your storage as an ordered immutable log and let every other concern — replication, delivery, recovery, even tables — become a reader position or a fold over it. Half of modern data infrastructure is that sentence wearing different logos.
Keep reading
Write Skew: The Anomaly Snapshot Isolation Does Not Catch
Two transactions each read what the other writes, both commit, and an invariant dies. Snapshot isolation allows write skew — here is how to close the hole.
Designing a Shopping Cart: Price Snapshots, Merge, and No Inventory Hold
A cart is a per-user document, not a reservation. What to store on the line, how to merge an anonymous cart at login, and when the price is allowed to change.
Designing Snowflake-Style IDs: Ordering, Workers, and Clock Rollback
A 64-bit id from a timestamp, a worker number, and a per-millisecond sequence. How many you can issue, why the clock must not step backwards, and why JavaScript cannot hold one.
PostgreSQL 19 on October 29: WAIT for Read-Your-Writes, REPACK Without the Lock, and What Got Pulled
PostgreSQL 19 GA is scheduled for October 29, 2026. The WAIT command gives standbys read-your-writes, REPACK CONCURRENTLY replaces VACUUM FULL and CLUSTER, autovacuum goes parallel, and SQL/PGQ, online checksums, and FOR PORTION OF were reverted in Beta 4.
Idempotency Keys for Write APIs
How to make POST safe to retry: the key, the stored response, the race between two identical requests, and the expiry that forgets too soon.
Read-Your-Writes: The User Just Saved and the Read Replica Does Not Know
Why a write to the primary followed by a read from a replica shows stale data, and the practical ways to pin the next read without sending all traffic to the primary.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
- No comments yet. Be the first.