MUHAMMAD
FARI
MADYAN
[ Press ESC or Click to Skip ]

System Design Roadmap

1

Part 1: Fondasi Jaringan – Paket Data, TCP/UDP, TLS & REST

2

Part 2: Komponen Inti Skalabilitas – Load Balancers, CDN, Redis, API Gateway, Database & Kafka

3

Part 3: Pola & Konsep Terdistribusi – Teorema CAP, Consistent Hashing, Sharding & Saga

4

Part 4: Studi Kasus Interview Nyata – TinyURL, WhatsApp, Food Delivery & Notifikasi

Artikel ini tersedia dalam Bahasa Inggris

🇬🇧 Read in English
🇮🇩 Bahasa Indonesia📚 System Design Roadmap

System Design Roadmap Part 3: Pola & Konsep Terdistribusi – Teorema CAP, Consistent Hashing, Sharding & Saga

Bedah mendalam teori sistem terdistribusi dan pola produksi: Teorema CAP & PACELC, ring consistent hashing, sharding database horizontal, API idempoten, Transactional Outbox, orkestrasi Saga, dan CQRS.

Muhammad Fari MadyanPenulis

13 menit baca

·

7 Oktober 2026


Pendahuluan: Melampaui Sekadar Menyusun Blok Lego

Pada Part 2: Komponen Inti Skalabilitas, kita telah memetakan komponen fisik skala besar: load balancer, cache memory, database cluster, dan message broker.

Namun, engineer level junior atau mid sering kali memperlakukan komponen tersebut seperti menyusun balok Lego sederhana—mengasumsikan bahwa menghubungkan Kafka ke PostgreSQL akan langsung bekerja mulus tanpa masalah. Sebaliknya, Senior dan Principal Engineer memahami kenyataan pahit di lapangan: dalam sistem terdistribusi, segala hal yang berpotensi rusak pasti akan rusak pada waktunya (chaos engineering).

Kabel optik bawah laut bisa putus akibat gempa bumi. Node server mendadak mati di tengah-tengah proses eksekusi transfer rekening bank. Request HTTP ganda datang di luar urutan aslinya. Disk database mendadak penuh di jam sibuk.

Untuk menembus wawancara System Design level senior, Anda harus membuktikan pemahaman mendalam tentang teori sistem terdistribusi dan pola arsitektur yang telah teruji dalam menghadapi situasi bencana.

┌─────────────────────────────────────────────────────────────────────────────┐ │ SPEKTRUM POLA SISTEM TERDISTRIBUSI │ └─────────────────────────────────────────────────────────────────────────────┘ [ Batas Teoretis ] ──► Teorema CAP & Trade-Off PACELC │ [ Partisi Data ] ──► Consistent Hashing & Sharding Database │ [ Ketahanan Gangguan ] ──► Kunci Idempotensi & Deduplikasi Pesan │ [ Sinkronisasi Antarservice ] ──► Transactional Outbox, Saga Pattern & CQRS │ [ Proteksi Trafik ] ──► Distributed Rate Limiting & Token Bucket

1. Teorema CAP & Realitas Praktis Model PACELC

Dirumuskan oleh Eric Brewer pada tahun 2000, Teorema CAP menyatakan bahwa dalam sistem penyimpanan data terdistribusi yang asinkron, Anda hanya bisa menjamin dua dari tiga karakteristik berikut secara bersamaan:

                 Consistency (C)
                    ▲       ▲
                   /         \
                  /   RDBMS   \
                 /  (Single)   \
                /               \
Availability (A) ════════════════ Partition Tolerance (P)
        (DynamoDB, Cassandra)       (HBase, MongoDB, Spanner)
  1. Consistency (C): Setiap operasi baca selalu mengembalikan data penulisan terbaru atau mengembalikan pesan galat / error (Linearizability).
  2. Availability (A): Setiap node yang masih hidup selalu memberikan respons sukses non-error untuk setiap request (tanpa jaminan bahwa data tersebut adalah yang paling mutakhir).
  3. Partition Tolerance (P): Sistem tetap dapat beroperasi meskipun terjadi kehilangan pesan atau keterlambatan komunikasi antarnode akibat gangguan jaringan.

