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…

URL shortener adalah layanan yang mengubah URL panjang jadi kode pendek, lalu mengarahkan (redirect) siapa pun yang membuka kode itu ke URL aslinya, seperti bit.ly. Bayangin penitipan tas di mal. Kamu menitipkan tas besar dan dapat kartu kecil bertuliskan "A-17". Siapa pun yang menunjukkan kartu itu, petugas tinggal melihat daftar lokernya dan mengambilkan tas yang benar. Kode pendek itu kartunya, daftar loker itu tabel kode → URL. Ini soal interview system design yang paling klasik, jadi kita kerjakan langkah demi langkah seperti di interview.

🧪 Tebakan cepat: kode dibuat dari 6 karakter acak (0-9, a-z, A-Z), jadi ada 56,8 miliar kemungkinan. Setelah berapa link peluang ada dua link yang dapat kode sama sudah sekitar 8%: 100 ribu, 10 juta, atau 1 miliar? Tebak dulu, jawabannya ada di bagian "Langkah 4: bikin kode pendek".

Langkah 1: requirements

Soalnya: TiketNada membuka layanan link pendek tn.test untuk promotor konser, supaya link seperti https://tiketnada.test/konser/42-konser-nusantara?utm_source=whatsapp&utm_campaign=war-tiket bisa dibagikan sebagai tn.test/4c92.

Langkah 2: estimasi

Angka model contoh: 100 juta link baru per bulan, 100 kali dibaca untuk tiap kali dibuat, disimpan 5 tahun, 500 byte per link.

Yang dihitung Hitungan Hasil
Link baru per detik 100.000.000 ÷ (30 × 86.400) ≈ 38,6
Redirect per detik 38,6 × 100 ≈ 3.858
Total link 5 tahun 100.000.000 × 12 × 5 6 miliar
Storage 6 miliar × 500 byte 3 TB
Cache 20% redirect sehari 333 juta × 20% × 500 byte ≈ 33 GB

Dari tabel ini kelihatan: menulis cuma 39 per detik, ringan. Membaca hampir 4.000 per detik, jadi desainnya harus fokus ke redirect yang cepat. Dan 33 GB cukup muat di memori satu server Redis.

Langkah 3: API dan penyimpanan

POST /api/shorten   { "url": "https://tiketnada.test/konser/42-..." }
  -> 201 { "short_url": "https://tn.test/4c92" }

GET /4c92
  -> 302 Found, Location: https://tiketnada.test/konser/42-...

Akses datanya sederhana: diberi kode, ambil URL-nya. Gak ada JOIN, gak ada pencarian rumit. Itu pekerjaan key-value store seperti Redis atau DynamoDB: lookup berdasarkan key, O(1), sangat cepat. Database relasional dengan index unik di kolom kode juga bisa, tapi untuk operasi sesederhana ini overhead-nya lebih besar.

Langkah 4: bikin kode pendek

Kodenya memakai base62: 62 simbol, yaitu 0-9, a-z, dan A-Z. Dengan 6 karakter ada 62⁶ = 56,8 miliar kode, cukup untuk 6 miliar link. Dengan 7 karakter, 3,5 triliun.

Cara 1: counter + base62. Tiap link baru dapat nomor urut dari database (auto-increment), lalu nomor itu ditulis dalam base62, persis seperti menulis angka biasa tapi dengan 62 digit. Link ke-1.000.000:

Langkah Hitungan Sisa Karakter
1 1.000.000 ÷ 62 = 16.129 2 2
2 16.129 ÷ 62 = 260 9 9
3 260 ÷ 62 = 4 12 c
4 4 ÷ 62 = 0 4 4

Sisa dibaca dari bawah ke atas: 4c92.

const ALFABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

function base62(id) {
  if (id === 0) return "0";
  let kode = "";
  while (id > 0) {
    kode = ALFABET[id % 62] + kode; // sisa bagi jadi satu karakter, di depan
    id = Math.floor(id / 62);
  }
  return kode;
}

console.log(base62(125)); // "21"
console.log(base62(1000000)); // "4c92"
console.log(base62(62 ** 6)); // "1000000": id pertama yang butuh 7 karakter

Nomor urut dijamin unik, jadi gak pernah ada tabrakan. Kekurangannya: kodenya berurutan, jadi orang bisa menebak link lain (4c93, 4c94) dan menghitung berapa link yang kamu punya. Kalau itu masalah, acak dulu nomornya dengan rumus yang tetap satu-ke-satu sebelum di-encode.

Cara 2: hash atau acak. Hash URL-nya (misalnya MD5), ambil 6 karakter pertama dalam base62, atau langsung buat 6 karakter acak. Gak bisa ditebak, tapi dua link berbeda bisa dapat kode yang sama: tabrakan (collision). Peluangnya mengikuti birthday problem, kira-kira 1 − e^(−n² ÷ (2 × 56,8 miliar)) untuk n link:

Jumlah link Peluang minimal satu tabrakan
10.000 0,09%
100.000 8,4%
1.000.000 99,98% (rata-rata hampir 9 tabrakan)

Jawaban tebakannya 100 ribu, jauh sebelum ruang 56,8 miliar terasa penuh. Jadi cara hash atau acak wajib mengecek database dulu dan mencoba lagi kalau kode sudah dipakai.

Langkah 5: redirect 301 atau 302

Karena requirements-nya menghitung klik, pilih 302.

Langkah 6: baca jauh lebih banyak dari tulis

Dengan 3.858 redirect per detik, jangan ke database untuk tiap klik. Taruh Redis di depannya (cache-aside): cari kode di cache; kalau gak ada (miss), ambil dari database dan simpan di cache. Link yang sedang viral, misalnya link war tiket Konser Nusantara, hampir selalu kena cache. Sebagian kecil link mendapat sebagian besar klik, jadi cache 20% (33 GB) menangkap kebanyakan redirect.

Langkah 7: analytics tanpa memperlambat redirect

Jangan menambah jumlah_klik di database dalam request redirect: 3.858 update per detik ke baris yang sama bikin baris itu jadi rebutan. Kirim event klik ke message queue, kembalikan 302 saat itu juga, dan biarkan worker menjumlahkannya per menit. Angka klik telat beberapa detik, redirect tetap cepat.

⚠️ Jebakan yang sering ketemu

TL;DR: URL shortener menyimpan pasangan kode → URL dan me-redirect. Kerjakan seperti interview: requirements (buat link, redirect, hitung klik; cepat dan read-heavy), estimasi (100 juta link per bulan = 38,6 tulis per detik, 3.858 redirect per detik, 6 miliar link dan 3 TB dalam 5 tahun, cache 33 GB), lalu desain. Mapping cocok di key-value store (lookup O(1)). Kode dari base62 (0-9a-zA-Z): 6 karakter = 56,8 miliar kode. Counter + base62 gak pernah tabrakan (link ke-1.000.000 = 4c92) tapi bisa ditebak; hash atau acak gak bisa ditebak tapi tabrakan muncul cepat (8,4% di 100 ribu link), jadi wajib cek ulang. Pakai 302 supaya setiap klik lewat server dan tercatat; 301 di-cache browser. Cache Redis di depan database untuk beban baca, dan catat klik lewat queue supaya redirect tetap cepat.

Yang akan kamu pelajari