The trade-off in CAP is C. If a user creates a short link in New York, it might take
50 milliseconds to replicate to a server in Tokyo. Slight delay is acceptable.
TRAFFIC
10M DAU
Users create 1M new URLs/day (~12writes/sec)
Users click (read) ~100x -> 100M redirects/day (~1200 reads/sec, ~3,000/sec at peak)
Read:write ratio = ~100:1
STORAGE
Each record ~1KB -> 1M/day -> ~1GB/day -> ~365 GB/year ->
~2 TB over 5 years.
CACHE
Read-heavy traffic warrants a cache layer (e.g. a day of hot URLs = ~100 GB)
POST /api/v1/urls with a JSON body:
{ "longUrl": "https://www.example.com/some/very/long/path?with=query¶ms=1" -> response 201 created or rejected 400 malformed
GET /{shortCode} -- e.g. GET /abc123
{ "shortURL": "https://tinyurl.com/abc123", "longUrl": "https://www.example.com/some/very/long/path?with=query¶ms=1" } -> redirect 301/302 or rejected 404 not found
301 = permanent: good for a simple service and performance
302 = temporary: for analytics
429 = rate limiter
A single, NoSQL db
Since it's literally just a distributed key-value store -> NoSQL is
preferred. The key/value shape matches the store. If we needed
custom aliases, a traditional SQL DB might be preferred.
Both DB and cache are distributed by short_code - DB shards own data permanently; cache nodes hold it temporarily, so losing
one only causes a miss.
The ID generator creates random base62 codes with the DB unique index as collision protection and retry on conflict (409).
I'd shard on hash(short_code) so each redirect hits a single shard,
and the cache absorbs hot spots
To survive an outage, we'd have pre-generated batches of codes ready to go until service is back up.
Another option is to use a service like snowflake that assigns a globally unique numeric ID and combine with our own base62-encode logic. Can be sequence-esq so salting might be necessary