Consider the below assumptions
With the above assumptions, let's answer the below question
How many requests do we need to handle every day?
Since we have 1 million daily users and each user makes 5 requests.
Total requests = daily active users * number of requests per user
Total requests = 1 million * 5 requests
Total requests = 5 million requests
How many Requests per Second (RPS)?
Total number of requests daily = 5 million requests (assuming each user performs 5 actions daily)
Therefore, requests per second (RPS) can be calculated as follows:
RPS = Total requests per day / Number of seconds in a day
RPS = 5,000,000 / 86,400 seconds (24 hours * 60 minutes * 60 seconds)
RPS ≈ 57.87
So, the system needs to handle approximately 58 requests per second on average.
How much storage do we need per day?
Each message is assumed to be about 500 bytes.
Total storage needed daily can be calculated as:
Daily storage = Total daily messages * Average message size
Daily storage = 5 million messages * 500 bytes per message
Daily storage = 2,500,000,000 bytes
Converting bytes to gigabytes (GB):
1 GB = 1,073,741,824 bytes
Therefore, Daily storage ≈ 2.33 GB
So, the system needs approximately 2.33 gigabytes of storage every day to accommodate the messages generated.
Database choices
Data Partitioning and Sharding
Given the distributed nature and high volume of data in a ride-sharing platform like Uber, a suitable partitioning strategy would be Horizontal Partitioning or "Sharding" based on geographical regions, where data related to users, drivers, rides, and bookings are partitioned across different geographic regions or cities.
This strategy ensures that data is distributed evenly, optimizing query performance and scalability, while also aligning with the natural segmentation of the platform's operations based on geographical locations.
The consistent hashing algorithm can be used for sharding, as it allows for efficient and balanced distribution of data across shards while minimizing data movement when the number of shards changes or nodes are added or removed from the system. This algorithm ensures that data remains evenly distributed even as the system scales, contributing to better load balancing and fault tolerance.
During peak load times or Surge, we will do a horizontal scaling of our system by adding more nodes to our system which allows for distributing the workload and data across multiple servers. This approach is preferable as it provides better scalability, fault tolerance, and flexibility to handle increasing data volume and user demand compared to vertical scaling
Fault tolerance and replication
We will need server replicas in case the Driver Location or Notification servers die. A secondary server can take control when a primary server dies. We can also store data in persistent storage like solid state drives (SSDs) to provide fast input and output. We can quickly use this persistent storage to recover data if both primary and secondary servers die.
Read/Write Separation:
Implementing read/write separation is beneficial for a ride-sharing platform like Uber. By separating read and write operations, it allows the system to optimize for performance and scalability. Read operations, such as retrieving ride details, user profiles, or driver information, are typically more frequent than write operations, such as updating ride statuses or booking new rides. Separating these operations enables the system to distribute the workload more effectively, scale read-heavy components independently, and improve overall system performance by reducing contention on the database resources. Additionally, it helps to ensure better fault tolerance and availability since read operations can still be served even if the write components experience issues or downtime.
Explain how the request flows from end to end in your high level design. Also you could draw a sequence diagram using the diagramming tool to enhance your explanation...
Dig deeper into 2-3 components and explain in detail how they work. For example, how well does each component scale? Any relevant algorithm or data structure you like to use for a component? Also you could draw a diagram using the diagramming tool to enhance your design...
Explain any trade offs you have made and why you made certain tech choices...
Try to discuss as many failure scenarios/bottlenecks as possible.
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?