Messaging & social, an interactive architecture walkthrough
Chat application (1:1 and group): how the architecture works
How a chat app keeps a WebSocket per online user, orders and stores messages per channel, and lets reconnecting clients catch up from history.
This is a learning explainer based on public engineering writing and general design practice. It is simplified, it is not the actual architecture of any company, and it may be inaccurate.
Interactive diagram
Overview
A chat application delivers messages between two people or within a group in near real time, and keeps the full history on the server so any device can load it. Each online client holds a WebSocket to a gateway server; a message is checked for membership, given the next sequence number in its channel, written to a store partitioned by channel, and then published to the members' topics so whichever gateway holds each member can push it.
The live push and the stored history play different roles. Push is fast but best effort, so clients that reconnect fetch everything after the last sequence number they have from the store. Offline mobile users are reached through APNs or FCM, and search and notifications run off an event stream. Unlike an end-to-end encrypted design, the server can read and index messages here. This is a simplified learning design, not any company's production system.
Trade-offs and what changes at scale
Server-side history makes multi-device sync and search simple but means the operator holds message content. Ordering is per channel, not global, which lets channels be spread across partitions. Publishing to per-user topics is simple for DMs and small groups, but a channel with tens of thousands of members multiplies every message, so large channels are better served by gateways subscribing to a per-channel topic. The gateway tier is stateful: deploys must drain sockets gradually and clients reconnect with jittered backoff so a restart does not become a reconnect storm.
How it works, step by step
The walkthrough above, as text. Each step lights up the components named under it.
When the app opens, it upgrades a connection through the Load Balancer to one of the Gateway Servers, which checks the token with the Auth Service. A WebSocket lets the server push new messages without the client polling.
The gateway records which server holds the user in the Session Registry with a TTL that heartbeats refresh, and subscribes to that user's topic on the Pub/Sub Bus. A gateway that crashes simply stops refreshing, and its entries expire.
A sent message travels over the socket to the Message Service, which asks the Channel Service whether the sender belongs to that DM or group. The client attaches its own message id, so a retry after a dropped connection is de-duplicated rather than posted twice.
The Message Service assigns the next sequence number for the channel and appends the message to the Message Store, partitioned by channel and time bucket. One partition then serves "the latest 50 messages in this channel" in a single read, and buckets stop any partition growing without limit.
After the write is acknowledged to the sender, the message is published on the Pub/Sub Bus to each member's user topic, and every gateway holding one of those users pushes it down the socket. The bus does not know about servers, so gateways can be added or restarted freely.
Redis pub/sub is at-most-once, so a client that reconnects asks the REST API for messages after the last sequence number it has, read from the Message Store. The store, not the live push, is the source of truth.
The same message is written to the Message Events topic, and the Push Worker checks the Session Registry for members with no live socket and notifies them through APNs or FCM. Mobile apps close their sockets in the background, so push is how they learn about new messages.
When a user reads a conversation, the client reports the highest sequence number seen and the Channel Service stores it as a last-read mark. Unread counts are the difference between the channel's latest sequence and that mark, so they need no per-message state.
The Presence Service derives online and idle status from gateway heartbeats and the Session Registry, and publishes changes only to users who share a channel. Typing indicators use the same short-lived path and are never stored.
The Search Indexer consumes message events into the Search Index, and the REST API filters results to channels the user can see. Indexing off the stream keeps search a few seconds behind without slowing sends.
Attachments are uploaded through the Media Service to the Attachment Store, and the message carries only a reference. Recipients download from the CDN, so files never pass through the socket tier.
Observability tracks open sockets per gateway, send-to-delivery latency and consumer lag on the event topic. A spike in reconnects after a deploy shows up here first.
Sequence diagram
The same walkthrough as a UML sequence diagram, one lane per layer or group (22 components in all), left to right in the order a request reaches them, numbered by step. A loop on a lane is a step that stays inside that group. Solid arrows are synchronous calls, dashed are asynchronous.
Key components
Clientslayer
- Mobile Client: iOS/Android, WebSocket while open
- Web Client: browser app, keeps one WebSocket
Edgelayer
- Auth Service: sessions and access tokens
- Load Balancer: TLS, WebSocket upgrade, spreads sockets
- REST API: history, channels, search, uploads
Real-time Tierlayer
- Gateway Servers: hold sockets, heartbeats, push frames (websocket)
- Presence Service: online, idle, typing indicators
- Pub/Sub Bus: per-user and per-channel topics (redis)
- Session Registry: user to gateway map with TTL (redis)
Messagingdomain
- Channel DB: channels, members, last-read marks (postgresql)
- Channel Service: DMs, groups, members, read state
- Message Service: membership check, sequence, persist
- Message Store: partitioned by channel and time bucket (cassandra)
Async Processinglayer
- APNs / FCM: mobile push providers
- Message Events: message created / edited topic (kafka)
- Push Worker: notifies users with no live socket
- Search Index: per-user searchable history (elasticsearch)
- Search Indexer: indexes message text
Attachmentsdomain
- Attachment Store: files and images (s3)
- CDN: attachment downloads
- Media Service: upload URLs, virus scan, thumbnails
Observability
- Observability: socket counts, delivery latency, lag
Sources
Public writing this explainer draws on. Read these for what the real systems actually do.
Make it yours
Copy this diagram and its walkthrough into your own workspace, then change anything: swap Postgres for DynamoDB, add a cache, rewrite the narration. Free, and it uses no AI credits.
Related walkthroughs
- WhatsApp-style chat appHow a chat system holds millions of persistent connections, routes end-to-end encrypted messages, and falls back to push when users are offline.
- Notification system at scaleHow a notification platform validates requests, applies user preferences, queues by priority, and delivers push, SMS and email with retries and fallbacks.
- Social news feed (Twitter-style timeline)How a social feed pushes posts into follower timelines, handles accounts with millions of followers by merging at read time, and ranks the result.
This is a learning explainer based on public engineering writing and general design practice. It is simplified, it is not the actual architecture of any company, and it may be inaccurate. Real systems differ in the details. Browse the library.