chat

Design a
Chat App

Discord, WhatsApp, Slack. Real-time messages, read receipts, and scaling to millions of concurrent connections. HTTP is not enough here.

Requirements

Functional

  • 1. 1:1 Chat and Group Chat.
  • 2. Messages should be delivered instantly (Real-time).
  • 3. Message History (Persistence).
  • 4. Online/Offline Status (Presence).
  • 5. "Seen" status (Read Receipts).

Non-Functional

  • 1. Low Latency: < 100ms for delivery.
  • 2. High Scale: 10M concurrent users.
  • 3. Consistency: Messages must appear in order.

WebSocket Architecture

settings_ethernet

Stateful Connections

We cannot use simple REST Load Balancing. User A is connected to Server 1. User B is on Server 2.

smartphone
User A
Conn: TCP/WS
Chat Server 1
Holds Connection A
Redis Pub/Sub
Chat Server 2
Holds Connection B
smartphone
User B
Conn: TCP/WS
Server 1 publishes message to Redis. Server 2 subscribes and pushes to User B.

Data Model (Cassandra)

Chat is Write-Heavy. RDBMS (Postgres) struggles with billions of inserts. Discord uses Cassandra (ScyllaDB).

Messages Table

partition_keychannel_id
clustering_keymessage_id (TimeUUID)
contenttext

Why Cassandra?

  • Log-structured merge tree (LSM) engine = Fast writes.
  • Partition by `channel_id` keeps all messages for a chat together on disk.
  • Cluster by `message_id` keeps them sorted by time.
  • Easy horizontal scaling.

Message Flow

1

User A sends message to Chat Server via WebSocket.

2

Chat Server saves message to Cassandra (Async).

3

Chat Server finds which server User B is on (using Redis/Zookeeper).

4

Chat Server forwards message to that server.

5

That server pushes to User B via WebSocket.

If User B is offline, send a Push Notification (FCM/APNS) instead.

Unread Counters

The Scalability Killer

Counting unread messages for every user in every channel is expensive. SELECT count(*) is too slow.

Optimized Approach

Store `last_read_message_id` for every user-channel pair.

Unread = Total_Messages - Index_Of(last_read_id)

Discord's Trick

They don't store the count. They client-side calculate it or only load it when you hover the channel list. Push notifications just say "New Message", not "5 New Messages" (sometimes).

Presence (Online/Offline)

How do I know if my friend is online?

Heartbeats

Client sends a "ping" every 30s. Server updates timestamp in Redis.

SET user_123_presence "online" EX 40
TTL (Time To Live) is 40s. If no ping, key expires → Offline.

Fan-out

When status changes, who do we tell?

1. Fetch all friends of User A.
2. Filter only those who are currently online.
3. Push "User A is Online" event to them.