System Design: URL Shortener — System Design

Mari praktikkan system design dengan merancang URL Shortener seperti bit.ly — service yang mengubah URL panjang menjadi URL pendek. Step 1: Requirements Functio

Mari praktikkan system design dengan merancang URL Shortener seperti bit.ly — service yang mengubah URL panjang menjadi URL pendek.

Step 1: Requirements

Functional:

Non-functional:

Step 2: Estimasi Kapasitas

Asumsi:
- 100 juta URL dibuat per bulan
- Rasio read:write = 100:1 (100x lebih banyak redirect dari create)
- Data disimpan 5 tahun

Perhitungan:
- Write: 100M / 30 / 24 / 3600 ≈ 40 URL/detik
- Read: 40 × 100 = 4.000 redirect/detik

Storage:
- 100M × 12 bulan × 5 tahun = 6 miliar URL
- Rata-rata 500 bytes per record → 6B × 500 = 3 TB

Step 3: Desain Short Code

6 karakter dari [a-zA-Z0-9] = 62^6 = 56,8 miliar kombinasi (cukup untuk 6 miliar URL).

// Approach 1: Base62 encoding dari auto-increment ID
function encode(id) {
  const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  let result = "";
  while (id > 0) {
    result = chars[id % 62] + result;
    id = Math.floor(id / 62);
  }
  return result.padStart(6, "0");
}
// encode(1000000) → "4c92" → predictable! Bisa ditebak.

// Approach 2: Random + check collision
function generateCode() {
  const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  let code = "";
  for (let i = 0; i < 6; i++) {
    code += chars[Math.floor(Math.random() * 62)];
  }
  return code;
}
// Perlu cek database apakah sudah ada (collision)

Step 4: Arsitektur High-Level

Client → Load Balancer → API Servers → Database (write)
                                     → Cache/Redis (read)
                            ↓
                      Cache (Redis)
                      Key: short_code
                      Value: original_url
                      TTL: 24 jam

Step 5: Database Schema

CREATE TABLE urls (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  short_code VARCHAR(10) UNIQUE NOT NULL,
  original_url TEXT NOT NULL,
  user_id BIGINT,
  created_at TIMESTAMP,
  expires_at TIMESTAMP,
  click_count BIGINT DEFAULT 0,
  INDEX idx_short_code (short_code)
);

Step 6: API Design

POST /api/shorten
  Body: { "url": "https://very-long-url.com/..." }
  Response: { "short_url": "https://short.ly/abc123" }

GET /:code → 301 Redirect ke URL asli

GET /api/stats/:code
  Response: { "clicks": 1234, "created_at": "..." }

Step 7: Redirect Flow (performa kritis)

app.get("/:code", async (req, res) => {
  const code = req.params.code;

  // 1. Cek cache dulu (< 1ms)
  let url = await redis.get("url:" + code);

  if (!url) {
    // 2. Cache miss → query database
    const record = await db.query("SELECT original_url FROM urls WHERE short_code = ?", [code]);
    if (!record) return res.status(404).send("Not found");

    url = record.original_url;
    // 3. Simpan di cache
    await redis.set("url:" + code, url, "EX", 86400);
  }

  // 4. Increment click count secara asinkron (jangan blok redirect)
  clickQueue.add({ code });

  // 5. Redirect (301 = permanent, bisa di-cache browser)
  res.redirect(301, url);
});

Optimasi tambahan:

🎭 Analogi sehari-hari: URL shortener = penerjemah kode pos. URL panjang = alamat lengkap "Jl. Mawar No. 17 RT 03 RW 05 Kelurahan X Kecamatan Y Jakarta Selatan 12345". Short URL = "JKT-MWR-17". Lebih cepat ditulis, tetep nyampe ke tempat yang sama. Tukang pos punya tabel mapping kode → alamat lengkap.

💡 Klasik interview SD: URL shortener = "hello world" untuk system design interview. Bagus karena cover banyak konsep: capacity estimation, ID generation, caching, hot key, redirect performance, analytics. Simple problem space tapi tiap keputusan ada trade-off.

⚠️ Jebakan klasik:

🎯 Trade-off keputusan:

TL;DR: URL Shortener = klasik interview SD. Cover: capacity estimation + ID gen (random+collision atau base62) + cache hot URL + 301 redirect + async analytics. Hot URL = critical optimization. Performance redirect (<50ms) = priority utama.