Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
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...
POST /api/long-urls
body {
"longUrl": string
}
GET /api/long-url/[shortUrl]
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.
Load balancer sits in front of the server, after the CDN. It routes traffic between multiple servers. Use a Level 7 load balancer here to route traffic intelligently.
We will have a read server and a write server. This separation is crucial to allow us to scale these servers separately, since reads/write demand may not always be directly correlated.
The servers handles API routes for GET and POST.
The write server.
45GB/day means that database sharding will be required.
Consistent hashing on the URL's database ID to partition shards?
CDN can actually cache the response to /api/[shortUrl] since it never changes!
Given the high read/write ratio, we are optimizing for high-read count here. This means: Caching! So, a redis cache sits in front of our database and caches responses to requests for any given URL, with the short url as the hash key, and the long url as the hash value in each KV string. Since the URL mappings never change, and extremely long TTL can be used. Maybe lazy-caching since some URLs may never be used and we don't want to overfill the cache.
Due to the nature of this system, some URLs will be read far, far more than others, with some extremely hot paths. This means that we need to optimize for hot-read-paths.
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...
Entities:
URL
{
shortURL: string; primary key
longURL: string;
createdAt; number
}
Stick with SQL - keep it simple.
The standard access pattern here is a query by shortURL, so we need an index on shortURL
BTree?
Since we are sharding the DB due to the scale, we need to decide on how the data is split. A simple approach here could be splitting on the shortURL using consistent hashing, since the hashing algorithm used to create shortURL should make this split data evenly.
CAP
We will always need to have partition error tolerance.
So the question is do we prioritize consistency or availability.
Eventual consistency is satisfactory here, since 404 on a new link is tolerable for the increase in availability (but isn't this the same in this case? - i.e. we will either see 404 if we prioritize availaibility, or a rejection of requests if we prioritize consistency?
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
Ok! I'll have a go.