In the world of modern chat applications, real-time communication is key. Whether it's for a messaging platform, collaborative tool, or gaming app, WebSockets offer a persistent and efficient channel for two-way communication. But what happens when you need to support thousands, or even millions, of users? Thatβs where a scalable architecture comes into play.
This blog will walk you through building a highly scalable chat microservice using WebSockets, Redis, DynamoDB, and a load balancer. Weβll explore how messages are routed, stored, and delivered efficiently across distributed services.
π Key Components
1. WebSocket Servers
These are the actual servers that handle WebSocket connections. Each server manages the live socket connections for the users it serves.
2. Redis (Pub/Sub + Socket Map)
Redis is used to keep track of where users are connected (i.e., which socket server they are on) and to publish/subscribe messages across different WebSocket servers.
3. DynamoDB (Persistent Storage)
Every chat message is stored in DynamoDB for durability and history retrieval.
4. Load Balancer
Distributes new incoming socket connections evenly across your pool of WebSocket servers.
πΊοΈ Architecture Diagram
ββββββββββββββ
β Client β
βββββββ¬βββββββ
β
βΌ
ββββββββββββββ
β Load Balancer
βββββββ¬βββββββ
ββββββββΌββββββββ
βΌ βΌ βΌ
ββββββββ ββββββββ ββββββββ
β WS1 β β WS2 β β WS3 β (WebSocket Servers)
ββββ¬ββββ ββββ¬ββββ ββββ¬ββββ
β β β
ββββββββββ΄βββββββββ
βΌ
ββββββββββ
β Redis β (Pub/Sub + Socket Map)
ββββββββββ
βΌ
ββββββββββββ
β DynamoDB β (Message Storage)
ββββββββββββ
π§© Setup Overview (Example):
πͺ User Connection Flow
1. User A opens a chat app
2. Load Balancer Routes the Request
The load balancer forwards this request to one of the WebSocket servers (e.g., WS1).
WebSocket handshake occurs.
3. WebSocket Server (WS1) handles the connection
Extracts userId from query
Stores socket info in Redis:
SET user:userA { socketId: xyz123, server: ws1 }
π¬ Sending a Message: User A β User B
First Message:
User A sends:
{
"type": "message",
"to": "userB",
"message": "Hi"
}
WS1 receives the message
WS1 looks up userB in Redis:
GET user:userB
Redis responds with ws3 (userB is connected to WS3)
WS1 publishes to Redis pub/sub:
channel: messages:ws3
payload: {
"to": "userB",
"message": "Hi",
"from": "userA"
}
WS3 receives it and sends it to userB via WebSocket
Message is also saved to DynamoDB
Second Message:
No new socket, no new Redis write
Uses existing socket to send again
Still does Redis lookup and pub/sub for delivery
π΄ User Goes Offline
1. WebSocket Disconnects
2. Another user sends them a message
3. User Comes Back Online
New WebSocket connects
Server updates Redis with new info
Checks for undelivered messages
Pushes them over the new socket
πͺ Is Load Balancer a Socket Server?
No. A load balancer is not a socket server. It only handles routing the initial HTTP/WS upgrade request to one of the WebSocket servers.
What the Load Balancer Does:
Routes initial WebSocket request
Uses round-robin or sticky session
After upgrade, client connects directly to WS server
All further communication happens directly
π Example With 10,000 Users
Infrastructure:
| Component | Count |
| Load Balancer | 1 |
| WebSocket Servers | 3 |
| Redis | 1 |
| DynamoDB Table | 1 |
Load Balancer Behavior:
Each WS server handles ~3333 users, while Redis tracks all socket associations.
π¬ WebSocket Communication Endpoints
| Endpoint | Method | Description |
/ws | WS | WebSocket upgrade endpoint |
messages:wsX | Redis PubSub | Channel for WS server wsX |
user:userId | Redis Key | Stores socket ID and server for user |
| DynamoDB Table | - | Stores persistent messages |
π Horizontal Scaling and Communication Between Servers
Servers communicate using Redis Pub/Sub:
When WS1 needs to message a user on WS3, it publishes to messages:ws3 channel
WS3 is subscribed and receives it
No direct server-to-server calls are needed β Redis handles the communication.
β¨ Conclusion
This architecture enables your chat app to:
And with Redis + DynamoDB, you get both speed and reliability. The load balancer ensures users are evenly spread and connections stay persistent without needing to restart sockets.
If you're building a high-performance chat backend, this is the blueprint to follow.