System design interview

Design a distributed message queue

Partitioned logs, offsets, consumer groups, replication and ack levels.

At a glance

Level
Advanced
Topics
Queues and streams, Storage, Consistency
Main parts
Leader broker · Controller · Follower broker · Partition log
What you'll learn
Practise this problem
Overview
ProducerConsumergroupLeaderbrokerControllerFollowerbrokerPartitionlogProducerConsumer groupLeader brokerControllerFollower brokerPartition log

Press Play to follow a request through the design, or step through it.

  • Client and edge
  • Service
  • Data store
  1. The producer hashes the message key to pick the partition, then sends a batch of messages to that partition's leader.
  2. The leader appends the batch to the end of the active log segment and gives each message the next offset.
  3. Followers fetch new data from the leader as it arrives. Each one appends it to its own copy of the log.
  4. A follower's next fetch carries the offset it has reached, which tells the leader that it holds the batch.
  5. Once every in-sync follower has the batch (ack level all), the leader acknowledges to the producer.
  • Producer
  • Consumer group
  • Leader broker: one per partition
  • Controller: leaders, in-sync sets
  • Follower broker: own copy of the log
  • Partition log: append-only segments
On this page

In 2 minutes

Key ideas
  • The numbers. The load is mostly large sequential writes and reads, which disks and memory caches handle well.
  • Storage model. An append-only log with committed offsets per group.
  • Ordering scope. Order within a partition.
  • Producer acks. Ack all with at least 2 in-sync replicas.
  • Delivery. At least once, with idempotent processing.
Read the 2-minute answer

I would build the queue as partitioned, append-only logs. A topic has about 200 partitions, each a sequence of segment files with a sparse offset index. Producers hash the message key to a partition, so a key stays ordered, and send compressed batches to that partition's leader. The leader appends them sequentially and assigns offsets. Writes are fast because they are sequential and recent data is served from the memory cache.

Each partition has three replicas in different zones. Followers fetch from the leader, and an acknowledgement at level all with a minimum of two in-sync replicas means an acknowledged message is on at least two machines. Consumers pull from the leader, and consumer groups share partitions, one consumer per partition. Each group commits its offsets to the broker, which gives replay and independent groups. Committing after processing gives at-least-once, and idempotent producers plus idempotent consumers make it effectively once.

A replicated controller watches heartbeats and, when a leader fails, elects a new one from the in-sync set and raises the epoch, which fences the old leader. The sizing is 1 GB/s of ingest, three copies, about 30 brokers. The main trade-off is that order holds only within a partition, in exchange for horizontal scale.

Step 1 of 6: Clarify the scope

Questions to ask first

Ask these before you draw anything. Guess why each answer changes the design, then open it to check.

Is ordering required, and across what scope?
Why it matters: A single global order would limit throughput to one writer. Order within a key lets us split the topic into partitions.
How long are messages kept, and may consumers read them again?
Why it matters: Keeping messages for days makes the queue a log that many readers can replay, unlike a queue that deletes a message when it is acknowledged.
Which delivery guarantee: at most once, at least once or exactly once?
Why it matters: The answer decides how producers retry, when consumers save their position, and what each side must do to avoid duplicates.
What are the message size, the producer rate and the number of consumer groups?
Why it matters: Rate and size set the disk and network needs. The number of readers sets how much read traffic the brokers carry.
How much data loss is acceptable when a machine fails?
Why it matters: Zero loss means copying to other machines before acknowledging, which costs latency and some availability.

Requirements

Functional

  • Producers publish messages to a named topic, with an optional key.
  • Consumers read a topic in order within each partition and track their own position.
  • Consumer groups share a topic, so each message is handled by one member of the group.
  • Messages are kept for a set time, 7 days here, so consumers can read them again.
  • Operators can create topics and change their partition count.

Non-functional

  • Sustain 1 million messages a second at 1 KB each.
  • Publish latency: p99 under 20 ms with replication.
  • An acknowledged message survives the loss of any one broker.
  • A partition accepts writes again within 10 seconds of its leader failing.
  • Messages with the same key are always read in the order they were written.

Step 2 of 6: Size it

Show the maths

These numbers are assumptions for the exercise, not facts about a real system:

  • 1 million messages a second, 1 KB each, and a replication factor of 3 (one leader and two followers).
  • Messages are kept for 7 days. One broker sustains about 100 MB/s of disk writes.
  • Each partition is kept to about 10 MB/s so it stays easy to move and recover.
  • Consumers mostly read recent data, which is still in the operating system's memory cache.

The arithmetic:

Ingest rate
1,000,000 messages/s × 1 KB
1 GB/s from producers
Disk writes with replication
1 GB/s × 3 copies = 3 GB/s; ÷ 100 MB/s per broker
30 brokers at least
Storage for 7 days
1 GB/s × 86,400 s = 86.4 TB a day; × 7 = 605 TB; × 3 copies; ÷ 30 brokers
≈ 1.8 PB in all, about 60 TB per broker
Partitions
1 GB/s ÷ 10 MB/s per partition = 100; double for headroom
200 partitions, about 7 leaders per broker
Largest consumer group
One consumer per partition at most
up to 200 consumers read in parallel

