Design TinyURL

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 → Analytics
API
  • 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/day
Concept 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.