Additional Future Requirements:
Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
The APIs can be divided into three categories, Control Plane APIs, Non Real Time Data plane APIs and Real Time Data WebSocket APIs for pushing server events.
Control plane controls non-real time APIs:
GET /api/v1/friends <- Returns Friends with Online Status.
Group Control APIs:
POST /api/v1/group/create <- Create Group. Specify Friends who are part of the group.
PUT /api/v1/group/settings <-Setting related to group such admin permissions, setting members as admin.
GET /api/v1/group/settings <- Read settings
GET /api/v1/group/list <- List Groups user is part of.
DELETE /api/v1/group/delete <-Delete Group
For Data Plane:
Non Real Time APIs:
POST /api/v1/message/
GET /api/v1/messages/
POST /api/v1/heartbeat <- Periodically called to make sure user status says online.
For real time APIs, App will establish a SSE connection that server can use to push SSE events back to client to /api/v1/updates
SSE Event:
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
User connects to load balancer which then connects to API gateway and to Control plan server APIs. The control plane server apis talks to Group Management Service and User Management Service. The group management service talks to No SQL Db which maintains the Group information about who is the group and members and other metadata info.
The User management service similarly service talks to the No SQL Db which maintains the information about the users and their metadata.
The other main service is the message service which is responsible for storing messages sent to Group or User in the No SQL Db and can be used to retrieve the message history. Using this you can retrieve the messages that were sent.
The Message service also talks to the redis cluster which maintains the live messages that are being sent and stores them in the message No SQL Dbs.
The user also opens a connection to Real Time server which then uses SSE events to push the messages back to the user.
Here is the flow, when the user logs in, app connects to real time server create a channel in the Redis Cluster which allows anyone to post the message to it i.e. other users can post the message. If the user stop sending the heat beat, the channel is closed.
When a user post a message, the control plane server forwards the message to the message service, which then store the messgae in the No SQL DB, and also check if a recipient channel is open in the redis cluster. If channel is open, it means the user is online and you can send the message which will allow redis to generate event and send message back to the user.
If channel doesn't exists, then message server simply sends it to notification service which will then send a notification to the user using push system (iOS/Android). So when the user login in, the messages are then directly retrieved from the message service using control plane.
API Gateway handles any burst and abuse of control plane apis i.e. throttling per user etc. It's also responsible for authenticating the users.
For messages sent to the Group, similar path is followed, the control plan API receives the message, that message is then forwarded to the message service along with information about the group itself from the group management service. The messages are the forwarded to Group Fan Out service which effectively figures out which users the messages needs to be delivered to and send sends the message to the all user channels. The message is also stored in the message No SQL DB.
When the real time servers forwards the message to the app, the app can also call back the Control Plane API to mark that particular message as received. This allows for user to retrieve only messages which were not received before when the app initially boot ups. This also takes care of the issue when app reconnects.
If the app loses connectivity, it simply reestablishes the connection and calls control plane api to recieve all unreceived messages. The key point to note is that each message is identified with globally unique messageId (could be generated using UUID), which allows for de-duplication etc.. i.e. even if app accidentally receives same messages from control plane and real time plane, it know it;s same message and can easily manage it. All messages are timestamped as well to make sure message are displayed in order.
Redis Channels are managed by the real time servers. If the user loses the connection, the channels are closed. Every message is stored in the message No SQL db, so messages are never lost. If the connection is lost, the message is never delivered and when app reconnect, it will simply open the channel again. App will get the message through the control plane.
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
Let's dive into some the details around the Message Service and how messages are stored.
The message service uses messageId generation service to create a globally unique messageId which is attached to the message, along with other meta data such as timestamp, userId of sender, and userId of the recipient and additionally group Id.
If the message is send 1:1, then first No SQL db store 1:1 messages, where primary Key is simply user1:user2 . the order of userId could simply be lexicographic Order as what matter is simply messages are stored for a pair of users. The attributes could include sender, receiver, timestamp, messageId and received. Separate indexes can be created based on sender, receiver, and messageId.
receiver Index is used to receive messages that are unread i.e. if they are not marked received then then can be received during app boot using control plane. When a message is delivered through the real time server to the app. The app can post a control plan call with the messageId marking it received. This allows message to be marked delivered and it won't be auto fetched next time unless explicitly requested like on cold start (or fresh install).
If app detects that connection has been dropped to real time server, it should reestablish the connection and fetch unread messages from control plane, this will make sure any unread messages are delivered.
For the group message, the no sql simply stores the groupId as key and other attributes such as sender, timestamp. Fan out service takes care of publishing group messages with appropriate attributes to the user channels in redis.
Now let's talk about how messages are marked
Also let's dive into the Redis Cluster for real time message delivery. We can take advantage of redis pub/sub method where redis cluster is used to create pub/sub. When a user establishes a connection to real time server, it rt server creates a subscription with userId. This allows anyone to publish to the userId to send message back to the user. This is what is used to real time mechanism. They key thing to note here is that if the user disconnects or rt server lose the connection, the channel is unsubscribed. Further real time servers could periodically run a clean job which makes sure only the channels which are active are remained open. This can also be enforced through the heart beat logic. If rt servers do not recieve a heart beat from the user, they can close the connection.
During high traffic scenarios, the fanout service maintains a throttling queue, which allows only limited number of messages to be published to the channel, hence to not overwhelm the group.