- Step 1 - Understand the problem and establish design scope
- Step 2 - Propose high-level design and get buy-in
- Step 3 - Design deep dive
- Step 4 - Wrap up
Step 1 - Understand the problem and establish design scope
So, we have chats and groups.
Step 2 - Propose high-level design and get buy-in
How to design the communication?
Polling
Polling is a technique that the client periodically asks the server if there are messages available. Depending on polling frequency, polling could be costly.
Long polling
In long polling, a client holds the connection open until there are actually new messages available or a timeout threshold has been reached.
- A server has no good way to tell if a client is disconnected.
- It is inefficient. If a user does not chat much, long polling still makes periodic connections after timeouts.
WebSocket
WebSocket is the most common solution for sending asynchronous updates from server to client. WebSocket connection is initiated by the client. It is bi-directional and persistent. Starts as HTTP protocol switches to WebSocket communication.
Stateful Service The only stateful service is the chat service. The service is stateful because each client maintains a persistent network connection to a chat server. In this service, a client normally does not switch to another chat server as long as the server is still available.
Let’s combiner services into one
Storage
Relational databases or NoSQL databases?
Data models
Just now, we talked about using key-value stores as our storage layer. The most important data is message data. Let us take a close look.
Message table for 1 on 1 chat
The primary key is message_id, which helps to decide message sequence. We cannot rely on created_at to decide the message sequence because two messages can be created at the same time.
Message table for group chat
The composite primary key is (channel_id, message_id). Channel and group represent the same meaning here. channel_id is the partition key because all queries in a group chat operate in a channel.
Message ID
• IDs must be unique. • IDs should be sortable by time, meaning new rows have higher IDs than old ones.
How can we achieve those two guarantees? The first idea that comes to mind is the “auto_increment” keyword in MySql. However, NoSQL databases usually do not provide such a feature. The second approach is to use a global 64-bit sequence number generator like Snowflake. (concatenating UUID and time)
The final approach is to use local sequence number generator. Local means IDs are only unique within a group. The reason why local IDs work is that maintaining message sequence within one-on-one channel or a group channel is sufficient. This approach is easier to implement in comparison to the global ID implementation.
Step 3 - Design deep dive
For the chat system, service discovery, messaging flows, and online/offline indicators worth deeper exploration.
Service discovery
The primary role of service discovery is to recommend the best chat server for a client based on the criteria like geographical location, server capacity, etc.
Apache Zookeeper [7] is a popular open-source solution for service discovery. It registers all the available chat servers and picks the best chat server for a client based on predefined criteria.
Message flows
It is interesting to understand the end-to-end flow of a chat system. In this section, we will explore 1 on 1 chat flow, message synchronization across multiple devices and group chat flow.
You can think of the message sync queue as an inbox for a recipient.
Message synchronization across multiple devices
Small group chat flow
In comparison to the one-on-one chat, the logic of group chat is more complicated
Online presence