Step 3 of 6: Sketch the path of one request

Step through the diagram at the top of the page to watch a request move through these parts.

A topic is split into partitions. Each partition is an ordered, append-only log held by one Leader broker, which also keeps copies on Follower brokers. A Producer picks the partition by hashing the message key, so messages with one key go to one partition and stay in order. It sends a batch to that partition's leader.

The leader appends the batch to the end of the Partition log, gives each message the next offset and sends the batch on to followers, which copy it. Depending on the ack setting, the leader answers the producer once it has written the batch itself, or once the in-sync followers have it too.

A Consumer group reads by asking a leader for messages from an offset. The broker does not track individual messages. Each group commits its position, the committed offset, to a coordinator broker, which stores one offset per group and partition in an internal replicated topic. A Controller, itself a small replicated group, stores topic settings and group membership, and elects a new leader from the in-sync followers when a leader fails.

Other paths through the system

Consume as a group

  1. A group member asks the leader of partition 7 for messages after its saved offset, for example 41,200.
  2. The leader finds that offset in the right segment using a sparse index, and reads forward.
  3. The bytes come back, usually from the memory cache that still holds the recent end of the log.
  4. The leader returns a batch of messages up to the high-water mark. The consumer processes them.
  5. The consumer commits its new offset, 41,300, to the broker, which stores it durably for the group.

A leader fails

  1. A follower's fetch from the leader times out, because the leader's machine is gone.
  2. The follower reports the failed fetch to the controller.
  3. The controller finds the old leader's heartbeats have stopped, and its session timer of about 6 seconds runs out.
  4. The controller picks a leader from the in-sync set and bumps the leader epoch. This follower is chosen.
  5. Producers and consumers refresh their metadata and go to the new leader. The old broker, if it returns, rejoins as a follower.

Step 4 of 6: Go deep on the hard part

Logs, segments and offsets

InterviewerHow is a partition stored on disk, and why is reading and writing it so fast?A partition is an append-only log.

A partition is an append-only log. New messages are only ever added at the end, and each gets the next offset, a number that grows by one. A message is identified by (topic, partition, offset), and its order is its offset.

The log is cut into segment files, for example one per gigabyte or per hour. Only the newest, the active segment, is written. Older segments never change. Each segment is named by its first offset and has a sparse index file that maps some offsets, say one every 4 KB of data, to a byte position. To read offset 41,200, find the segment by name with a binary search, look up the nearest index entry before it, then scan forward a few kilobytes. It needs no search of the whole log.

Why it is fast. Sequential disk writes are far faster than random ones, because the disk head or the flash controller never jumps around. The operating system keeps the recent part of the log in its page cache, so a consumer that keeps up reads from memory, not from disk. The broker can hand file bytes straight to the network without copying them through the application (the sendfile call). Producers send batches and often compress them. The broker stores the whole batch unchanged, and the consumer unpacks it, so the broker spends little CPU per message.

Retention is cheap. To delete data older than seven days, delete whole old segment files. That is one fast operation per file, not a delete per message. A second mode, compaction, keeps only the latest message per key, which suits a stream of updates to the current state.

Positions are kept per group. The broker does not track or delete messages as they are read. A consumer reads from an offset and commits the offset it has finished. The group's coordinator broker stores it for that group and partition in an internal compacted topic, replicated like any other log. This keeps the broker simple and allows many independent groups at different speeds. It also allows replay: reset the offset to last Tuesday and read again after a bug fix. The cost is that consumers must commit offsets carefully, which is the next topic.

Choosing the partition. Hash the key and take the remainder by the partition count. The same key always lands in the same partition and so stays ordered. Changing the count later reshuffles keys, so pick a count with headroom.

Consumer groups and delivery guarantees

InterviewerHow do consumers share a topic, and how do you get at-least-once or effectively-once processing?A consumer group is a set of consumers with one group id.

A consumer group is a set of consumers with one group id. Each partition is assigned to exactly one consumer in the group, so messages in a partition are handled in order by one worker. A group can use at most as many active consumers as there are partitions. A second group with a different id receives every message again, independently.

A broker acting as the group's coordinator tracks members through heartbeats. When a member joins, leaves or stops sending heartbeats, the coordinator starts a rebalance and reassigns partitions. During a plain rebalance everyone pauses. Newer incremental rebalancing moves only the partitions that must move. Static membership, where a restarting consumer keeps its identity for a short grace period, avoids a rebalance for a quick restart.

Delivery guarantees depend on when you commit the offset.

  • At most once. Commit first, then process. A crash after the commit loses the message.
  • At least once. Process first, then commit. A crash before the commit means the batch is read again, so the consumer sees duplicates. This is the usual default.
  • Effectively once. Accept at-least-once delivery, and make duplicates harmless.

