Chat / Messaging System — Cheatsheet¶
One-page revision. Reproduce from memory and you can drive the interview.
Problem¶
Real-time 1:1 + group messaging (WhatsApp/Messenger/Slack): sub-second delivery, presence, receipts, multi-device history, offline push — at hundreds of millions of concurrent connections, tens of billions of messages/day.
Profile (say first)¶
Connection-bound, not request-bound. Stateful real-time plane (WebSocket gateways) + durable storage plane (source of truth). You pay for who's connected, not who's talking.
Requirements¶
- Must: real-time 1:1 + group, durable multi-device history, ordered at-least-once delivery + dedup, presence, receipts, offline push.
- Defer/seams: large-group/broadcast fan-out, media (object store + URL), E2EE (a mode), server search.
- Dominant NFRs: massive concurrent connections · low-latency ordered delivery · no message loss · multi-device sync. Presence/receipts explicitly softer.
Numbers (example)¶
| Concurrent connections | ~200M → ~1k stateful gateways, sized by RAM/conn (~2 TB) — the cost |
| Heartbeats | ~6.6M frames/sec before any real message |
| Messages | 50B/day → ~500k/s avg, ~2.5M/s peak (receipts add ~2×) |
| Fan-out | 1 write → up to millions of deliveries (groups); delivery ≫ send |
| Storage | ~15 TB/day, ~5.5 PB/yr → wide-column by conv_id, tiered retention |
Architecture¶
clients ── WebSocket ──► [Gateways] (stateful, sticky, hold sockets, heartbeat, register user→gateway)
│ send
[Message Service] dedup(clientMsgId) · assign SEQ · PERSIST · resolve members
│
┌────────────────┼─────────────────┐
[Routing/PubSub] [Message Store] [Offline?] → [Notification System] (push)
user→gateway wide-column
(Redis, TTL) partition=conv_id, cluster=seq
│
recipient [Gateways] ── push over socket ──► clients ── receipt(delivered/read) ──►
Key ideas¶
- Two ids run everything: server per-conversation
seq(ordering + gap detection + sync cursor) and clientclientMsgId(idempotent dedup). At-least-once + dedup = exactly-once effect. - Persist before deliver — message is safe even if the gateway dies; connection tier is disposable.
- Routing:
user→gatewayregistry (Redis, ephemeral, TTL) + pub-sub bus; stale entry → fall back to push. - Receipts: cumulative (
upTo: seq) → idempotent, self-healing, softer/batchable lane. Cap per-member read receipts in big groups. - Presence/typing: best-effort, TTL-derived from connections, never persisted, shed first under load.
- Multi-device:
user → many connections; per-device read cursors converge on max; sync-after-cursor on reconnect. - Groups: small = fan-out on write; huge/broadcast = fan-out on read / pull.
Transport¶
WebSocket (full-duplex, low overhead) + long-poll fallback. Auth on connect, not per message. Sticky sessions.
Bottlenecks (ranked)¶
- Connection fleet → scale gateways horizontally + lean bytes/conn
- Routing/fan-out → pub-sub bus + dedicated fan-out workers
- Hot conversation/partition → queue + pace; flip broadcast to pull
- Message-store writes → partition by conv_id; receipts softer lane
- Reconnect thundering herd → backoff+jitter + accept rate-limiting
Failure one-liners¶
- Client/gateway drop → persisted already → reconnect + cursor sync (never lost).
- Store write fails → don't ack → client retries same clientMsgId (idempotent).
- Stale registry → delivery miss → push + sync (not loss).
- Dup → dedup on clientMsgId. Gap → detect by seq, replay from store, render by seq.
- Hot group → fan-out workers + queue; broadcast → pull. Offline → push + sync.
- Presence/receipts → best-effort, self-healing, shed before messages.
Patterns used¶
websockets/real-time · queues+workers · idempotency · sharding · caching · rate limiting · notifications (offline push).
SQL vs NoSQL¶
Messages → wide-column by conv_id + seq. Conversations/members → relational/wide-column. Read cursors → KV. Routing registry → in-memory Redis (ephemeral, TTL).
Summary line¶
"Two planes: stateful WebSocket gateways for live connections + a durable wide-column store that's truth. On send I dedup on a client id, assign a per-conversation sequence number, persist, then route to the recipient's gateway via a Redis registry + pub-sub — persist before deliver. At-least-once + dedup = exactly-once effect; the seq number is ordering, gap detection, and the reconnect sync cursor. Small groups fan out on write, broadcasts flip to pull. Presence/receipts are best-effort; offline gets a push + cursor sync. The connection tier is disposable behind the store."
← Back to README