Fakta Nyata Wawancara: "CA" Adalah Mitos di Sistem Terdistribusi

Partisi jaringan (network partition) adalah keniscayaan fisik yang tidak bisa dihindari (kabel putus, switch router hang, lonjakan packet loss). Oleh sebab itu, Partition Tolerance (P) wajib dipenuhi.

Pilihan arsitektur nyata Anda selalu mengerucut pada CP atau AP:

  • Sistem CP (Konsistensi di atas Ketersediaan): Jika Node 1 terputus dari Node 2, sistem akan menolak operasi tulis atau mengembalikan error daripada melayani data yang basi atau tidak sinkron. Contoh: Google Cloud Spanner, Apache HBase, MongoDB (dengan majority write concern), sistem buku besar bank.
  • Sistem AP (Ketersediaan di atas Konsistensi): Jika terjadi pemutusan jaringan, kedua node tetap menerima operasi tulis dari pengguna secara terpisah. Sistem menerima ketidaksinkronan data sementara dan menyelesaikan konflik nantinya via eventual consistency. Contoh: Apache Cassandra, Amazon DynamoDB, Couchbase, sistem DNS.

Melampaui CAP: Teorema PACELC

Teorema CAP hanya menjelaskan perilaku sistem ketika terjadi partisi jaringan. Apa yang terjadi selama 99,9% kondisi jaringan normal tanpa gangguan?

Teorema PACELC oleh Daniel Abadi melengkapi gambaran tersebut:

Jika terjadi Partisi jaringan, pilih trade-off antara Availability vs Consistency; Else (jika kondisi normal), pilih trade-off antara Latency vs Consistency.

  • PA/EL: Bila partisi terjadi, utamakan Availability; Dalam kondisi normal, korbankan strong consistency demi Latency yang super cepat (misalnya DynamoDB, Cassandra).
  • PC/EC: Bila partisi terjadi, utamakan Consistency; Dalam kondisi normal, pertahankan Consistency ketat meskipun harus membayar biaya latency ekstra (misalnya PostgreSQL dengan replikasi sinkron, Bigtable).

2. Consistent Hashing: Mengurangi Lonjakan Eviksi pada Klaster Dinamis

Saat mendistribusikan jutaan kunci cache ke $N$ server Redis atau Memcached, rumus modulus sederhana terlihat sangat menggoda:

$$\text{Index Server} = \text{hash}(\text{key}) \pmod N$$

Bencana Algoritma Modulus Sederhana

Jika Anda memiliki 4 server ($N=4$) lalu menambahkan 1 server baru ($N=5$), angka pembagi berubah untuk hampir seluruh kunci data. Sekitar 80% dari seluruh kunci cache seketika terpetakan ke server yang salah! Hal ini memicu badai cache miss massal secara tiba-tiba di seluruh infrastruktur, membombardir database primer dan berujung pada kelumpuhan sistem total (cascading outage).

Cincin Hash Konsisten (Consistent Hashing Ring)

Consistent Hashing memetakan identitas server maupun kunci data ke atas sebuah Cincin Hash 32-bit melingkar ($0$ hingga $2^{32} - 1$):

                                  [ Server A ] (0)
                                     /       \
                                    /         \
                      [ Key 1 ] ───►           \
                                  /             \
                   [ Server C ]                  [ Server B ]
                   (2^32 * 0.66)                (2^32 * 0.33)
                                  \             /
                                   \           /
                     [ Key 2 ] ─────►         /
                                     \       /
  1. Nilai identitas server (misalnya IP address Server-A) di-hash ke titik koordinat tertentu pada cincin.
  2. Kunci data (misalnya user_49102) di-hash ke koordinat pada cincin yang sama.
  3. Untuk menentukan server mana yang menyimpan data tersebut, telusuri cincin searah jarum jam (clockwise) hingga menemukan server terdekat pertama.

