|$ curl https://forge-ai.dev/api/markdown?path=docs/system-design/chat-system
$cat docs/chat-system.md
updated Recently·40 min read·published

Chat System

System DesignAdvanced🎯Free Tools
Introduction

A real-time chat system lets users send and receive messages instantly. Designing one requires balancing low-latency delivery, persistent storage, presence awareness, and scalability across millions of concurrent connections. The canonical examples are WhatsApp, Messenger, Slack, and Discord.

The hardest challenges are connection management — keeping millions of WebSocket connections alive — and message synchronization — ensuring every device sees the right messages in the right order, even after going offline.

This case study designs a chat system supporting one-on-one chats, group chats, presence, read receipts, and media sharing. It covers requirements, capacity estimation, protocol choice, storage, and delivery architecture.

Requirements

Start by defining what the system must do and the scale it must support. Chat systems have both real-time and persistent requirements, which create tension.

Functional Requirements

Send and receive messages in real time
Support one-on-one and group chats
Show online/offline presence and last seen
Delivered and read receipts
Message history and sync across devices
Media sharing (images, files)

Non-Functional Requirements

Low latency: message delivery under 500 ms globally
High availability: 99.99% uptime
Scalability: 100M DAU, 10M concurrent connections
Durability: messages must not be lost once acknowledged

info

Reliability matters more than raw speed for chat. Users will tolerate a 200 ms delay, but they will never forgive lost messages.
Capacity Estimation

Estimating scale helps choose the right storage, connection, and message routing architecture.

capacity.txt
TEXT
1Assumptions:
2- 100M daily active users
3- Each user sends 50 messages/day
4- Total messages/day: 5B
5- Average message size: 200 bytes
6- 10M concurrent connections at peak
7- Each connection: ~10 KB memory on server
8
9Messages per second: 5B / 86,400 ≈ 57,870 msg/sec
10Peak message rate: 3× average ≈ 173,610 msg/sec
11
12Daily storage (text messages):
13 5B × 200 bytes ≈ 931 GB/day
14
15Connection memory:
16 10M × 10 KB ≈ 100 GB RAM across chat servers
17
18Media storage is orders of magnitude larger and
19is offloaded to object storage with CDN delivery.
📝

note

Message metadata often exceeds message body size. Sender ID, receiver ID, timestamps, read receipts, and encryption headers add significant overhead per message.
Real-Time Protocols

Real-time chat needs a persistent, bidirectional connection. WebSocket is the standard for browser and mobile apps. For environments where WebSocket is blocked, long polling and Server-Sent Events can fall back.

ProtocolDirectionBest For
WebSocketBidirectionalPrimary chat transport
Long pollingClient pullFallback for restrictive networks
Server-Sent EventsServer to clientLive updates, one-way streams

best practice

Maintain a single WebSocket connection per user per device. Multiplex all chat events, presence updates, and typing indicators over this one connection to reduce overhead.
High-Level Design

The chat system has three major components: a connection layer for WebSockets, a message routing layer for delivery, and a storage layer for persistence and history. A presence service tracks who is online.

chat-architecture.txt
TEXT
1High-level architecture:
2
3Clients (mobile/web/desktop)
4
5
6Load Balancer (L4/L7, sticky by connection)
7
8
9Chat Servers (WebSocket gateways)
10
11 ├─▶ Message Service (store and route)
12 │ │
13 │ ├─▶ Message Database (sharded)
14 │ └─▶ Message Queue (Kafka)
15
16 ├─▶ Presence Service (Redis)
17
18 └─▶ Media Service (object storage + CDN)
19
20Users connect to a chat server. Messages are
21routed through the message service to the
22recipient's chat server or stored for later delivery.

warning

Chat servers are stateful because they hold WebSocket connections. You cannot freely restart them without draining connections. Use connection draining and redundant instances per user to avoid dropped messages.
Message Storage

The storage model depends heavily on access patterns. Chat history is read in time order, usually paginated. Writes are append-only per conversation. A database that supports efficient range scans on a composite key (conversation_id, timestamp) is ideal.

chat-schema.sql
TEXT
1Message table schema:
2
3CREATE TABLE messages (
4 conversation_id BIGINT,
5 message_id BIGINT,
6 sender_id BIGINT,
7 content TEXT,
8 created_at TIMESTAMP,
9 PRIMARY KEY (conversation_id, message_id)
10);
11
12message_id can be a Snowflake ID that embeds
13timestamp, allowing time-ordered retrieval.
14
15Conversation membership:
16
17CREATE TABLE participants (
18 conversation_id BIGINT,
19 user_id BIGINT,
20 joined_at TIMESTAMP,
21 PRIMARY KEY (conversation_id, user_id)
22);
23
24CREATE INDEX idx_user_conversations
25 ON participants(user_id, joined_at);

