Shorten the long URLs to a short URL
Support custom alias (if time permits)
Support expiry for URLs
Redirect short URLs to long URLs
Analytics for clicks
Scalability: (1 million unique URLs write per day) with 100:1 read/write ratio
Latency: p99 at 50ms
High Availability
Durability: Once the URLs are written, they should not be deleted
Unique 1-1 mapping between short and long urls
Total writes (per sec) = 10^6 / 10^5 = 10 writes / sec [Peak = 30 writes / sec]
Total reads (per sec) = 100 * 10^6 / 10^5 = 1000 reads / sec [Peak = 3000 reads / sec]
Capacity -> Long URL + Short URL + User ID + Meta data = 300 bytes / URL
Capacity in a day = 300 * 10^6 = 300 MB
Capacity in a year = 110 GB
Capacity in 5 years = 550 GB
Request:
{
"long_url" : "youtube.com/asdfdfsldfjasd",
"alias": "",
"expiry_at": "2026-08-31T10:15:06Z",
}
Response:
{
"short_url": "short.com/abkkh8"
"success": True
}
2. GET /{short_code}
Response:
{
"long_url" : "youtube.com/asdfdfsldfjasd",
"success": True
}
3. Analytics -> GET /analytics/{short_code}
Response:
{
"total_count": 500,
"expires_at": "2026-08-31T10:15:06Z",
"last_accessed": "2026-08-25T10:15:06Z",
}
Clients will connect to the load balancer, which will redirect the call to write/read service.
The write service will validate the long URL (and alias if provided) and generate short URL and writes the data to cassandra DB.
Generating short URL:
We will keep unique counters for each shard -> 10 bits for shard ID and rest 54 bit for the counter. We will increment the counter (using locks within each shard). We will generate hash for the counter using SHA256. We will take 48 bits and then use Base62 conversion for generating the short URL.
If there is a collision, we can add a salt to the counter and generate hash to avoid collision.
The read service will use cache aside method, where we will check Redis cache first for the short_url, if not, read the DB and cache the result.
The read service will also call analytics Service, which will push the read to the kafka queue, and a batch job will aggregate and flush the data into another cassandra DB, which will be used for the analytics
We are going with Cassandra DB, bcz the data we want to write is of key-value pair type and we have high read volumes.
DB 1 (Short URL mapping)
It will have the following fields: short_url, long_url, user_id, created_at, expiry_at, alias
We will have the primary key as short_url and secondary key as user_id (if we want to see the url generated by a user)
While creating the short_url, we will write only if the short_url doesn't exist. Return empty if it exists.
We need strong consistency for writes, while eventual consistency is okay for the reads.
QUOROM writes, ONE reads
Redis cache (hot keys):
We will have keys as short_url and the value as {long_url: "", expire_at: ""}
DB 2 (Analytics}:
We will have the following fields: short_url, count, last_accessed_at.
We will the primary key as short_url (our access pattern is through the short_url)
We will deep dive into the following aspects:
ID Generation will be handled by using a shard_id (10bit) + counter on each shard. Counter will be guarded by lock (lua script in Redis), so that counter is unique. We will add a small salt if there is a collision.
To prevent users from guessing the next counter, we can do an XOR of the counter with a selected mask (so that the counter is obtained by reversing the method)
High Read Speed:
We will use 302 Redirect, because we want to have the analytics. Using 301 would be fast, and return from the browser, but we will lose control of the analytics and the URL might be used even after expiry.
We will use DNS routing to route the user to the closest edge server. The data can be returned from there (along with expiry). If not present, the data will check the Redis layer, where we have the expires_at field, which will check for the expiry and return if the data is present and valid. Lastly it will hit the cassandra DB. This will help us maintain a p99 of 50ms
Analytics:
We will push the event into a kafka queue. We will have a background job which will batch the data, aggregate and flush the data into Cassandra. This could be eventually consistent, as the analytics service can wait. While flushing the data, we will store the timestamps, which will help us track the analytics based on timestamps.