body: {
long-url: string;
}
response: {
short-url: string;
}
response (3xx redirect): {
long-url: string;
}
Services needed:
Table: Short URLs
Columns:
short_url: string;
long_url: string;
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss trade offs, capacity, and any relevant algorithms or data structures.
Let's assume a "short" URL is 8 characters long, base-62 encoded, for human readability. This means 62^8 ~= 2x10^14 (200 trillion) possible short URLs, which is more than enough for our user base.
One way to generate a unique URL is to simply try to generate one randomly, and see if the result collides with another entry. If it does -> we can simply retry.
There would be minimal downsides, as firstly, the odds of a collision happening in our encoding space is extremely low. The response time would be very fast, as generating a random number is a very quick operation, so even if by complete chance a collision occurs, we can quickly generate a new sequence.
In the event we SOMEHOW suffer performance hits due to too many collisions from occupied encoding spaces, we can simply increase the URL length by another place, exponentially increasing our encoding space.
To ensure only one source of truth for generating URLs -> We can centralize the URL shortener service, and handle multiple requests in sequence asynchronously using a message queue within the shortener service.
We can assume that URL accesses (reads) are a much more common operation than creations (writes).
One way to handle this throughput is through a Redis cache, where we cache the most frequently accessed URLs.
A cache is sufficient here, because a vast majority of URLs that get created only get accessed a handful of times, but a handful of URLs get accessed a vast majority of times (hotspot problem). Since the amount of the most frequently accessed URLs is much less, we can easily fit these mappings into memory, where we get the advantage of significantly faster reads vs another strategy like indexing a DB and reading from disk.
A Redis cache is also much more easy to scale vs another strategy like DB sharding, which is too complex for this application.
A LFU cache invalidation policy would be the ideal choice here. Upon a cache miss, the cache would evict the lowest frequency accessed entry, and replace with the new one. On a cache hit, we can increment the access frequency count for that short URL. A TTL policy is not needed because shortened URLs persist indefinitely and are not updated. Evictions only happen on a cache miss on the least frequently accessed entry. Lookups by short URL will always route to the same DB instance.