To make duplicates harmless, work at both ends. On the producer side, an idempotent producer adds a producer id and a sequence number to each batch for a partition. The broker discards a batch it has already stored, so a retry after a lost acknowledgement does not duplicate it. On the consumer side, either make the processing idempotent, for example by an upsert keyed by message id, or save the result and the new offset together in one transaction in the same database.

Be careful with the phrase "exactly once". It holds inside the queue's own read-process-write cycle. A side effect outside it, such as an email, can still repeat unless it is idempotent.

Bad messages. A message that always fails would block its partition if retried forever. After a few attempts, move it to a dead-letter topic with the error, and carry on. Watch lag, which is the log end offset minus the committed offset, and add consumers, up to the partition count, when it grows.

Replication, in-sync replicas and leader failure

InterviewerHow do you keep acknowledged messages safe when a broker dies?Each partition has a replication factor of 3: one leader and two followers, placed in different zones.

Each partition has a replication factor of 3: one leader and two followers, placed in different zones. All reads and writes go to the leader, which keeps ordering simple. Followers fetch from the leader as a consumer would and append to their own log.

The in-sync replicas (ISR) are the leader plus the followers that are caught up, within a lag limit such as 10 seconds. The high-water mark is the highest offset that every in-sync replica holds. Consumers can read only up to it, so they never see a message that a failover could erase.

Ack levels let the producer choose its safety:

  • Ack 0: do not wait. Fastest, and a message can vanish with no sign.
  • Ack 1: the leader has written it. If the leader dies before a follower copies it, the message is lost.
  • Ack all: every in-sync replica has it. Combined with a minimum in-sync count of 2, the write is refused whenever fewer than two replicas are in sync. An acknowledged message is then on at least two machines.

The minimum in-sync setting is the durability and availability dial. A higher value means fewer losses and more refused writes during trouble.

When a leader fails, the controller notices missing heartbeats after the session timeout, about 6 seconds here. It picks the new leader from the in-sync set, so the chosen replica holds every acknowledged message. It raises the leader epoch, a number stamped on requests. Brokers reject requests carrying an old epoch, which fences off a former leader that wakes up and still believes it is in charge. When that old broker rejoins, it truncates any messages the new leader never had and then follows.

Electing a replica that is not in sync, an unclean election, restores availability sooner and loses acknowledged messages. Leave it off for data that matters.

The controller must not be a single point of failure, so it is a small group of 3 or 5 nodes agreeing through a consensus protocol. With a 6-second timeout and a short election, a partition accepts writes again well inside the 10-second target.

Step 5 of 6: Name the trade-offs

Storage model
Chose
An append-only log with committed offsets per group
Over
Delete each message when it is acknowledged

Writes are sequential, many groups can read independently and replay is easy. The cost is that per-message features, such as delayed retry, are harder to build.

Ordering scope
Chose
Order within a partition
Over
One global order

Partitions are written and read in parallel, which is how it reaches a million messages a second. The cost is that messages with different keys have no order between them.

Producer acks
Chose
Ack all with at least 2 in-sync replicas
Over
Ack 1 from the leader only

An acknowledged message survives a broker loss. The cost is higher publish latency, and writes refused when too few replicas are in sync.

Delivery
Chose
At least once, with idempotent processing
Over
At most once

A crash never loses a message. The cost is possible duplicates, handled by idempotent producers and consumers.

Partition count
Chose
Enough partitions for double the current load
Over
As few as possible

It leaves room to add consumers and brokers. The cost is more files, more replication work and longer recovery after a failure.

Step 6 of 6: Say what breaks next

Follow-up questions an interviewer asks once the design is on the board. Answer each one out loud, then open it to compare.

How do you choose the number of partitions?
Take the larger of two numbers: target throughput divided by what one partition handles, and the highest consumer parallelism you will want. Then add headroom, because raising the count later reshuffles keys. Avoid thousands per topic, since each partition adds files and replication work.
What if one key is much hotter than others?
One partition then carries most of the load, and the hash cannot split a single key. Add a small random suffix to that key, which spreads it over several partitions and gives up order across the suffixes. Or give the hot key its own topic.
How is this different from a classic queue that deletes on acknowledgement?
A classic queue tracks each message's state, so it can retry or delay one message on its own. A log gives up that per-message control to gain throughput, replay and many independent readers. You can build retries on a log with extra retry topics.
How do you add a broker to a busy cluster?
Move some partitions to it. The new broker first becomes a follower and copies the log, then leadership can shift to it. Throttle the copy bandwidth, so the move does not starve live traffic.

Free ATS check

See your resume the way an ATS reads it, then fix what it misses.

Drop in a PDF for a score out of 100, every fix that cost a point, and the line it belongs to. It runs in your browser, with no signup for the score.