List the key functional requirements for the system (Ask the AI for hints if stuck)...
List the key non-functional requirements (performance, scalability, reliability, etc.)...
Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
Assuming 100 million DAU, and on peak hours, 40% of the users are online at the same time and are watching matches. While watching matches, the user checks scores/status on average once per 5 seconds.
The peak read QPS = 100 million DAU * 40% * 0.2 reads per second = 8 million
Assuming that we have at most 1000 matches concurrently. For each match, we write latest match data every 2 second, the peak write QPS = 1000 * 0.5 writes per second = 500
For network bandwidth, at peak, assuming 1kb of data is transmitted on average per user, the bandwidth needed is:
1kb * 40 million = 40GB per second
For storage, we need to store:
Assuming for users, each user's metadata is 1kb, and for matches, each match metadata is 5kb.
Assuming we have 100 million users and 1 million matches.
user metadata storage = 100 million * 1 kb = 100GB
matches metadata storage = 1 million * 5kb = 5GB
Both are trivial for a modern database.
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...
For sports event to be registered:
POST v1/register_event {
event_name: String,
event_type: String,
event_time: Timestamp,
expected_event_duration: String,
event_metadata: String
}
For real-time updates for events:
POST v1/update_event_real_time {
event_id: UUID,
event_update: String
}
To read real-time event developments and statistics:
GET v1/read_live_event_stats {
event_id: UUID
}
GET v1/player_stats {
player_id: UUID
}
GET v1/game_timeline {
event_id: UUID
}
For pushing real-time events to users:
POST v1/push_event {
event_id: UUID,
user_id: UUID,
event_update: String
}
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.
In this system, we have the write path of sports events creation, and real time events ingestion, as well as the read path of browsing events feed and reading real-time event updates.
On the write path, we have a real-time data write service that ingests sports events from external sports events vendors.
For each real-time sports match update, we trigger an event on Kafka queue.
There are 3 downstreams consumers of Kafka:
On the read path, all requests from the client go through load balancers, which balance traffic based on consistent hashing. Then they go through API gateway, which does authorization, authentication and rate limiting.
There are 2 types of reads:
In case of redis cache, we will read directly from the database. To avoid overwhelming the database, we will implement circuit breakers on the request side, and load shedding on the database side.
Given the database is eventually consistent, we will implement monotonic read on the client side. We will record the latest timestamp of events that the client has received, and sent that in the request to servers. The servers can't return an older event that is older than the client timestamp.
This approach is risky if we have client-side clock skew. In that case, we can also send vector clocks. However, that introduces additional complexity and additional storage need. So it might not be the right choice for sports events update. Timestamp might still be the better option.
In case of kafka queue being flooded with overwhelming events from multiple hot sports events, we will prioritize the high-priority events in the kafka topics, and drop low-priority events as a load shedding mechanism.
Define the data model. Identify the main entities, their attributes, and relationships. Consider the choice of database type (SQL vs NoSQL) and justify your decision based on access patterns...
In this design, there are 3 types of data we need to store:
For real time events data and real time player statistics data, we will use cassandra. While for registered sports events data, we will use a relational database.
For real time events, cassandra is a good fit because:
The tradeoffs are:
Here's the sample data schemas:
table sports_game_updates {
game_id: UUID,
game_name: String,
event_update: String,
event_update_time: Timestamp
}
The partition column is game_id and the clustering key is event_update_time
table sports_game_player_statistics {
game_id: UUID,
player_id: UUID,
update_time: Timestamp,
update_event: String
}
The partition column is game_id while the clustering keys are player_id and update_time.
For the real time events, to reduce read latency, we will use a redis cluster as a read aside cache.
For writes, they are written directly to the database, and the corresponding entries in the cache are invalidated.
For reads, they access redis first, and if not available, we query the database and write the updated results to the cache.
The benefits are:
The tradeoffs are:
Both tradeoffs are acceptable in our use case.
For the registered sports events data, however, we want to use a relational database. The benefits are:
The tradeoffs are:
However, these are acceptable tradeoffs as the registered events need to be consistent, and we won't have frequent schema changes.
A sample data schema is like:
table registered_sports_events {
event_id: UUID,
event_name: String,
event_type: String,
event_time: Timestamp,
event_metadata: String
}
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
In this design, there are 3 types of data we need to store:
For real time events data and real time player statistics data, we will use cassandra. While for registered sports events data, we will use a relational database.
For real time events, cassandra is a good fit because:
The tradeoffs are:
Here's the sample data schemas:
table sports_game_updates {
game_id: UUID,
game_name: String,
event_update: String,
event_update_time: Timestamp
}
The partition column is game_id and the clustering key is event_update_time
table sports_game_player_statistics {
game_id: UUID,
player_id: UUID,
update_time: Timestamp,
update_event: String
}
The partition column is game_id while the clustering keys are player_id and update_time.
For the real time events, to reduce read latency, we will use a redis cluster as a read aside cache.
For writes, they are written directly to the database, and the corresponding entries in the cache are invalidated.
For reads, they access redis first, and if not available, we query the database and write the updated results to the cache.
The benefits are:
The tradeoffs are:
Both tradeoffs are acceptable in our use case.
For the registered sports events data, however, we want to use a relational database. The benefits are:
The tradeoffs are:
However, these are acceptable tradeoffs as the registered events need to be consistent, and we won't have frequent schema changes.
A sample data schema is like:
table registered_sports_events {
event_id: UUID,
event_name: String,
event_type: String,
event_time: Timestamp,
event_metadata: String
}
In this system, we have the write path of sports events creation, and real time events ingestion, as well as the read path of browsing events feed and reading real-time event updates.
On the write path, we have a real-time data write service that ingests sports events from external sports events vendors.
For each real-time sports match update, we trigger an event on Kafka queue.
There are 3 downstreams consumers of Kafka:
On the read path, all requests from the client go through load balancers, which balance traffic based on consistent hashing. Then they go through API gateway, which does authorization, authentication and rate limiting.
There are 2 types of reads:
In case of redis cache, we will read directly from the database. To avoid overwhelming the database, we will implement circuit breakers on the request side, and load shedding on the database side.
Given the database is eventually consistent, we will implement monotonic read on the client side. We will record the latest timestamp of events that the client has received, and sent that in the request to servers. The servers can't return an older event that is older than the client timestamp.
This approach is risky if we have client-side clock skew. In that case, we can also send vector clocks. However, that introduces additional complexity and additional storage need. So it might not be the right choice for sports events update. Timestamp might still be the better option.
In case of kafka queue being flooded with overwhelming events from multiple hot sports events, we will prioritize the high-priority events in the kafka topics, and drop low-priority events as a load shedding mechanism.
There are several other key areas: