01

Requirements & scope

Problem Statement & Requirements

Design a distributed message queue system that decouples producers from consumers, enables asynchronous processing, guarantees message delivery, and scales to handle millions of messages per second — similar to Apache Kafka, AWS SQS, or RabbitMQ.

Functional Requirements

  • Producers publish messages to named topics
  • Consumers subscribe to topics and receive messages in order
  • Support consumer groups — multiple consumers sharing the load of a topic, each message delivered to exactly one consumer in the group
  • Messages are persisted on disk, not lost on broker failure
  • Support at-least-once, at-most-once, and exactly-once delivery semantics (configurable)
  • Consumers can replay messages from a specific offset (time-travel)
  • Support message retention by time (e.g., 7 days) or size (e.g., 1 TB per topic)
  • Ordering guarantees within a partition (not across partitions)
  • Dead letter queue (DLQ) for messages that fail processing after N retries

Non-Functional Requirements

  • High throughput — handle millions of messages/second per cluster
  • Low latency — end-to-end publish-to-consume in < 10 ms (p99) for real-time use cases
  • Durability — zero message loss once acknowledged by the broker
  • High availability — survive broker failures, rack failures, even AZ failures
  • Horizontal scalability — add brokers to increase throughput linearly
  • Fault tolerance — automatic leader election and partition rebalancing on failure

Out of Scope

  • Complex message routing (fanout, topic exchanges like RabbitMQ — simplified to topic-partition model)
  • Message transformation / stream processing (that's Kafka Streams / Flink territory)
  • Multi-tenancy and authentication (simplify for interview)
02

Scale estimations

Scale Estimations

Traffic

Metric Value
Messages produced / second 1M (peak: 3M)
Average message size 1 KB
Messages consumed / second 3M (each message consumed by ~3 consumer groups on average)
Number of topics 10,000
Number of partitions (total) 100,000
Number of producers 50,000
Number of consumer groups 5,000

Storage

Metric Value
Data ingested / second 1M × 1 KB = 1 GB/s
Data ingested / day 1 GB/s × 86,400 = ~86 TB/day
Retention period 7 days
Raw storage (7 days) 86 TB × 7 = ~600 TB
Replication factor 3
Total storage (with replication) 600 TB × 3 = ~1.8 PB
Storage per broker (50 brokers) 1.8 PB / 50 = ~36 TB per broker

Bandwidth

Metric Value
Incoming (produce) 1 GB/s
Outgoing (consume, 3 consumer groups avg) 3 GB/s
Replication traffic (2 replicas × 1 GB/s) 2 GB/s
Total network throughput ~6 GB/s cluster-wide
Per broker (50 brokers) ~120 MB/s per broker

Hardware Estimate (per broker)

Component Spec
CPU 16-24 cores (mostly I/O bound)
RAM 64-128 GB (OS page cache is critical)
Disk 8× 4 TB NVMe SSDs in JBOD (32 TB usable)
Network 10 Gbps NIC (25 Gbps preferred)
Brokers in cluster 50-100
03

Layered architecture

High-Level Architecture

High-Level ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore

Component Breakdown

Component BreakdownExcalidraw diagram · editable shapes · reveal step by stepExplore

Key: P0-L = Partition 0 Leader, P0-F = Partition 0 Follower

04

API & contracts

API Design

Producer API

EXAMPLE
POST /api/v1/topics/{topic_name}/messages
Content-Type: application/json

Request:
{
  "key": "user-123",                    // partition key (optional — null = round-robin)
  "value": "base64-encoded-payload",     // the message body
  "headers": {                           // optional metadata
    "correlation-id": "req-abc",
    "content-type": "application/json"
  },
  "partition": 3,                        // optional — explicit partition override
  "timestamp": 1712150400000             // optional — defaults to broker time
}

Response (202 Accepted):
{
  "topic": "user-events",
  "partition": 3,
  "offset": 847291,
  "timestamp": 1712150400000
}

Consumer API

EXAMPLE
GET /api/v1/topics/{topic_name}/messages?group_id=analytics-pipeline
    &max_messages=500
    &timeout_ms=5000

Response (200 OK):
{
  "messages": [
    {
      "key": "user-123",
      "value": "base64-encoded-payload",
      "headers": {"correlation-id": "req-abc"},
      "partition": 3,
      "offset": 847291,
      "timestamp": 1712150400000
    },
    ...
  ]
}

Commit Offset (Acknowledge)

EXAMPLE
POST /api/v1/topics/{topic_name}/offsets
Content-Type: application/json

Request:
{
  "group_id": "analytics-pipeline",
  "offsets": [
    {"partition": 0, "offset": 12345},
    {"partition": 3, "offset": 847292}
  ]
}

Response: 204 No Content

Admin API

EXAMPLE
POST   /api/v1/topics                     — Create topic (name, partitions, replication_factor, retention)
DELETE /api/v1/topics/{topic_name}        — Delete topic
PUT    /api/v1/topics/{topic_name}/config — Update retention, compaction, etc.
GET    /api/v1/topics/{topic_name}/info   — Topic metadata (partitions, leaders, ISR)
POST   /api/v1/topics/{topic_name}/partitions — Add partitions (cannot decrease)

SDK-Level API (Internal Binary Protocol — High Performance)

EXAMPLE
// Producer (batch-oriented)
producer.send(topic, key, value, headers) → Future<RecordMetadata>
producer.flush()                          → blocks until all buffered messages sent

// Consumer (poll-based)
consumer.subscribe(topics, group_id)
records = consumer.poll(timeout_ms)       → batch of ConsumerRecords
consumer.commitSync(offsets)              → block until offsets durably stored
consumer.commitAsync(offsets, callback)   → fire-and-forget with callback
consumer.seek(partition, offset)          → rewind/fast-forward to specific offset
05

Data model

Data Model

Core Abstractions

Core AbstractionsExcalidraw diagram · editable shapes · reveal step by stepExplore

Message Record (On-Disk Format)

Message Record (On-Disk Format)Excalidraw diagram · editable shapes · reveal step by stepExplore

Log Segment (On-Disk Storage Unit)

EXAMPLE
Partition Directory: /data/user-events-0/

  00000000000000000000.log      ← active segment (append-only)
  00000000000000000000.index    ← sparse offset → file position index
  00000000000000000000.timeindex← timestamp → offset index
  00000000000065536000.log      ← rolled segment (immutable)
  00000000000065536000.index
  00000000000065536000.timeindex

Segment rolls when:
  - Size exceeds segment.bytes (default 1 GB)
  - Time exceeds segment.ms (default 7 days)
  - Index file is full

Offset Storage

Offset StorageExcalidraw diagram · editable shapes · reveal step by stepExplore

Core Design Decisions — The Storage Engine

The most critical design decision in a message queue is how messages are stored and retrieved. This determines throughput, latency, durability, and operational complexity.

Option 1: Append-Only Log on Disk (Kafka's Approach)

Option 1: Append-Only Log on Disk (Kafka's Approach)Excalidraw diagram · editable shapes · reveal step by stepExplore

Why sequential I/O matters:

I/O Pattern HDD SSD
Sequential write 100-200 MB/s 500-3000 MB/s
Random write 0.1-1 MB/s 50-200 MB/s
Sequential read 100-200 MB/s 500-3000 MB/s
Random read 0.1-1 MB/s 50-200 MB/s

Sequential I/O is 100-1000× faster than random I/O. By only appending, Kafka achieves disk throughput that rivals network throughput.

Zero-copy transfer (sendfile):

EXAMPLE
Traditional path:                    Zero-copy path:
Disk → Kernel buffer                 Disk → Kernel buffer
Kernel buffer → User buffer             ↓  (sendfile syscall)
User buffer → Socket buffer          Kernel buffer → NIC buffer
Socket buffer → NIC buffer

4 copies, 4 context switches         2 copies, 2 context switches
~60% of CPU on data copies           ~0% CPU on data copies
Pros Cons
Extremely high throughput (millions msg/sec) No per-message deletion (retention-based only)
Sequential I/O leverages OS page cache Messages are immutable once written
Zero-copy transfers to consumers Consumer must track own offset
Simple — append + read, no complex data structures Ordering only within a partition

Option 2: In-Memory Queue with WAL (RabbitMQ-style)

Option 2: In-Memory Queue with WAL (RabbitMQ-style)Excalidraw diagram · editable shapes · reveal step by stepExplore
Pros Cons
Very low latency (microsecond for in-memory) Memory-limited — can't buffer large backlogs
Per-message ACK and deletion Complex — memory management, overflow to disk
Flexible routing (exchanges, bindings) Lower throughput at scale vs log-based
Push-based delivery No message replay after consumption

Option 3: Database-Backed Queue (SQS-style)

Option 3: Database-Backed Queue (SQS-style)Excalidraw diagram · editable shapes · reveal step by stepExplore
Pros Cons
Fully managed (SQS), no operations Lower throughput (1000s msg/s vs millions)
At-least-once with visibility timeout No ordering guarantees (SQS standard)
Per-message deletion Higher latency (database round-trips)
Simple operational model No replay capability

Recommendation

Use Option 1 (Append-Only Log) for a high-throughput, scalable message queue:

  • Sequential I/O + zero-copy = unmatched throughput
  • Persistent storage = unlimited retention and replay
  • Simple broker logic = operational simplicity
  • Consumer offset tracking = flexible consumption patterns
06

Core design decisions

Workshop note · added for the website’s common reading format

Use the detailed source walkthroughs in this article to examine Distributed Message Queue at this layer. State the requirement, propose the smallest component that satisfies it, and make its cost and failure mode explicit.

07

Request flows

Detailed Flow Diagrams

Produce Flow

Produce FlowExcalidraw diagram · editable shapes · reveal step by stepExplore

Consume Flow (Pull-Based)

Consume Flow (Pull-Based)Excalidraw diagram · editable shapes · reveal step by stepExplore

Consumer Rebalance Flow

Consumer Rebalance FlowExcalidraw diagram · editable shapes · reveal step by stepExplore
08

Performance & caching

Log Compaction (Bonus Deep Dive)

Log Compaction (Bonus Deep Dive)Excalidraw diagram · editable shapes · reveal step by stepExplore
09

Advanced design

Replication & Consistency

ISR (In-Sync Replicas)

ISR (In-Sync Replicas)Excalidraw diagram · editable shapes · reveal step by stepExplore

Acks Configuration & Durability Tradeoffs

acks Setting Behavior Durability Latency Throughput
acks=0 Producer doesn't wait for any ACK Lowest — fire and forget ~0.5 ms Highest
acks=1 Wait for leader to write to local log Medium — lost if leader dies before replication ~2-5 ms High
acks=all Wait for all ISR replicas to write Highest — survives any single broker failure ~5-15 ms Lower

Recommendation: acks=all with min.insync.replicas=2 for production:

  • Tolerates 1 broker failure without data loss
  • Blocks produces if ISR shrinks below 2 (prevents single-replica writes)

Leader Election

Leader ElectionExcalidraw diagram · editable shapes · reveal step by stepExplore

Partitioning Strategy

How Messages Are Routed to Partitions

How Messages Are Routed to PartitionsExcalidraw diagram · editable shapes · reveal step by stepExplore

Partition Count Selection

EXAMPLE
Target throughput: 1 GB/s

Single partition throughput:
  - Producer: ~10-50 MB/s per partition
  - Consumer: ~20-100 MB/s per partition (consumer is usually faster)
  - Bottleneck is typically producer side

Calculation:
  Required partitions = Target throughput / Per-partition throughput
                      = 1 GB/s / 30 MB/s (conservative)
                      = ~34 partitions per topic (round up to 36-48)

Rule of thumb:
  - Start with max(expected_throughput / 30MB, num_consumers_in_largest_group)
  - Can always ADD partitions later (cannot remove)
  - More partitions = more parallelism BUT more open file handles,
    longer leader election, more memory for consumer offset tracking

Partition-to-Consumer Assignment Strategies

Strategy How It Works Pros Cons
Range Assign consecutive partitions to consumers (P0-P2→C1, P3-P5→C2) Preserves key locality for co-partitioned topics Uneven if partition count not divisible by consumers
Round-Robin Distribute partitions one by one (P0→C1, P1→C2, P2→C1...) Even distribution Breaks key co-partitioning
Sticky Like round-robin but minimizes reassignment during rebalance Fewer partition movements on rebalance Slightly more complex
Cooperative Sticky Incremental rebalance — only moves partitions that need moving Near-zero downtime rebalancing Requires multiple rebalance rounds

Recommendation: Cooperative Sticky Assignor — minimizes stop-the-world rebalancing.

10

Edge cases

Handling Edge Cases

Consumer Lag & Backpressure

EXAMPLE
Problem: Consumer is slower than producer → unbounded lag
  
Monitoring:
  consumer_lag = latest_offset(partition) - committed_offset(consumer_group, partition)
  
Mitigations:
  1. Scale out consumers (up to # of partitions)
  2. Increase consumer fetch size (fetch.max.bytes)
  3. Tune consumer processing (batch DB writes, parallelize)
  4. Add partitions to topic (+ proportional consumers)
  5. Alert when lag exceeds threshold (e.g., > 100K messages)

Exactly-Once Semantics (EOS)

Exactly-Once Semantics (EOS)Excalidraw diagram · editable shapes · reveal step by stepExplore

Dead Letter Queue (DLQ)

Dead Letter Queue (DLQ)Excalidraw diagram · editable shapes · reveal step by stepExplore

Message Ordering Across Partitions

EXAMPLE
Problem: User actions must be ordered, but topic has 12 partitions

Solution: Use the entity ID as the partition key
  - key = "user-123" → hash("user-123") % 12 = partition 7
  - ALL events for user-123 go to partition 7
  - Within partition 7, ordering is guaranteed

Caveat: If you add partitions, hash changes → key mapping shifts
  - Mitigate: Don't add partitions to topics where key ordering matters
  - Or: use a custom partitioner with a stable mapping table

Large Messages

Large MessagesExcalidraw diagram · editable shapes · reveal step by stepExplore
11

Tradeoffs

Tradeoffs & Design Decisions Summary

Decision Option A Option B Chosen Why
Storage Engine Append-only log (Kafka) In-memory + WAL (RabbitMQ) Append-only log Sequential I/O = highest throughput; unlimited retention; replay capability
Delivery Model Pull (consumer polls) Push (broker pushes) Pull Consumer controls pace; no backpressure on broker; natural batching
Metadata Store ZooKeeper (external) KRaft (self-managed) KRaft Removes external dependency; simpler operations; better scaling (>200K partitions)
Ordering Scope Global ordering Per-partition ordering Per-partition Global ordering = 1 partition = no parallelism; per-partition is the right balance
Consumer Offset Broker tracks (auto) Consumer commits (manual) Manual commit Application controls exactly-once semantics; no re-processing on crash
Acks acks=1 (leader only) acks=all (full ISR) acks=all Data loss is unacceptable for most use cases; latency cost is acceptable (5-15 ms)
Replication Synchronous Asynchronous Sync for ISR Async loses data on leader failure; sync ISR guarantees committed = durable
Rebalancing Stop-the-world Cooperative incremental Cooperative Stop-the-world pauses ALL consumers during rebalance; cooperative only moves affected partitions
Compaction Delete old segments Log compaction (keep latest per key) Both Delete for event streams; compaction for changelog/state topics
Wire Protocol HTTP/REST Custom binary (TCP) Binary Lower overhead, better batching, connection multiplexing; REST for admin only
12

Reliability & fault tolerance

Reliability & Fault Tolerance

Single Points of Failure & Mitigations

Single Points of Failure & MitigationsExcalidraw diagram · editable shapes · reveal step by stepExplore

Graceful Degradation

EXAMPLE
Scenario: Broker goes down (1 of 50)

  1. Controller detects missing heartbeat (18s timeout)
  2. For each partition where dead broker was leader:
     → Elect new leader from ISR
     → Update metadata, notify clients
  3. For each partition where dead broker was follower:
     → Remove from ISR
     → Continue with remaining ISR
  4. When broker returns:
     → Fetches missed data from current leaders
     → Rejoins ISR after catching up
     → Preferred leader election restores original topology

Impact: ~5-30s of unavailability for affected partitions
         Other partitions continue unaffected
         Zero data loss (acks=all with min.insync.replicas=2)

Data Durability Guarantees

Data Durability GuaranteesExcalidraw diagram · editable shapes · reveal step by stepExplore
13

Production architecture

Full System Architecture (Production-Grade)

Full System Architecture (Production-Grade)Excalidraw diagram · editable shapes · reveal step by stepExplore
14

Further exploration

Workshop note · added for the website’s common reading format

Rebuild Distributed Message Queue from memory, then change one assumption: ten times more traffic, a new region, or a stricter consistency requirement. Which component must change first—and which does not?

Compare Leader brokers with the same boundary in a related design. Write down one alternative you rejected, what it would simplify, and when you would choose it instead. Follow the source link at the end of this article to explore the original document.

15

Interview playbook

Interview Tips

  1. Start with the append-only log insight — This is the foundational design decision. Explain why sequential I/O and zero-copy make disk-based queues faster than in-memory alternatives at scale.

  2. Draw the partition model early — Topics → Partitions → Segments. This shows you understand the parallelism model. Emphasize: ordering is per-partition, not per-topic.

  3. Discuss the acks tradeoff in depthacks=0 vs acks=1 vs acks=all. Connect it to ISR and min.insync.replicas. This is a nuanced durability vs latency discussion interviewers love.

  4. Explain consumer groups and rebalancing — How partitions are assigned, what triggers a rebalance, why cooperative sticky is preferred over stop-the-world. Mention the "consumers ≤ partitions" constraint.

  5. Address exactly-once semantics — Most candidates only know at-least-once. Explaining idempotent producers (PID + sequence number) and transactions sets you apart.

  6. Mention KRaft over ZooKeeper — Shows you know the modern architecture. ZooKeeper was a bottleneck at >200K partitions. KRaft uses Raft consensus built into the brokers themselves.

  7. Discuss operational concerns — Partition count selection, when to add partitions (and why you can't remove them), retention policies, monitoring consumer lag, dealing with hot partitions.

  8. Claim-check pattern for large messages — Shows pragmatism. Don't just increase max.message.bytes — explain the S3 reference pattern.

  9. Compare with alternatives when asked — Kafka (high throughput, log-based) vs RabbitMQ (flexible routing, push-based) vs SQS (managed, simple). Each has its sweet spot.

  10. End with scale numbers — "A single Kafka cluster with 50 brokers can handle 1M+ messages/sec, store petabytes with 7-day retention, and serve hundreds of consumer groups simultaneously."