URL Shortener — Detailed#
flowchart TB
subgraph Client[Clients]
BR([Browser])
APP([Mobile / API consumer])
end
subgraph Edge
DNS[DNS<br/>tinyurl.com]
CDN[CDN<br/>redirect-edge cache]
LB[L7 LB]
WAF[WAF / Rate Limit]
end
subgraph App[App Tier]
GW[API Gateway]
WRITE[Shorten Service<br/>POST /shorten]
READ[Redirect Service<br/>GET /:code]
ANL[Analytics Service]
end
subgraph ID[ID Generation]
SNOW[Snowflake / Counter<br/>chunked from ZK/etcd]
B62[Base62 encode<br/>7 chars = 62^7 ≈ 3.5T]
CUST[Custom alias check<br/>collision check]
end
subgraph Data[Data Layer]
RDS[(Redis hot cache<br/>code -> url<br/>LRU + TTL)]
BF[Bloom filter<br/>existing codes]
KV[(Sharded KV<br/>Cassandra / DynamoDB<br/>by hash code)]
META[(Postgres<br/>users, custom domains, plans)]
CLICKS[[(Click events<br/>Kafka -> ClickHouse)]]
end
subgraph Async
Q[[Kafka click topic]]
AGG[[Stream aggregator<br/>Flink / Spark]]
DASH[Dashboard]
end
subgraph Obs[Observability]
M[Metrics: redirect QPS,<br/>p99 latency]
L[Logs]
T[Traces]
end
BR --> DNS --> CDN --> LB --> WAF --> GW
APP --> DNS
GW --> WRITE
GW --> READ
WRITE --> CUST
WRITE --> SNOW --> B62
B62 --> KV
WRITE --> META
READ --> RDS
RDS -. miss .-> KV
KV -. fill .-> RDS
READ --> BF
READ -.fire&forget.-> Q
Q --> AGG --> CLICKS --> DASH
App -.metrics.-> M
App -.logs.-> L
App -.traces.-> T
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class BR,APP client;
class DNS,CDN,LB,WAF,GW edge;
class WRITE,READ,ANL,B62,CUST,BF service;
class SNOW,KV,META,CLICKS datastore;
class RDS cache;
class Q,AGG queue;
class DASH,M,L,T obs;
Key design choices#
- ID strategy: pre-generate counter blocks from ZooKeeper/etcd, encode base62, 6–8 chars.
- Alternative: hash(longUrl) → base62 → check collision (deterministic, dedupes).
- Storage: Cassandra/DynamoDB keyed by code, 2 replicas across AZs.
- Redirect: 301 (cacheable, browser caches forever) vs 302 (every hit logged).
- Analytics: never block redirect on logging — async to Kafka.
API#
POST /shorten { url, custom_alias?, ttl? } -> { code, short_url }
GET /:code -> 301/302 to long url
GET /api/v1/:code/stats -> { clicks, geo, referers, time series }
Schema#
url(code PK, long_url, owner_id, created_at, expires_at, custom bool)
clicks(code, ts, ip_hash, ua, geo, referer) -- ClickHouse / BQ
users(id, plan, api_key_hash)
Glossary & fundamentals#
Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
Load balancer / GSLB | L4/L7 traffic distribution and failover | load-balancer |
HLD |
CDN | edge caching for static assets | cdn |
HLD |
API gateway / BFF | single ingress, auth, rate limit, routing | api-gateway |
HLD |
Pub/Sub & message brokers | topics, consumer groups, delivery semantics | pub-sub-pattern |
HLD |
Leader/follower replication | sync/semi-sync/async replication, failover | replication-leader-follower |
HLD |
Probabilistic data structures | Bloom, HLL, Count-Min, MinHash, t-digest | probabilistic-data-structures |
HLD |
Observability | metrics, logs, traces, SLOs | observability |
HLD |
Batch & stream processing | Lambda vs Kappa, watermarks, windows | batch-stream-processing |
LLD |
Behavioural patterns | Strategy, Observer, State, Command, Chain | behavioral-patterns |