Belajar Software Architect - Resilience & Reliability Design
Episode 16 of 28

Belajar Software Architect - Resilience & Reliability Design

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

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

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?"

Timeout: Fondasi yang Paling Sering Dilupakan

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:

Timeout budget checkout
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 + serialisasi

Aturan 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: Bermanfaat dan Berbahaya Sekaligus

Retry menyembuhkan failure transien — tetapi retry buta pada overload adalah akselerator kegagalan: sistem yang kesulitan justru menerima trafik berlipat.

Tiga aturan retry yang aman:

  1. Hanya idempotent operation — retry charge() tanpa idempotency key (episode 9) menciptakan double charge. Non-idempotent = jangan diretry otomatis.
  2. Exponential backoff + jitter — jeda naik ganda (100 ms → 200 → 400) dengan pengacakan supaya ribuan client tidak menyerbu serempak:
Backoff dengan full jitter
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")
}
  1. Batasi jumlah & lingkup — dua-tiga attempt maksimal di jalur user-facing; lebih dari itu, latensi gabungan meledak melewati budget.

Dan satu pengecualian penting: saat penerima merespons 429 atau breaker terbuka, hentikan retry — mereka sedang berkata jelas bahwa menunggu lebih baik daripada mendesak.

Circuit Breaker

Breaker adalah sekring untuk panggilan jaringan — mencegah aplikasi menghabiskan resource menunggu dependensi yang sudah mati:

Tiga state circuit breaker
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 lagi

Implementasi 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 & Graceful Degradation

Bulkhead meniru sekat kapal: kebocoran satu ruang tidak menenggelamkan kapal. Implementasi praktisnya isolasi resource per dependensi:

Isolasi pool 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:

PrioritasFiturSaat Overload
Wajib hidupCheckout, pembayaran, cek stokLindungi dengan segala cara
DegradedRekomendasi, review, wishlistMatikan/cache statis
OpsionalAnalytics real-time, badge sosialDrop 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.

Error Budget & Chaos Readiness

Reliability juga disiplin organisasi:

Error Budget

SLO 99,95% berarti boleh gagal 21,6 menit/bulan — itulah error budget:

Kebijakan 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 menunggu

Ini menerjemahkan janji availability menjadi keputusan rilis harian yang objektif — debat "aman nggak ya deploy" berganti menjadi pembacaan angka.

Chaos Readiness

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.

Praktik: Resilience Profile Checkout Studi Kasus

Mari rangkum desain lengkap jalur kritis kita:

case-studies/ecommerce/resilience-checkout.yaml
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-kritis

Setiap baris dokumen ini jejaknya bisa diuji: breaker punya test pemicu, degradasi punya drill, budget punya dashboard burn rate.

Kesalahan Umum

  • Retry tanpa jitter — sinkronisasi massal client justru menjadwalkan DDoS terhadap sistem sendiri.
  • Retry di banyak layer bertumpuk — tiga layer masing-masing 3 attempt = 27 panggilan efektif; kalikan retry hanya di satu titik.
  • Timeout default diam-diam — library HTTP tanpa timeout adalah bom waktu; standarkan di shared client.
  • Breaker tanpa fallback plan — fail fast tanpa jawaban cadangan hanya memindahkan pain.
  • SLO tanpa konsekuensi — angka cantik di slide; tanpa kebijakan freeze, budget hanyalah statistik.
  • Chaos langsung di produksi hari pertama — tanpa observability dan runbook, eksperimen berubah menjadi insiden sungguhan.

Penutup

Inti yang harus dibawa pulang:

  • Timeout budget turun-menurun adalah fondasi; tanpa itu semua pola lain rapuh.
  • Retry: idempotent-only, backoff + jitter, terbatas jumlah dan lingkupnya.
  • Circuit breaker selalu berpasangan dengan fallback; metric-based lebih cerdas dari hitungan sederhana.
  • Bulkhead mengisolasi resource per dependensi; graceful degradation menyepakati urutan mati bersama bisnis.
  • Error budget menerjemahkan SLO menjadi kebijakan rilis; chaos readiness menguji semua janji ini sebelum produksi melakukannya gratis.
  • Studi kasus kini punya resilience profile checkout yang lengkap dan teruji di staging.

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!

Belajar Software Architect - Resilience & Reliability Design | Belajar Software Architect