URL
Shortener
300M URLS · 100:1 READ:WRITE · <10ms REDIRECT
GET /{code} → redirect to original URL (302)
Custom alias → user specifies their own code
TTL / expiry → optional expiration date per URL
Analytics → click count, referrer, geo (optional)
OUT OF SCOPE: user accounts, dashboard, link editing
Write: 100 new URLs/sec (peak: 500/sec)
Read: 10,000 redirects/sec (peak: 50,000/sec)
Read:write ratio = 100:1
Redirect p99 latency < 10ms
Shorten p99 latency < 100ms
Availability: 99.99% (52 min downtime/year)
Durability: URLs must never be lost
| METRIC | VALUE | CALCULATION |
|---|---|---|
| Write QPS (avg) | 100/sec | given requirement |
| Write QPS (peak) | 500/sec | 5× average peak factor |
| Read QPS (avg) | 10,000/sec | 100:1 ratio × 100 writes/sec |
| Read QPS (peak) | 50,000/sec | 5× average peak factor |
| Storage per URL | ~500 bytes | 7-char code + 2KB URL + metadata |
| Total storage | 150 GB | 300M × 500 bytes |
| Storage/year | ~1.5 TB | 100 URLs/sec × 86,400 × 365 × 500B |
| Short code length | 7 chars | 62^7 = 3.5 trillion unique codes |
| Hot cache size | ~30 GB | 20% of 300M URLs × 500B (80/20 rule) |
| App servers needed | 5–10 | 50K peak QPS ÷ 10K/server |
L7 / HTTPS / SSL termination
stateless × 5–10
Caffeine · 10K items · 5min TTL
auto-increment + base62
url:{code} → long_url · LRU · 24h TTL
durable write · ACID
read traffic · async replication
fire-and-forget analytics
GET /abc1234
Check in-process cache (Caffeine): ~100ns HIT (60%): return HTTP 302 immediately ✅
Check Redis cache: ~0.5ms HIT (39%): populate L1 cache, return HTTP 302 ✅
Cache MISS (1%): query MySQL read replica: ~5ms Populate Redis (TTL 24h), populate L1 (TTL 5min), return HTTP 302 ✅
Async (does NOT block redirect): Publish click event to Kafka → Analytics consumer updates click_count
✓ Stateless, no DB for ID
✗ Must detect + retry on collision
✗ Custom alias not supported
✓ Simple implementation
✓ Short codes for small IDs
✗ DB is ID single point of failure
✗ Reveals URL count to scrapers
✓ Scales to any throughput
✓ Sortable by creation time
✗ Clock skew issues
✗ More complex
✓ No collision risk
✓ Fast fetch (no computation)
✗ Pool management overhead
✗ Wasted codes if URLs deleted
private static final String ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int BASE = 62;
public static String encode(long id) {
if (id == 0) return "0";
StringBuilder sb = new StringBuilder();
while (id > 0) {
sb.append(ALPHABET.charAt((int)(id % BASE)));
id /= BASE;
}
return sb.reverse().toString();
}
public static long decode(String code) {
long result = 0;
for (char c : code.toCharArray()) {
result = result * BASE + ALPHABET.indexOf(c);
}
return result;
}
// encode(0) → "0"
// encode(62) → "10" (like binary: 1×62 + 0)
// encode(123456789) → "8M0kX"
// decode(encode(n)) == n ✅
// 62^7 = 3,521,614,606,208 ≈ 3.5 trillion unique 7-char codes
public String getLongUrl(String shortCode) {
// L1: in-process
String url = l1Cache.getIfPresent(shortCode);
if (url != null) return url;
// L2: Redis
url = redis.get("url:" + shortCode);
if (url != null) {
l1Cache.put(shortCode, url); // populate L1
return url;
}
// DB: only 1% of traffic
UrlRecord rec = db.findByShortCode(shortCode);
if (rec == null || rec.isExpired()) return null;
redis.setex("url:" + shortCode, 86400, rec.longUrl); // 24h TTL
l1Cache.put(shortCode, rec.longUrl);
return rec.longUrl;
}
// Invalidation: on URL delete
public void deleteUrl(String shortCode) {
db.softDelete(shortCode);
redis.del("url:" + shortCode);
l1Cache.invalidate(shortCode); // invalidate all instances via broadcast
}
✓ Reduced latency for repeat visits
✓ CDN can cache the response
✗ Can't update long URL after caching
✗ Analytics are incomplete
✓ Can update long URL at any time
✓ A/B testing possible
✗ Slightly higher latency
✗ No browser-level caching
Background job: daily DELETE WHERE expires_at < NOW().
Redis TTL: set same TTL as URL expiry on cache key.
Blocklist: reserve "api", "health", "admin".
Regex validate: alphanumeric + hyphens only.
EXPIRE 3600 → if >100 → 429 Too Many Requests.
Separate limits for /shorten vs GET /{code}.
If insufficient: replicate key to N Redis shards.
CDN 301 caching as last resort (lose analytics).
Check against phishing/malware blocklist.
Detect redirect loops (sho.rt → sho.rt).
Max long URL: 2048 chars.
Monitor consumer lag: alert if >100K backlog.
Scale consumer group (add instances up to numPartitions).
Write base62 encode and decode in Java (or your language of choice):
- Handle edge cases: encode(0), encode(1), encode(62), encode(62^7 - 1)
- Verify roundtrip: assert decode(encode(n)) == n for 1000 random values
- How many chars does encode(300_000_000) produce?
- Extend: implement MD5 + truncation approach. Write the collision detection retry loop.
Extend the base schema to support:
- User accounts owning URLs — with "my URLs" listing page (paginated, sorted by created_at)
- URL groups / campaigns — tag multiple URLs together (e.g., "summer-sale")
- Per-URL hourly analytics — click count broken down by hour-of-day
- A/B testing — one short code routes 50% to URL-A, 50% to URL-B
For each: write the CREATE TABLE SQL, necessary indexes, and explain the query pattern.
Design the failure handling for each scenario. What does the system do? What does the user experience?
- Redis cluster completely unavailable
- MySQL primary goes down mid-write (during shorten request)
- ID generator service (if using external Snowflake service) is unreachable
- Analytics Kafka topic has 5-minute lag — consumer backlog of 3M events
- A single URL (/abc1234) receives 1M requests/sec (DDoS via viral celebrity tweet)
Redesign for global deployment with these constraints:
- Users in each region get <5ms redirect latency
- URLs created in US accessible from EU within 1 second
- Unified analytics across all regions
- No EU user data may leave EU (GDPR compliance)
- System remains available if one entire region goes down
Design: DNS routing strategy, data replication model, consistency choice for URL creation, analytics aggregation, GDPR compliance approach.
Home timeline generation · Celebrity problem · News feed ranking
Push vs pull model · Redis timeline cache · Hybrid approach