Apa yang Terjadi Saat Server Ditambah atau Dihapus?

Ketika server baru disisipkan di antara Server A dan Server B, hanya kunci yang berada di antara Server A dan server baru tersebut yang berpindah lokasi. Kunci-kunci lain di seluruh penjuru cincin sama sekali tidak terpengaruh! Dengan $N$ node yang ada, penambahan atau pengurangan node rata-rata hanya memindahkan $1/N$ dari total seluruh kunci data.

Node Virtual (Virtual Nodes / Vnodes) Mencegah Beban Berat Sebelah

Pada cincin dasar, hasil hash server bisa tersebar tidak merata, menciptakan titik panas (hotspot) di mana satu server bisa menanggung 60% beban trafik.

Solusi: Petakan setiap server fisik ke ratusan Virtual Node (Vnodes) yang disebar acak di sepanjang cincin (misal Server-A#1, Server-A#2, ..., Server-A#200). Teknik ini meratakan distribusi beban data hingga variasi toleransi di bawah 2% dan memudahkan pemberian bobot kapasitas (mesin RAM 64GB diberi 200 vnodes, mesin RAM 32GB diberi 100 vnodes).


3. Sharding Database: Penskalaan Kapasitas Tulis Secara Horizontal

Ketika sebuah tabel database relasional melampaui puluhan juta baris, indeks B-Tree tidak lagi muat di memori RAM dan kapasitas IOPS disk mulai jenuh. Sharding (Partisi Horizontal) memecah satu tabel logis menjadi beberapa bagian yang disimpan pada instans database fisik yang terpisah.

┌─────────────────────────────────────────────────────────────────────────────┐ │ TOPOLOGI SHARDING DATABASE │ └─────────────────────────────────────────────────────────────────────────────┘ [ Aplikasi Backend ] │ ▼ [ Lapisan Routing Sharding ] │ ┌─────────────────────────┼─────────────────────────┐ ▼ ▼ ▼ [ Shard 1 ] [ Shard 2 ] [ Shard 3 ] (User ID 1 - 10M) (User ID 10M - 20M) (User ID 20M - 30M)

Strategi Sharding Populer

  1. Range-Based Sharding:
    • Merutekan data berdasarkan rentang nilai tertentu (misal Shard 1 memuat User ID 1 s.d. 1.000.000; Shard 2 memuat 1.000.001 s.d. 2.000.000).
    • Risiko: Titik panas penulisan (write hotspot). Karena auto-increment ID selalu bertambah, 100% trafik penulisan data baru akan membebani shard tertinggi, sementara shard lama menganggur.
  2. Directory-Based (Lookup) Sharding:
    • Menyimpan tabel pemetaan terpusat ID Entitas -> ID Shard.
    • Kelebihan: Fleksibilitas maksimal; pemindahan data antarspek shard dapat dilakukan dinamis.
    • Risiko: Layanan lookup menjadi titik kegagalan tunggal (SPOF) dan menambah overhead latensi pada setiap kueri.
  3. Hash-Based Sharding (Standar Industri):
    • Merutekan baris menggunakan rumus $\text{hash}(\text{ShardKey}) \pmod{\text{Jumlah Shard}}$.
    • Kelebihan: Distribusi penulisan data tersebar merata ke seluruh node.
    • Trade-off: Mengubah jumlah shard (resharding) memerlukan pipeline migrasi data latar belakang yang rumit.

Tantangan Rumit Sharding yang Wajib Dibahas dalam Wawancara:

  • Cross-Shard Joins: Menjalankan kueri JOIN antartabel yang berada di server database fisik yang berbeda sangatlah lambat dan mahal. Solusi: Denormalisasi skema data, duplikasi tabel referensi statis di semua shard, atau lakukan join di level kode aplikasi.
  • Transaksi Terdistribusi: Mengubah data di beberapa shard sekaligus membutuhkan Two-Phase Commit (2PC) yang memicu latensi tinggi dan penguncian tabel. Solusi: Rancang Shard Key agar entitas yang saling berhubungan selalu berada di shard yang sama (misalnya shard tabel pesanan berdasarkan user_id sehingga seluruh pesanan seorang user berada di satu shard).

4. Idempotensi: Mengatasi Jaringan yang Rentan Gangguan

Dalam arsitektur terdistribusi, jaringan internet sering memutus paket. Ketika client mengirim transaksi pembayaran lalu menerima pesan HTTP 504 Gateway Timeout, client tidak bisa mengetahui apakah server gagal memproses sebelum eksekusi, atau server sudah sukses memotong saldo namun paket respons balik terputus di tengah jalan.

Jika client secara membabi-buta mengulang (retry) permintaan tersebut, saldo pelanggan berisiko terpotong dua kali.

Client Server │ │ │ ── POST /v1/payments (Idempotency-Key: abc-123) ────> │ │ │ 1. Cek Redis: Belum ada │ │ 2. Set kunci Redis: "PROCESSING" │ │ 3. Eksekusi debet bank │ │ 4. Update status Redis: "SUCCESS", payload │ X (Koneksi putus sebelum client terima respons!) │ │ │ │ ── RETRY POST /v1/payments (Key: abc-123) ──────────> │ │ │ 5. Cek Redis: Ditemukan status "SUCCESS" │ <── HTTP 200 OK (Kembalikan Salinan Hasil Awal) ───── │ 6. Langsung respons tanpa debet ulang!

Implementasi Idempotensi Standar Enterprise:

  1. Aplikasi client selalu menghasilkan UUID unik pada header Idempotency-Key untuk setiap operasi mutasi data.
  2. Server backend menjalankan operasi pengecekan atomik di Redis (SET key "PROCESSING" NX EX 120).
    • Jika kunci sudah ada dengan status PROCESSING, kembalikan HTTP 409 Conflict (permintaan sedang dalam proses).
    • Jika kunci sudah ada dengan status COMPLETED, langsung kembalikan respons JSON yang tersimpan di cache tanpa menyentuh logika bisnis perbankan.
  3. Database menetapkan aturan UNIQUE(idempotency_key) pada tabel transaksi keuangan untuk mencegah kondisi balapan (race condition) di lapisan penyimpanan.

5. Pola Transaksi Terdistribusi: Outbox, Saga & CQRS

Pada arsitektur microservice, masing-masing layanan memiliki database terisolasi sendiri. Transaksi monolitik ACID lintas database tidak lagi dimungkinkan. Bagaimana menjaga konsistensi datanya?

Pola 1: Transactional Outbox Pattern (Menghilangkan Masalah Dual-Write)

CELAH BAHAYA DUAL-WRITE:
1. Service Order menulis data pesanan ke database PostgreSQL lokal.
2. Service Order mencoba mengirimkan event ke klaster Apache Kafka.
3. Klaster Kafka mendadak down atau timeout!
Akibat: Database memiliki data pesanan, tetapi Kafka tidak pernah menerima event. Sistem desinkronisasi!
┌─────────────────────────────────────────────────────────────────────────────┐ │ TRANSACTIONAL OUTBOX PATTERN │ └─────────────────────────────────────────────────────────────────────────────┘ [ Order Service ] │ │ 1. Transaksi SQL ACID Lokal Tunggal: │ - INSERT INTO orders (...) │ - INSERT INTO outbox_table (event_payload, status) ▼ [(Database PostgreSQL)] │ │ 2. Engine CDC (Change Data Capture, misal Debezium) │ Membaca log transaksi WAL (Write-Ahead Log) ▼ [ Debezium / Poller ] │ │ 3. Menerbitkan pesan terjamin ke broker ▼ [ Apache Kafka ]

Dengan menyimpan event ke dalam tabel outbox di dalam satu transaksi ACID lokal yang sama persis dengan penulisan entitas pesanan, kita menjamin secara matematis: jika pesanan berhasil tersimpan di DB, maka rekaman event outbox dipastikan ikut tersimpan. Komponen Change Data Capture (CDC seperti Debezium) membaca log biner WAL database dan mengalirkannya secara andal ke Kafka.


Pola 2: Saga Pattern (Transaksi Terdistribusi Tanpa 2PC)

Saga memecah satu transaksi bisnis besar yang panjang menjadi rangkaian transaksi lokal antarmicroservice. Apabila salah satu langkah mengalami kegagalan, Saga mengeksekusi Compensating Transactions (Transaksi Kompensasi) secara terbalik untuk membatalkan perubahan data sebelumnya.

Alur Sukses (Happy Path):
[ Buat Pesanan Pending ] ──► [ Kunci Stok Barang ] ──► [ Potong Saldo Bank ] ──► [ Pesanan Terkonfirmasi ]

Alur Gagal & Kompensasi:
[ Buat Pesanan Pending ] ──► [ Kunci Stok Barang ] ──► [ Saldo Gagal Debet! ]
                                       │
                                       ▼ Langkah Kompensasi Pembalik
                             [ Buka Kunci Stok ] ──► [ Tandai Pesanan Dibatalkan ]

Pendekatan Koreografi vs. Orkestrasi:

  • Choreography (Koreografi Berbasis Event): Setiap service mendengarkan event Kafka dan memicu langkah berikutnya secara otonom. Cocok untuk alur sederhana (2–4 service). Kelemahan: Sulit dilacak dan didebug saat alur bisnis semakin kompleks (spaghetti events).
  • Orchestration (Orkestrasi Terpusat): Satu service koordinator (misalnya Temporal, AWS Step Functions, atau engine orkestrator kustom) secara eksplisit memerintahkan setiap service langkah apa yang harus diambil selanjutnya dan melacak status Saga secara menyeluruh. Sangat disarankan untuk alur bisnis kritis perusahaan.

Pola 3: CQRS (Command Query Responsibility Segregation)

Pada sistem bertrafik masif, karakteristik beban penulisan (write) dan pembacaan (read) memiliki kebutuhan performa dan konsistensi yang sangat bertolak belakang (sering kali rasio perbandingannya adalah 99% baca, 1% tulis).

┌─────────────────────────────────────────────────────────────────────────────┐ │ ARSITEKTUR POLA CQRS │ └─────────────────────────────────────────────────────────────────────────────┘ [ Aplikasi Client ] / \ Tulis (Command) / \ Baca (Query) / \ ▼ ▼ [ Command Service ] [ Query Service ] │ │ ▼ ▼ [ Primary RDBMS ] [ Read Replicas / ] (Skema Normal SQL) [ Elasticsearch ] │ (Denormalized JSON) │ ▲ │ 1. Terbitkan Event │ └──────► [ Kafka ] ───┘ 2. Sinkronkan Proyeksi
  • Model Command: Menangani operasi tulis, memvalidasi aturan bisnis yang rumit, dan menyimpan data ke database relasional ternormalisasi (PostgreSQL).
  • Model Query: Mengonsumsi event dari Kafka untuk memperbarui proyeksi tampilan yang telah dihitung sebelumnya (pre-computed views) pada Elasticsearch, MongoDB, atau Redis. Kueri pencarian dapat dieksekusi dalam hitungan milidetik tanpa membebani tabel relasional!

6. Distributed Rate Limiting: Melindungi API dari Ledakan Trafik

Rate limiting membatasi frekuensi request client untuk melindungi server dari serangan bot scraping, pencurian kredensial (credential stuffing), dan lonjakan trafik mendadak.

┌──────────────────────────────────────┬──────────────────────────────────────┐ │ Algoritma │ Karakteristik & Trade-off │ ├──────────────────────────────────────┼──────────────────────────────────────┤ │ 1. Token Bucket │ Hemat memori; mengizinkan trafik │ │ │ lonjakan singkat (*burst*). │ │ 2. Leaky Bucket │ Meratakan lonjakan menjadi aliran │ │ │ stabil; membuang request jika antrean│ │ │ penuh (*queue overflow*). │ │ 3. Fixed Window Counter │ Sangat sederhana; rentan lonjakan 2x │ │ │ kuota pada batas pergantian jendela. │ │ 4. Sliding Window Log │ Akurasi 100% sempurna; boros memori │ │ │ (menyimpan setiap timestamp di ZSET).│ │ 5. Sliding Window Counter │ Sangat hemat memori; menghitung bobot│ │ │ jendela berdampingan (error < 0.05%).│ └──────────────────────────────────────┴──────────────────────────────────────┘

Implementasi Produksi: Skrip Lua Redis Sliding Window Counter

Pada klaster server API Gateway multi-node, perhitungan kuota harus bersifat atomik. Menjalankan beberapa perintah bolak-balik ke Redis secara terpisah akan memicu balapan data (race condition). Kita menyelesaikannya dengan mengeksekusi Skrip Lua Atomik langsung di dalam mesin Redis:

LUA

-- KEYS[1]: Kunci identitas rate limit (misal "ratelimit:user_123:minute")
-- ARGV[1]: Maksimal request yang diizinkan (misal 100)
-- ARGV[2]: Timestamp detik saat ini
-- ARGV[3]: Durasi jendela waktu dalam detik (misal 60)

local current = redis.call('GET', KEYS[1])
if current and tonumber(current) >= tonumber(ARGV[1]) then
    return 0 -- Kuota habis! Tolak request & return HTTP 429
else
    local count = redis.call('INCR', KEYS[1])
    if tonumber(count) == 1 then
        redis.call('EXPIRE', KEYS[1], ARGV[3])
    end
    return 1 -- Request diizinkan lewat
end

Karena mesin Redis mengeksekusi skrip Lua secara single-threaded, logika pemeriksaan kuota terjamin 100% atomik di hadapan ribuan koneksi konkuren.


Matriks Ketahanan Sistem: Ringkasan Pola Solusi

Skenario KrisisSolusi Arsitektural Terdistribusi
Kabel bawah laut putus membelah data centerTerapkan model AP (eventual consistency) atau tolak operasi tulis (CP).
Menambah server cache memicu badai eviksiConsistent Hashing dengan virtual node untuk membatasi churn sebesar $1/N$.
Tabel transaksi melampaui 500 juta barisHash-Based Database Sharding dengan shard key berbasis ID user.
Jaringan putus saat memotong saldo penggunaClient mengirimkan Idempotency-Key, server validasi status atomik di Redis.
Microservice crash di tengah alur checkoutSaga Pattern Orchestrator memicu langkah kompensasi pembalik secara terbalik.
Serangan bot scraping membanjiri serverSliding Window Rate Limiter di API Gateway mengembalikan HTTP 429 Too Many Requests.

Langkah Selanjutnya di Part 4

Kita telah menguasai fondasi jaringan, komponen fisik skalabilitas, dan pola arsitektur sistem terdistribusi. Sekarang saatnya menguji seluruh keahlian tersebut dalam simulasi wawancara nyata.

Di bagian penutup, Part 4: Studi Kasus Interview Nyata, kita akan merancang 5 sistem produksi nyata dari nol:

  1. Merancang Sistem Pemendek URL (TinyURL)
  2. Merancang Sistem Chat Real-Time (WhatsApp)
  3. Merancang Aplikasi Pesan Antar Makanan (Zomato / Gojek)
  4. Merancang Sistem Notifikasi Skala Masif
  5. Merancang Distributed Rate Limiter

Lanjut Membaca

Previous article thumbnail

← Artikel Sebelumnya

System Design Roadmap Part 2: Core Building Blocks – Load Balancers, CDN, Caching, API Gateway, DBs & Kafka

Artikel Selanjutnya →

System Design Roadmap Part 4: Real-World Interview Systems – URL Shortener, Chat, Food Delivery & Notification

Next article thumbnail