Design TinyURL
Design a URL shortener supporting ~100M new URLs/day and ~10B reads/day. Short links must be 7 characters and resolve in <100ms globally.
Required building blocks
Load Balancer
CDN
Cache-Aside (Lazy Loading)
Key-Value Store
Sharding
Nice to have
Rate Limiting
API Gateway
Canonical answer
Heavy read skew → CDN + cache aside is critical. Base62 encoding of an auto-increment or hash; KV store for short→long lookup; sharded by short_id hash.
Capacity estimation
- 100M new URLs/day → ~1,200 writes/sec average; ~3,000/sec at 2.5x peak.
- 10B redirects/day → ~115,000 reads/sec average; ~290,000/sec at 2.5x peak.
- Read:write ratio ~100:1 — design pivots on read path, not write path.
- 5-year storage: 100M × 365 × 5 × ~500 B/record ≈ 90 TB of metadata.
- Cache hot set (~20% of URLs serve 80% of traffic): ~70M entries × 500 B ≈ 35 GB across Redis cluster.
- Bandwidth: redirects are 301 + tiny body; CDN absorbs the long tail, origin sees <5% of reads.
Architecture
Client ─→ CDN (edge cache hot short codes) ─┐
│ miss
Client ─→ DNS ──────────────────────────────┘
│
▼
Load Balancer
│
┌──────────┴──────────┐
▼ ▼
API Server API Server (stateless, autoscaled)
│ │
├─→ ID Generator (Redis INCR / Snowflake) → base62
│
└─→ Redis Cache ──miss──→ Sharded KV (DynamoDB / Cassandra)
shard key = hash(short_id)
│
└─→ Kafka → AnalyticsAPI
- POST /shorten { long_url, custom_alias?, ttl? } → { short_url, expires_at? }
- GET /:short_id → 301 redirect to long_url (cache-aside lookup)
- DELETE /:short_id (owner-authenticated) → 204
- GET /:short_id/stats → { clicks, top_countries, ts_series } (async pipeline)
Data model
urls (KV, partitioned by short_id):
short_id (PK) : string, 7 chars base62
long_url : string
owner_id : string? nullable for anonymous
created_at : timestamp
expires_at : timestamp?
click_count : counter eventually consistent via stream
users (SQL, optional):
user_id (PK) : uuid
email : string
api_quota : int requests/dayConcept blurbs
Load Balancer
Distribute requests across healthy backends (L4 or L7).
CDN
Edge-cached static (and sometimes dynamic) content close to users.
Cache-Aside (Lazy Loading)
App reads cache first; on miss, loads from DB and populates cache.
Key-Value Store
O(1) get/put by key; massively scalable (DynamoDB, Redis, Cassandra).
Sharding
Partition data across DB instances by key (hash, range, or geography).
Rate Limiting
Throttle requests per user/IP with token bucket, leaky bucket, or sliding window.
API Gateway
Single entry point: auth, rate limit, routing, transformation.