Sharding Strategy

Shard messages by conversation_id to keep all messages for a conversation on the same shard. This makes range queries efficient. For very large groups, consider separate sharding or splitting message metadata from content.

best practice

Use a time-ordered ID generator such as Snowflake or ULID for message IDs. This avoids the need for a separate timestamp index and prevents insertion hot spots.
Presence and Status

Presence tracks whether a user is online, offline, or recently active. Redis is a natural fit: store user_id → status with a TTL, and update on connect, disconnect, and heartbeat. Broadcast presence changes to friends or channel members.

presence.txt
TEXT
1Presence model:
2
3Key: presence:user:{id}
4Value: { status: "online", last_seen: 1234567890, device: "mobile" }
5TTL: 60 seconds
6
7Heartbeat:
8- Client sends heartbeat every 30 seconds
9- Server extends TTL in Redis
10
11On disconnect:
12- Mark status as "offline" or "last_seen"
13- Notify subscribers (friends, group members)
14
15Fan-out:
16- For 1:1 chats, notify the single recipient
17- For groups, publish to a presence topic or pub/sub channel

info

Do not broadcast every presence change to every user in a large group. Use heartbeat batching and sampling to reduce fan-out. For massive channels, show aggregate counts rather than individual status.
Message Delivery

When a user sends a message, the system must store it, update conversation metadata, and deliver it to online recipients. Recipients who are offline retrieve messages when they reconnect via sync.

delivery.txt
TEXT
1Send message flow:
2
31. Client sends message to its chat server
42. Chat server forwards to Message Service
53. Message Service:
6 a. Generates message_id
7 b. Writes message to database
8 c. Updates conversation last_message_at
9 d. Publishes to message queue
104. Message queue fans out to recipient chat servers
115. Recipient chat servers push over WebSocket
126. Acknowledgment flows back to sender (delivered receipt)
13
14Offline recipient:
15- Message is stored; no immediate push
16- On reconnect, client requests sync from last known message_id

best practice

Decouple message storage from delivery. Writing to the database is the source of truth; delivery can retry asynchronously. This ensures messages are not lost even if a recipient is temporarily unreachable.
Read Receipts

Read receipts track when a message has been seen by the recipient. They are small but numerous: every viewed message can generate a receipt. Aggregate read receipts per conversation rather than sending one per message.

read-receipts.txt
TEXT
1Read receipt strategy:
2
3Per-conversation watermark:
4- Store max_message_id_read per user per conversation
5- Sender sees: "Read up to message X"
6- This reduces receipt volume from O(messages) to O(conversations)
7
8For group chats:
9- Store read watermark per participant
10- Display "Read by Alice, Bob" or aggregate count
11
12Delivery receipt:
13- Sent when message reaches recipient device
14- Separate from read receipt

warning

Read receipts can create a thundering herd in large groups. Use aggregation, debouncing, and batching. Send updates only when the watermark advances, not on every message view.
Group Chats

Group chats amplify fan-out. A single message in a 1,000-person group must be delivered to 1,000 devices. The architecture must limit the work done synchronously and leverage efficient fan-out mechanisms.

group-chat.txt
TEXT
1Group chat fan-out:
2
3Small group ({'<'} 1000 members):
4- Write message once
5- Fan out to each member's inbox / queue
6- Each recipient pulls from their queue on reconnect
7
8Large group (10K+ members):
9- Write message once
10- Publish to a group channel (pub/sub)
11- Online members receive push
12- Offline members sync on reconnect
13- Avoid storing per-member copies for all messages
Group SizeStrategy
< 100Direct fan-out, per-member inbox
100 - 10,000Hybrid: fan-out + channel subscription
10,000+Channel-based, pull-on-sync
🔥

pro tip

For very large groups like Discord servers or Slack channels, use a pull-based model. Clients fetch recent messages when opening the channel, and live messages are pushed over a shared channel subscription.
Multi-Device Sync

Users expect messages, read receipts, and deletions to sync across phones, tablets, and desktops. Each device maintains a sync cursor — the last message_id or timestamp it has seen — and requests everything newer on reconnect.

sync.txt
TEXT
1Sync flow:
2
31. Device connects and sends last_sync_message_id
42. Server queries messages where message_id {'>'} last_sync_id
5 for all conversations the user participates in
63. Server returns new messages, receipt updates, and metadata changes
74. Device updates local state and acknowledges new cursor
8
9Optimizations:
10- Paginate sync responses
11- Exclude muted conversations unless opened
12- Use push notifications to trigger sync on mobile
📝

note

End-to-end encryption complicates multi-device sync because each device has its own keys. Messages must be encrypted for all of a user's devices, and key changes must be propagated securely.
$Blueprint — Engineering Documentation·Section ID: SD-CHAT-01·Revision: 1.0