Design a URL shortener.
basicGenerate a unique short code per long URL, store the mapping, and serve redirects from a cache-fronted key-value store; the system is read-heavy and latency sensitive.
- Requirements: shorten, redirect (301/302), custom alias, expiry, click analytics; 99.99% availability on redirect.
- Estimates: 100M new URLs/month (
40 writes/s), 100:1 read ratio (4K reads/s, peak 10x); 5 years = 6B rows x ~500 B = ~3 TB. 7 base62 chars = 62^7 = 3.5T codes. - API:
POST /v1/urls {longUrl, alias?, ttl?} -> {shortUrl};GET /{code} -> 302 Location. - Data model:
urls(code PK, long_url, user_id, created_at, expires_at); key-value or sharded SQL bycode.
Client -> CDN/LB -> Redirect Service -> Redis (code->url) -> DB (sharded by code)
\-> Create Service -> Key Gen Service (range allocator) -> DB
Redirect Service -> Kafka (click events) -> Analytics- Key generation: a counter service hands each app node a range (e.g., 1M ids) from ZooKeeper/DB, encoded to base62; no collisions, no per-write coordination. Hashing (MD5 truncated) needs collision retries.
- 301 is cacheable by browsers (less load, lost analytics); 302 keeps every click visible.
- Trade-offs: sequential ids are guessable (permute bits); range allocation wastes ids on crash (fine).
- Failure modes: cache outage sends load to DB, so use replicas and request coalescing; allocator down means nodes use their remaining range; expired links are cleaned lazily plus a TTL sweep.
- Why not hash the long URL directly? Collisions and the same URL by two users map to one code; needs extra checks.
- How to scale reads 100x? CDN caching of redirects, local in-process LRU, then Redis.