Call an API to generate a URL that has a maximum length of 8 characters after the forward slash
For example: https://tinyu.com/34fumnk3
This URL will redirect to an actual URL.
Can handle traffic of 10000 tiny URL hits per second, and 100 tiny URL generations per second.
HTTP GET https://tiny.com.api/tiny {"url": "https://google.com"}
Response: {"tinyurl": "https://tinyu.com/34fumnk3"}
HTTP GET https://tiny.com/34fumnk3
HTTP Redirect (code 302)
The database maps from a tiny URL string to a long URL.
tinyString | website
--------------------------------
34fumnk3 | https://google.com
We'll have a URL generation API that converts long URLs to tiny URLs. It will store the tiny URLs in a relational database. The tiny URL strings via these steps: First a hash is generated from the URL using md5. Md5 should be fine because this hash doesn't need to be secure. Then the first 8 characters of the hash are used as the tiny string. Then we check that there is no matching string in the database. If there is already a matching string with a different long URL, we add a character to to long URL and generate a hash again.
We'll also have a HTTP redirect API which redirects requests to tiny URLs to long URLs using the 302 HTTP code.
A user makes an HTTP GET request to the tiny URL API to generate a tiny URL. This tiny URL's full URL is stored in a relational database. Then the tiny URL is returned to the user as an HTTP response.
When the user opens the tiny URL in their browser, it hits the HTTP redirect API, which redirects the request to the long URL it corresponds to using the 302 HTTP code.
If we want to enable tracking metrics for every tiny URL generated by every user, we could consider guaranteeing that the tiny URL is unique for every call to the tiny URL creation API. And whenever someone hits the tiny URL, in addition to performing an HTTP redirect, it can store metrics in a database, such as request counts, areas where people are using the API the most, etc.
We could split the URL creation APIs and URL redirect APIs into different servers, if we find that the traffic from the two APIs is interfering with the other. We could also enable CPU based autoscaling so that the number of servers can scale up as the number of requests increases.
We could use database sharding with a leader follower model to increase the read capacity of our database. This is probably a read heavy application, as more people will likely be trying to load tiny URLs than generate them.
The repeated hashing is pretty efficient and won't run into collisions too often. However, there is an increasing chance of collisions as the number of URLs increases. There are 16^8 possible URLs, as the digits are hexadecimal. This could be improved by using more characters. For example we could include all uppercase characters and lowercase characters and this would result in a 62^8 possible URLs, a significantly larger number.
Instead of using hashing, we could just start the tiny URL from a single digit string and count upwards from there. The downside is it would clearly reveal our implementation details and give insight into when URLs were generated. The upside is it would avoid the possibility of hash collisions. One potential challenge would be, if we are distributing this across multiple servers, there could potentially be a race condition where multiple servers try to increment a counter at the same time.
We are bottlenecked by the server's ability to handle API requests. We are bottlenecked by the number of possible URLs - if there are too many URLs generated already we could run into more and more hash collisions that would slow down the functionality. We are bottlenecked by the size of the database.
Like I mentioned previously, adding more possible characters would reduce the rate of hash collisions. The servers and the databases could be scaled up. If the relational database isn't able to handle the load, even with the followers sharded across multiple instances, we could consider switch to a NoSQL key value store that might be able to scale even more.