DevOps Peach

System Design for DevOps · Design a System: Three Worked Cases · Lesson 1 of 3

Design a URL Shortener

A URL shortener maps short codes to long URLs: a write path that generates unique codes, and a very read-heavy redirect path served from cache, with a key-value store and analytics off the hot path.

Practice interactively →

The method for every case: 1) requirements, 2) estimates, 3) API, 4) data model, 5) architecture, 6) bottlenecks and scaling.

flowchart LR
  U["User clicks sho.rt/aZ3k9Q"] --> CDN["CDN / edge"]
  CDN --> LB["Load balancer"]
  LB --> API["Redirect service"]
  API --> C["Redis cache"]
  API --> KV["Key-value store: code → URL"]
  API -.->|"click event"| Q["Queue → analytics"]
  W["POST /shorten"] --> API
  %% step U: Every click is a GET on a short code, answered with an HTTP redirect.
  %% step CDN: Popular redirects can even be cached at the edge.
  %% step API: Stateless redirect service, scaled horizontally behind the load balancer.
  %% step C: Hot codes are served from Redis — most clicks never reach the database.
  %% step KV: A key-value store holds code → long URL; lookups by key only.
  %% step Q: Click analytics are sent asynchronously so they never slow down the redirect.

1. Requirements

2. Estimates

100 M new links/month ≈ 40 writes/s. Read:write 100:1 → ~4,000 redirects/s average, ~20,000 peak. 5 years × 1.2 B links × ~500 bytes ≈ 3 TB. Cache the hottest 20% of daily traffic: a few GB of Redis.

3. API

POST /v1/links        {"url": "...", "alias": "optional", "expires_at": "..."}  → 201 {"code": "aZ3k9Q"}
GET  /{code}          → 301/302 Location: <long url>
GET  /v1/links/{code}/stats

301 (permanent) lets browsers cache the redirect — fewer requests but less accurate stats. 302 keeps every click visible.

4. Data model

links(code PK, long_url, user_id, created_at, expires_at) in a key-value store (DynamoDB, Cassandra) — every lookup is by key, and it scales out.

5. Generating codes

7 characters of base62 = 62⁷ ≈ 3.5 trillion codes.

Approach Pros Cons
Hash of URL (first 7 chars) Same URL → same code Collisions to handle
Counter + base62 (ID ranges per server) No collisions, fast Sequential codes are guessable → shuffle/encrypt the number
Random + check exists Simple, unguessable Extra read per write; rare retries

A common answer: each app server reserves a block of 1,000 IDs from a central counter (Redis INCRBY or a database sequence), then encodes them in base62.

6. Bottlenecks

Traps: a relational database with joins for a pure key lookup; synchronous click counting; sequential guessable codes; forgetting expiry and cleanup.

Practice interactively →