Lesson 0028 · System Design · Modul Studi kasus · ±20 menit

Studi kasus: URL shortener

Base62 vs hash, sistem read-heavy, cache untuk link populer, dan redirect 301 vs 302, dikerjakan dengan kerangka 5 langkah

Sebelumnya, lesson 0027 memberi kerangka 5 langkah. Lesson ini menjalankannya penuh untuk soal yang paling sering muncul: "rancang layanan seperti bit.ly". Perhatikan urutannya: angka dulu, desain v1 sederhana, lalu setiap komponen tambahan dibenarkan angka.

Target lesson ini: Anda bisa menjawab "Rancang URL shortener untuk 1 juta URL baru per hari" tanpa catatan, dalam 90 detik: estimasi QPS dan storage, pilihan base62 dari counter vs hash (dan collision-nya), desain v1, kapan cache ditambahkan, dan kapan memakai 301 atau 302.

Langkah 1 — Klarifikasi requirement

Langkah 2 — Estimasi

BesaranHitunganHasil
QPS tulis106 ÷ 105 detik±10 QPS, puncak ±30
QPS baca100 × QPS tulis±1.000 QPS, puncak ±3.000 (read-heavy)
Jumlah URL 5 tahun106 × 365 × 5±1,8 × 109
Storage1,8 × 109 × ±500 byte (URL + kode + metadata)±1 TB
Panjang kode626 ≈ 5,7 × 1010; 627 ≈ 3,5 × 10127 karakter base62 memberi ruang ±2.000× jumlah URL
Cache link populer10 juta link terpanas × 500 byte±5 GB, muat di satu node cache

Kesimpulan angka: tulis sangat kecil, baca 100× lebih besar, data ±1 TB. Ini sistem read-heavy, jadi deep dive nanti berfokus pada jalur baca, bukan pada menskalakan tulis.

Langkah 3 — API dan data model

POST /api/urls            { "longUrl": "https://…", "expiresAt": "2027-01-01" }
  → 201 Created           { "shortUrl": "https://sho.rt/aZ3kQ9x" }

GET /{kode}
  → 302 Found             Location: https://… (URL asli)
  → 404 Not Found         (kode tidak ada atau kedaluwarsa)

Tabel urls
  kode        varchar(7)  PRIMARY KEY      ← lookup redirect lewat index primary key
  long_url    text        NOT NULL
  created_at  timestamptz
  expires_at  timestamptz NULL

Cara membuat kode. Dua pilihan yang perlu bisa Anda bandingkan:

Counter + base62Hash URL dipotong 7 karakter
CaraAmbil angka unik dari sequence database, lalu ubah ke basis 62 (0-9a-zA-Z): 125 = 2×62 + 1 → "21"Hitung hash (mis. SHA-256) dari URL, ambil 7 karakter base62 pertama
CollisionTidak ada: setiap angka dipakai sekaliPasti terjadi: dengan 1,8 × 109 kode di ruang 3,5 × 1012, perkiraan birthday n²/2N ≈ 4,6 × 105 pasangan bentrok
KelemahanKode berurutan bisa ditebak; butuh satu sumber angkaButuh cek dan ulangi (tambah salt) saat bentrok
KelebihanSederhana dan pendekURL yang sama menghasilkan kode yang sama (dedupe gratis)

Untuk v1, counter cukup: PostgreSQL CREATE SEQUENCE membuat "a new sequence number generator", dan nilainya diambil dengan nextval. [1] Puncak 30 QPS tulis jauh dari batas satu sequence, jadi ID generator terdistribusi (mis. jatah range angka per server) belum dibenarkan. Kalau memilih hash, collision ditangkap oleh unique constraint, yang memastikan nilai kolom "is unique among all the rows in the table"; insert yang bentrok gagal, lalu ulangi dengan salt. [2]

Latihan 1 — Kode pendek

Jawab tanpa scroll ke atas. Soal yang sama akan muncul lagi, diacak, di review 0035.

Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil review 0035.

Langkah 4 — High-level design (v1)

Desain v1 paling sederhana yang memenuhi angka langkah 2: satu app server stateless dan satu PostgreSQL. Tulis: nextval → base62 → INSERT. Baca: SELECT long_url FROM urls WHERE kode = $1 lewat index primary key → 302.

Latihan 2 — Gambar diagram v1 sendiri dulu

Gambar diagram v1 Anda sendiri dulu di kertas: kotak apa saja, panah ke mana, dan jalur satu request tulis serta satu request baca. Setelah selesai, buka jawabannya.

Lihat jawaban
Client App server PostgreSQL POST · GET sequence urls (kode PK), ±1 TB / 5 th
v1: cukup untuk 30 QPS tulis dan, setelah diukur, kemungkinan besar 3.000 QPS baca lewat lookup primary key.

Jalur baca: client → GET /aZ3kQ9x → app server → lookup primary key → 302 + Location. Jalur tulis: POST → nextval → base62 → INSERT → 201.

Langkah 5 — Deep dive, bottleneck, trade-off

