List functional requirements for the system (Ask the chat bot for hints if stuck.)...
List non-functional requirements for the system...
Estimate the scale of the system you are going to design...
QPS read : 10k
QPS write : 100
size of one record = 2100 bytes
TTL = 30 days
Total capacity for 30 days = 2100 * 100 * 3600 * 24 * 30 = 0.54 Tb
Instance size estimation
QPS
Read - 10k
Write - 100
16k total (assuming 50% extra bandwidth for spiky traffic)
No of clusters needed = 6 (assuming 2k bandwidth per cluster) each cluster has some nodes and each node has 6 cores : 1 leader and 2 followers per region
Replication : cross dc replication
Consistency : we need local read after write consistency so we can use regional read after write winds strategy. This is eventually consistent and not strongly consistent
Hot shards : we will need to monitor load distribution at shard and parition level. If there is uneven load distribution we may benefit from caching the most frequent urls. There are disadvantages to caching too and we will need to monitor cache hit ratio.
Define what APIs are expected from the system...
setUrl(
string url,
) (string, error)
getUrl(
string url,
) (string, error)
schema
CREATE TABLE shortened_urls(
tiny_url String (100),
url String (2000),
) ((tiny_url), url)
local indexed on url
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...
client interacts with the service via an API and gets a shortenedURL in return. Lets call our API tinyURL. On receiving a request, tinyURL makes an API call to external 3rd party like bitly and uses a cache to calls the database to check if this URL is free.
Solution1 : Local indexing on db
Solution2: caching the key-value pairs in a redis and keeping the cache up to date with database by subscribing to db deletion operations in our service and triggering cache updates
For the database :
We can add service level rate timing to handle spiky traffic and our Sharding key can be bucketed in case of hot shards. We can also add a cacheFront if problem remains.
For monitoring we can rely on platform alerts
We can also add an analytics layer on top of this which will process records in an async manner with a TTL of 24 hrs and a retention of 2 years. Because it is for offline analytics
For API design :
SLO aggrements for P99 latency of the endpoint to be within 200 ms and timeout within 500 ms. We can add retries in the client side in case of failures. We can have burn rate alerts for 5xx 4xx and P99 latency timeout issues at the endpoint.
We can add rate limiting to avoid fraud users from accessing the api.
Metrics and rollout :
Can shadow metrics and gradually rollout
For the schema design :
we chose eventually consistency model over strongly consistent so our solution can serve stale reads.
we did not go with cache in the proposal this may sacrifice on the read latency
We do not delete records. In case someone maps a record by mistake and does not want it to be retrieved our db has no such functionality
Every write call is a write call + read call since we access the db to see if the parition already existed. The advantage of doing it this way over a cache is that when TTL expires cache needs to be updated too.
Pros of secondary index & quering the databse :
cons
Since requirement isread heavy and not write heavy - not a lot of frequent db updates and thus less sync cache based soln serves better
We have a single endpoint and datastore and assuming they both can scale horizontally and add capacity we should be scalable
3rd party dependency : If they are down we are down
In the future we can definitely add caching if needed
We can extend this solution to experiment with a new schema design and if it works well migrate from old to new storage