Merancang sistem yang tetap berdiri di tengah kegagalan: timeout budget, retry dengan backoff dan jitter, circuit breaker, bulkhead, graceful degradation, error budget SLO, hingga chaos readiness — semua dituning untuk janji availability 99,95% jalur checkout studi kasus kita

Setelah di episode 15 kalian memahami evolusi modular monolith ke services beserta roadmap bertahapnya, pada episode ini kita garap dimensi yang menjadi alasan utama janji bisnis kalian — resilience & reliability.
Quality scenario episode 5 menjanjikan availability ≥ 99,95% di jalur checkout. Angka itu bukan harapan; ia properti yang dirancang. Di sistem terdistribusi (dan bahkan monolith dengan dependensi eksternal), kegagalan adalah kondisi operasi normal — jaringan putus, database lambat, gateway timeout. Pertanyaan architect bukan "bagaimana agar tidak pernah gagal", melainkan "ketika gagal — karena pasti — sistem berperilaku bagaimana?"
Semua pola resilience lain tidak berguna tanpa timeout eksplisit. Panggilan tanpa timeout menunggu selamanya default OS (bisa 2+ menit) — satu dependensi lambat cukup menghabiskan seluruh thread pool aplikasi.
Prinsip menyusun timeout budget dari atas ke bawah:
User-facing SLA : p99 ≤ 800 ms
├── API server total : ≤ 700 ms
│ ├── inventory reserve : 150 ms
│ ├── payment gateway : 300 ms ← paling lambat, wajar
│ └── order persist : 100 ms
└── sisanya : buffer + serialisasiAturan mainnya: timeout anak selalu lebih kecil dari budget induknya, dan setiap hop harus tahu budget sisa yang diteruskan (propagate deadline). Tanpa ini, komponen terdalam berperilaku seolah punya seluruh waktu di dunia padahal pemanggil sudah menyerah sejak lama.
Retry menyembuhkan failure transien — tetapi retry buta pada overload adalah akselerator kegagalan: sistem yang kesulitan justru menerima trafik berlipat.
Tiga aturan retry yang aman:
charge() tanpa idempotency key (episode 9) menciptakan double charge. Non-idempotent = jangan diretry otomatis.async function retry<T>(
op: () => Promise<T>,
attempts = 3,
): Promise<T> {
for (let i = 0; i < attempts; i++) {
try {
return await op()
} catch (err) {
if (i === attempts - 1 || !isRetryable(err)) throw err
const base = Math.min(100 * 2 ** i, 2000)
const delay = Math.random() * base // full jitter
await sleep(delay)
}
}
throw new Error("unreachable")
}Dan satu pengecualian penting: saat penerima merespons 429 atau breaker terbuka, hentikan retry — mereka sedang berkata jelas bahwa menunggu lebih baik daripada mendesak.
Breaker adalah sekring untuk panggilan jaringan — mencegah aplikasi menghabiskan resource menunggu dependensi yang sudah mati:
CLOSED : normal, semua request lewat
└─ error rate > threshold (misal 50% dalam 10s)
OPEN : request langsung ditolak cepat (fail fast)
└─ tunggu open window (30s)
HALF-OPEN : izinkan beberapa request percobaan
├─ sukses → CLOSED
└─ gagal → OPEN lagiImplementasi modern (Resilience4j, Polly, opossum) menawarkan metric-based breaker — putus berdasarkan rasio error atau latency persentil, bukan sekadar hitungan. Untuk studi kasus kita, breaker dipasang di tiga tempat: payment gateway, shipping provider API, dan email service — semuanya dependensi eksternal yang kualitasnya di luar kendali kita.
Perhatikan pasangan wajibnya: breaker tanpa fallback hanya mengubah jenis kegagalan (dari lambat menjadi error). Rencanakan jawaban cadangan per jalur: produk rekomendasi gagal → sembunyikan section; stok check gagal → tolak order dengan pesan jujur (konsisten scenario degradation episode 5).
Bulkhead meniru sekat kapal: kebocoran satu ruang tidak menenggelamkan kapal. Implementasi praktisnya isolasi resource per dependensi:
App instance
├── pool DB ordering : 40 conn ← inti transaksi
├── pool DB analytics : 10 conn ← query berat tak boleh
│ menyita pool utama
├── worker threads payment : dedicated queue
└── worker threads email : dedicated queue (boleh antre)Tanpa sekat, satu laporan analitik berat pukul 20.00 bisa mengunci connection pool yang dibutuhkan checkout tepat saat flash sale.
Graceful degradation menyusun prioritas fitur secara eksplisit — apa yang mati duluan ketika kapasitas kurang:
| Prioritas | Fitur | Saat Overload |
|---|---|---|
| Wajib hidup | Checkout, pembayaran, cek stok | Lindungi dengan segala cara |
| Degraded | Rekomendasi, review, wishlist | Matikan/cache statis |
| Opsional | Analytics real-time, badge sosial | Drop tanpa ragu |
Keputusan tabel ini sebaiknya disepakati bersama product owner — ia adalah kebijakan bisnis yang dieksekusi teknis, dan load shedder di edge akan membacanya sebagai konfigurasi.
Important
Resilience pattern yang belum pernah diuji hanyalah dekorasi. Setiap breaker, fallback, dan load shedding wajib memiliki test yang benar-benar memicunya di staging — kalau tim baru mengetahui perilaku fallback saat insiden produksi pertama, kalian sedang belajar dengan biaya pelanggan.
Reliability juga disiplin organisasi:
SLO 99,95% berarti boleh gagal 21,6 menit/bulan — itulah error budget:
Burn rate rendah (<25%/jam) → rilis normal jalan terus
Burn tinggi (>200%/jam) → freeze rilis non-kritis,
fokus mitigasi
Budget habis → reliability work saja,
fitur baru menungguIni menerjemahkan janji availability menjadi keputusan rilis harian yang objektif — debat "aman nggak ya deploy" berganti menjadi pembacaan angka.
Chaos engineering memverifikasi resilience lewat eksperimen terkendali: matikan instance acak di staging, tambahkan latensi 500 ms ke payment gateway, penuhi disk replica. Metodologinya: rumuskan hipotesis steady-state ("checkout p99 tetap < 800 ms meski gateway lambat"), suntikkan gangguan, amati, perbaiki gap, ulangi.
Mulai dari staging dengan blast radius kecil, otomatisasi lewat tool seperti Litmus/Chaos Mesh, dan jangan sentuh produksi sebelum muscle memory on-call terbentuk.
Mari rangkum desain lengkap jalur kritis kita:
jalur: POST /orders (flash sale)
budget:
total_p99: 800ms
hops:
inventory.reserve: {timeout: 150ms, retries: 1}
payment.charge:
timeout: 300ms
retries: 2 # idempotency key wajib
backoff: exponential+jitter
circuit_breaker:
failure_rate: 50% / window 10s
open_duration: 30s
half_open_probe: 3 req
shipping.quote: {timeout: 200ms, fallback: tarif_flat}
degradasi:
trigger: cpu > 70% atau queue_depth > threshold
urutan_mati: [analytics_rt, recommendation, review]
proteksi: [checkout, payment, stock]
load_shedding:
edge: tolak > kapasitas dengan Retry-After
prioritas: user aktif checkout > browsing
chaos_schedule: staging mingguan; produksi kuartalan
(blast radius kecil, approval on-call)
error_budget:
slo: 99.95% jalur checkout
kebijakan: burn>200%/jam -> freeze rilis non-kritisSetiap baris dokumen ini jejaknya bisa diuji: breaker punya test pemicu, degradasi punya drill, budget punya dashboard burn rate.
Inti yang harus dibawa pulang:
Di episode 17 selanjutnya kita bahas dimensi yang jarang masuk diagram arsitektur tapi selalu masuk rapat direksi: cost & efficiency architecture — FinOps, unit economics, cost-driven design, dan model biaya konkret infrastruktur studi kasus kita. Sampai jumpa!