Evolusi dari v1 ke v2, satu komponen per alasan:

  1. Availability 99,9% (jatah ±43 menit/bulan): satu app server adalah single point of failure. Tambah server kedua di belakang load balancer; server stateless, jadi tidak ada session yang harus dipindah.
  2. 3.000 QPS baca di puncak: ukur dulu p99 query dan CPU database. Kalau p99 redirect mendekati 100 ms, tambah cache-aside di Redis untuk link populer (±5 GB). Redis menyarankan allkeys-lru "when you expect that a subset of elements will be accessed far more often than the rest", pola yang cocok dengan link viral. [3] Biayanya: satu komponen lagi, dan link yang dihapus atau kedaluwarsa bisa tetap terlayani sampai TTL cache habis.
  3. Cache miss masih membebani database: baru tambah read replica. Link baru yang telat beberapa detik terlihat di replica sudah diizinkan requirement.
  4. Sharding belum perlu: ±1 TB dalam 5 tahun dan 30 QPS tulis masih muat di satu primary. Sebut sinyal kapan naik: storage atau tulis mendekati batas satu node; saat itu shard berdasarkan hash kode.

301 atau 302? Ini trade-off yang hampir selalu ditanyakan:

301 Moved Permanently302 Found
Arti di RFC 9110Resource punya URI permanen baru; "any future references to this resource ought to use one of the enclosed URIs", dan 301 "is heuristically cacheable" [4]Resource sementara berada di URI lain; "the client ought to continue to use the target URI for future requests" [4]
EfekBrowser bisa menyimpan redirect, klik berikutnya tidak sampai ke server: beban turunSetiap klik sampai ke server: bisa dihitung, tujuan bisa diubah atau dikedaluwarsakan
Pilih kalauBeban baca adalah masalah utama dan analytics tidak dibutuhkanButuh analytics klik atau link bisa berubah (pilihan v1 di sini)
Client Load balancer App 1 App 2 Redis (cache) DB primary Read replica async cache-aside
v2: setiap kotak tambahan punya alasan angka. LB + App 2 untuk availability 99,9%; Redis untuk 3.000 QPS baca setelah p99 diukur; replica hanya kalau cache miss masih membebani primary.

Latihan 3 — Redirect dan jalur baca

Latihan lisan — Ucapkan dengan suara keras

Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.

"Rancang URL shortener untuk 1 juta URL baru per hari. Mulai dari v1, lalu jelaskan evolusinya." (90 detik)
Bandingkan dengan jawaban contoh dan checklist-nya

"Dengan rasio baca:tulis 100:1, tulisnya sekitar 10 QPS dan baca 1.000 QPS, puncak sekitar 3.000; dalam 5 tahun ada 1,8 miliar URL, sekitar 1 TB. Tujuh karakter base62 memberi 3,5 triliun kombinasi, jadi cukup. Kodenya saya buat dari sequence database lalu di-encode base62, jadi tidak ada collision; kalau pakai hash, saya harus menangkap collision dengan unique constraint dan mengulang dengan salt. v1-nya satu app server dan satu PostgreSQL dengan kode sebagai primary key. Untuk availability 99,9% saya tambah server kedua di belakang load balancer. Setelah mengukur p99 baca di puncak, saya tambah cache-aside di Redis untuk link populer, sekitar 5 GB. Redirect saya pakai 302 supaya setiap klik terhitung; 301 mengurangi beban karena browser menyimpannya, tapi analytics hilang. Sharding belum perlu karena 1 TB dan 30 QPS tulis masih muat di satu node."

Checklist: apakah Anda menyebut…

  • estimasi: ±10 QPS tulis, ±1.000 QPS baca (puncak ±3.000), ±1 TB dalam 5 tahun?
  • kode base62 dari sequence, atau hash dengan penanganan collision, dan 7 karakter cukup?
  • v1: app server dan satu database dengan kode sebagai primary key?
  • 301 (cacheable, beban turun) vs 302 (setiap klik terhitung, link bisa diubah)?
  • cache-aside untuk link populer setelah mengukur p99 atau beban database?
  • membenarkan komponen dengan angka (LB untuk availability, cache untuk QPS baca, sharding belum perlu)?

Lulus jika minimal 4 dari 6.

Bacaan lanjutan (opsional, tidak dihitung dalam durasi) RFC 9110 bagian 15.4: Redirection 3xx, ±15 menit. Beda 301, 302, 307, dan 308.

Berikutnya, lesson 0029: rate limiter terdistribusi, dengan counter di Redis dan race condition-nya.

Istilah: GLOSSARY.md (bagian "Studi kasus") · Indeks: semua lesson System Design

Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "bagaimana mencegah kode berurutan mudah ditebak?", "bagaimana menghapus link kedaluwarsa dari 1,8 miliar baris?", atau "kapan 308 lebih tepat dari 301?"

Sumber: [1] PostgreSQL docs: CREATE SEQUENCE · [2] PostgreSQL docs: Unique Constraints · [3] Redis docs: Key eviction · [4] RFC 9110: HTTP Semantics (301, 302)