List functional requirements for the system (Ask the chat bot for hints if stuck.)...
List non-functional requirements for the system...
The system prioritises consistency in WRITE (URL generation) and availability in READ (Redirection). The writes are consistent to a primary database node this is replicated to other nodes of postgres in synchronous manner. This has a latency for writes however our writes is compartively lower than reads. Secondary nodes are only for read and a secondary node becomes primary in case of a fail over.
Estimate the scale of the system you are going to design...
The read to write ratio is 1000 reads to 1 write per sec, that is every url which is created is read 1000 times per second, hence the database design takes into consideration of designing a high read throughput.
Define what APIs are expected from the system...
POST /urls
Request: {
"userid" : "bob",
"longUrl": "www.example.com/video1",
"alias" "ABC123"
}
Response: HTTP 201 created with generated shorturl for instance https://mytinyurl.ly/ABC123
GET /urls/{alias}
Request: Path parameter
Response : 404 NOT Found, means the alias is available
This is a GET request that checks whether an alias is available against a Bloom filter in the url generator node
This is a third-party site requesting us to get the long url for a short url, for instance if some user clicks on a link "www.mytinyurl.ly/ABC123" where the host "mytinyurl.ly" is me and then I have to respond with a 301/302 HTTP response code with long url
Defining the system data model early on will clarify how data will flow among different components of the system. Also you could draw an ER diagram using the diagramming tool to enhance your design...
According to the design the read to write ratio is
userid (15 chars),
longUrl (2MB), Indexed
expiresOn (timestamp),
shortUrl (6 chars), Indexed field, Primary Key // this is also the alias in case user provided one
createdOn (timestamp)
The database is deployed as single master + replicas, where master is used as primary and for writes. The reads (Redirection) happens from a replica.
The datatabase is sharded using the primary key of URL Record that is the generated short hash for the url. The routing is done as below,
mod(abs(CRC32(short-code)), N) \\ Where N is the no of postgres nodes
We use a consistent hashing strategy to figure out which shard we should write the data. If the user provides an alias, this may appear in unexpected partition than the generated short code based hash. The code that reads the data first identifies which shard to read from by computing a mod of the short code and reach out to the respective shard.
shard_id = mod(abs(CRC32(short-code)), N)
Read operations attach the share id to the query, this will introduce a latency since the application has to calculate the hash & mod the short code, find out which shard the data is available and read from the shard. following a read the url record is added to Redis cache for further lookups or even cached at CDN.
Under a shard, the short url code is used for partitioning the data this is infact hash based partitioning.
When a short url goes viral and spikes the request, caching the url onto Redis is an option to avoid looking up in a shard. We may never know when the url is created whether its going to be viral or not, however on the first redirect we push this to Redis cache and further it stays on in Redis (with LRU eviction strategy)
You should identify enough components that are needed to solve the actual problem from end to end. Also remember to draw a block diagram using the diagramming tool to augment your design. If you are unfamiliar with the tool, you can simply describe your design to the chat bot and ask it to generate a starter diagram for you to modify...
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...
Each short url generator has a logic to generate a hash without collisions and without the need for distributed counter or hash generator. Each node takes a 36 bit id, which contains (bits from right to left)
Each url generator service connects to a global bloom filter. The Bloom filter uses hashes and keep the bits on/off for the hash and lightning fast to say whether an alias exists or not. however it might be wrong with 1% error, however it can act as first defence and avoid going into database. The given alias is checked against the bloom filter for its existence, if it says "may-be-present", we confirm this against a database lookup else we definitely let the user continue to use alias ie. when bloom filter says no then we let the alias go through.
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?