CMD Guide
HomeSystem DesignSystem Design Problems

Designing Facebook Messenger

This is the introductory version. For the staff-depth treatment — mechanism, failure modes, and the trade-off layer — study the deep companion: Designing WhatsApp/Messenger — Online/Offline Delivery, Traced

1. What we are building

Facebook Messenger is a real-time, text-based instant-messaging service. Users chat one-on-one (and in groups) from phones and the web, see who is online, and expect their full chat history to be present and identical on every device they log in from.

The design splits into three hard problems that pull against each other: real-time delivery (get a message to the recipient with minimum latency), durable, consistent history (never lose a message; show the same transcript everywhere), and presence (track online/offline for hundreds of millions of connections without melting the fleet).

Requirements

Functional

Non-functional

Extended

2. Capacity estimation

Rough numbers are what drive the storage-engine and sharding choices later, so we do them first.

QuantityAssumptionResult
Daily active users500 M
Messages / user / day40
Messages / day500M × 4020 B
Avg message size100 B
Storage / day20B × 100B2 TB
Storage / 5 years2TB × 365 × 5 = 3650 TB≈ 3.65 PB
Bandwidth2TB / 86400 s≈ 25 MB/s each way

Every inbound message must go back out to a recipient, so we need roughly the same 25 MB/s for both ingress and egress. The 3.65 PB figure excludes user records, per-message metadata (ID, timestamp, sequence number), compression, and replication — real footprint is a few multiples of this. The takeaway that matters for design: this is a write-heavy stream of tiny records that we mostly read back sequentially per conversation.

diagram
diagram

3. Delivery: push, not pull

To receive a message the client can pull (poll the server periodically) or push (keep a connection open and let the server deliver instantly).

Pull forces a latency-vs-waste tradeoff: poll often and you burn resources on mostly-empty responses; poll rarely and messages feel laggy. The server also has to keep buffering undelivered messages until the next poll.

Push wins for chat. Active users hold a connection open; the instant the server has a message it writes it down the existing connection — minimum latency, no polling storm, and no need to track pending messages for online users.

Holding the connection: long-poll vs WebSocket

Two ways to keep a channel open:

Modern Messenger-class systems use WebSockets (with a long-poll fallback for hostile networks). Either way the server must map each connected user to the box holding their socket.

diagram
diagram

4. Connection registry, ordering, and offline handling

Routing to an open socket. Each chat server keeps a hash table UserID → connection. On receiving a message for a user it looks up the connection and writes on it. Fleet sizing: plan for 500 M concurrent connections; if one modern box handles ~50K, that's ~10K chat servers. A load balancer maps each UserID to the server that currently owns its connection.

Processing a send. On a new message the server (1) appends it durably to the message store — an LSM memstore write plus its WAL append, a cheap sequential I/O — then (2) acks the sender as sent, and (3) delivers it to the recipient's server, which later produces the separate delivered receipt. The tempting shortcut — ack first, store asynchronously in the background — is a durability bug under this page's own requirements: we declared "never lose a message" and consistency-over-availability, yet a chat-server crash after an early ack silently drops an acknowledged message. So the durable (WAL) write sits on the ack path, costing a few milliseconds of sender latency; everything after durability (replica catch-up, cache fill, receipts) stays asynchronous.

Ordering. A server-receive timestamp is not enough: if A→B arrives at T1 and B→A arrives at T2>T1, ordering by timestamp makes each participant see a different-but-plausible sequence, and worse, isn't stable across a user's own devices. The fix is a per-user monotonic sequence number: every message is stamped with the next sequence number in that user's own stream. Two participants may still see slightly different interleavings of a conversation, but each user's view is identical on all of their devices — which is exactly the consistency requirement. (Section 6 shows why UserID-based sharding makes this sequence number cheap to generate.)

Offline recipient. If the recipient is genuinely gone, notify the sender of delivery failure. If it's a transient drop (a long-poll timeout, a flaky socket), expect a reconnect: buffer the message briefly and retry on reconnect, and/or let the client re-send transparently so the user never retypes.

5. Storage engine: why a wide-column store

The access pattern is a firehose of tiny writes plus per-conversation range reads ("give me the last N messages, then page backward"). That immediately rules some options out and points to one family.

Real-world corroboration. Discord's message store evolved through exactly this reasoning. Per Discord's own engineering blogs, it began on MongoDB, migrated to Cassandra in 2017 as message volume exploded, and then re-platformed onto ScyllaDB in 2023 (a C++ rewrite of the Cassandra data model) to tame tail latency and GC pauses at trillions of messages. The throughline matches our choice: a document store did not hold up for a write-heavy chat firehose, and the endpoint is a wide-column, LSM store — the same category as HBase.

6. Data partitioning

At 3.65 PB over five years we must shard. Two candidate keys:

Shard by UserID (chosen). Hash the UserID and keep all of a user's messages — their entire inbox across every conversation — on one shard: shard = hash(UserID) % 1000. At ~4 TB/shard, 3.65 PB needs ~900 shards; round to 1K logical shards, initially packed several-per-physical-server and spread out as data grows. Fetching a user's history is then a single-shard operation.

Shard by MessageID (rejected). Scattering one conversation's messages across shards turns every history fetch into a slow cross-shard scatter-gather. Don't.

Reconciling sharding with the per-user sequence number

These two decisions are not in tension — one enables the other. Because UserID sharding co-locates a user's entire message stream on a single home shard, the per-user monotonic sequence number from Section 4 can be produced by a single counter on that one shard. No cross-shard coordination, no distributed consensus, no clock sync is needed to hand out the next number — the very property (all my data on one shard) that makes history reads fast is what makes strictly-ordered, device-consistent sequencing cheap. Sharding by MessageID would have scattered a user's stream and made that counter a distributed-coordination problem.

diagram
diagram

7. Presence, cache, balancing, fault tolerance, and extensions

Presence (online/offline)

The connection object already tells us who is online. Broadcasting every flip to all friends of 500 M users would be ruinous, so we soften it: pull a friend list's statuses on app start; mark a user offline lazily when a send to them fails; delay broadcasting "came online" a few seconds to absorb flapping; and only pull status for users actually in the viewport, tolerating slightly stale offline state.

Cache

Cache the last ~15 messages of the last ~5 conversations in a user's viewport. Since a user's whole history lives on one shard, their cache can live entirely on one machine too.

Load balancing

A load balancer in front of the chat servers maps each UserID to the server holding its connection; a second balancer fronts the cache tier.

Fault tolerance & replication

Failing over live TCP connections is impractical — instead, clients auto-reconnect with jittered exponential backoff and the LB routes them to a new chat server. The jitter matters: a dead box means ~50K sockets re-authenticating and catch-up-syncing near-simultaneously (an AZ loss multiplies that by hundreds of servers), so spread the reconnects over tens of seconds and rate-limit the catch-up pull path to protect the message store during mass reconnects. Message data is never single-copy: replicate the hot message log across servers; erasure coding (e.g., Reed–Solomon) only pays off at the aggregated file/block layer (HDFS-style EC on flushed HFiles) — never per ~100-byte message, where the coding metadata would dwarf the payload.

Group chat

Model a group as a GroupChatID object holding its member list; the LB routes group messages by GroupChatID, and the owning server fans the message out to each member's connection server. Group chats are stored in their own table partitioned by GroupChatID.

Push notifications

Offline users are reached via a Notification server that forwards messages to the platform push services (APNs, FCM), which deliver to the device. This is what turns "send fails when recipient is offline" into "recipient gets a banner and syncs on next open."

8. The judgment layer: when to use each choice, and when NOT

Delivery model — push vs pull

ChoiceUse whenAvoid when
Push (open connection)Interactive chat, presence, live feeds — latency and efficiency both matter and clients are long-lived.Clients are short-lived, huge, and rarely-active (millions of idle mobile apps): holding a socket per client is wasteful — lean on push notifications to wake them instead.
Pull (poll)Low-frequency updates, simple stateless clients, or reconciliation/catch-up sync after reconnect.Real-time messaging: polling is either laggy or wasteful, and buffers pending messages needlessly.

Open channel — WebSocket vs long-poll

ChoiceUse whenAvoid when
WebSocketHigh message rates, true bidirectional traffic, per-message overhead matters — the default for Messenger-class chat.Networks/proxies that block upgrades, or infra that can't manage millions of stateful sockets and heartbeats.
Long pollingRestrictive networks, simpler infra, or as a graceful fallback beneath WebSockets.Chatty, latency-critical, high-throughput streams — each message pays a full request/response and reconnect cost.

Message store — wide-column vs RDBMS vs document

ChoiceUse whenAvoid when
Wide-column / LSM (HBase, Cassandra, ScyllaDB)Write-heavy firehose of small records with per-key range reads — exactly the message log.You need multi-row transactions, joins, or ad-hoc analytical queries.
RDBMSUser accounts, relationships, billing — relational integrity, joins, transactions.The high-volume message hot path — row-per-message cost and index churn don't scale.
Document storeFlexible, self-contained documents read/written whole.The chat firehose — Discord hit exactly this wall on MongoDB and moved to Cassandra, then ScyllaDB.

Partition key — UserID vs MessageID

ChoiceUse whenAvoid when
UserIDReads are per-user history; co-location also gives a cheap single-shard per-user sequence counter.A single user's volume alone exceeds one shard's capacity (rare here; celebrity/bot accounts may need sub-sharding).
MessageIDNever, for conversation history.Any per-conversation range read — it scatters one thread across shards into a slow scatter-gather.

Bottom line: Messenger is a push system over WebSockets, backed by a wide-column message log sharded by UserID, with a per-user sequence number for device-consistent ordering, and push notifications to bridge the offline gap.

Sources

🪜 Drill ladder: Designing Facebook Messenger

  1. L1 — Group chat fan-out: a 200-member group, every member on a different chat server. One message arrives at the group's owning server — count the cross-server writes it triggers, and compare against fanning out at the sender's server instead. (Answer: ~200 either way, but only the GroupChatID-owner design keeps ordering per group on one box.)
  2. L2 — Presence accuracy: 500M users averaging 300 friends, each flipping online/offline twice a day. Compute the eager-broadcast rate (500M × 2 × 300 ≈ 300B notifications/day ≈ 3.5M/s) and state which two of this page's softenings kill most of that load.
  3. L3 — Reconnect stampede: one chat server dies holding 50K sockets. Compute the reconnect QPS at the LB if clients retry uniformly within 1s versus jittered over 30s (50K/s vs ~1.7K/s), and name what else must be rate-limited besides the handshake (catch-up sync reads).
  4. L4 — Delivery receipts vs durability: with the WAL append on the ack path, estimate the added sender-perceived latency (~1–5ms sequential append) and state exactly what an ack-before-store design silently loses when the chat server crashes (acknowledged-but-unstored messages).
🤖 Don't fully get this? Learn it with Claude

Stuck on Designing Facebook Messenger? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Designing Facebook Messenger** (System Design) and want to truly understand it. Explain Designing Facebook Messenger from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Designing Facebook Messenger** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Designing Facebook Messenger** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Designing Facebook Messenger** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes