Kodeloka
  1. Kodeloka
  2. Kelas Microservices
  3. Materi

Keandalan ยท Modul 17 dari 24

Resilience Pattern - Circuit Breaker, Retry, Timeout & Bulkhead

Mencegah satu layanan yang lambat menyeret seluruh sistem: timeout wajib, retry yang tidak memperparah keadaan, circuit breaker, dan isolasi sumber daya.

1. Kenapa Monolith Tidak Butuh Ini, Microservices Butuh

Di monolith, memanggil fungsi lain adalah pemanggilan di dalam memori: selalu berhasil, selalu instan. Di microservices, setiap panggilan melewati jaringan yang bisa lambat, putus, atau menjawab separuh jalan.

๐Ÿšซ Kegagalan berantai (cascading failure)

Layanan Pembayaran melambat menjadi 30 detik per permintaan. Layanan Pesanan yang memanggilnya ikut menunggu, thread pool-nya habis. Layanan Keranjang yang memanggil Pesanan ikut menunggu, thread-nya juga habis. Dalam hitungan menit, seluruh sistem mati padahal hanya satu layanan yang bermasalah. Inilah pola kegagalan paling umum dan paling merusak di arsitektur microservices.

2. Timeout โ€” Pertahanan Paling Dasar

โš ๏ธ Klien HTTP bawaan sering tidak punya timeout

Banyak pustaka HTTP secara default menunggu tanpa batas. Satu dependensi yang menggantung berarti thread Anda menggantung selamanya. Setiap panggilan keluar wajib punya timeout eksplisit โ€” tanpa kecuali.

Anggaran waktu berjenjang
Klien browser        : 10 detik
  โ”” API Gateway      : 9 detik
      โ”” Layanan Pesanan  : 6 detik
          โ”” Layanan Stok     : 2 detik
          โ”” Layanan Harga    : 2 detik

Aturannya: timeout di hulu HARUS lebih besar daripada
total timeout seluruh panggilan di hilirnya.
๐Ÿ“Œ Sebarkan tenggat, bukan hanya timeout

Yang lebih baik daripada timeout tetap adalah deadline propagation: kirimkan sisa waktu yang tersedia bersama permintaan. Bila layanan hulu sudah menunggu 5 detik dari anggaran 6 detik, layanan hilir tahu ia hanya punya 1 detik dan bisa langsung menyerah alih-alih mengerjakan pekerjaan yang hasilnya pasti dibuang. Ini bawaan di gRPC lewat context.WithTimeout, dan bisa ditiru di HTTP lewat header seperti X-Request-Deadline.

3. Retry โ€” dan Cara Melakukannya Tanpa Merusak

Retry naif yang MEMPERPARAH keadaan
for (let i = 0; i < 3; i++) {
  try { return await panggilLayanan(); }
  catch (e) { /* langsung coba lagi */ }
}
// 1.000 klien gagal -> 3.000 permintaan menghantam
// layanan yang SEDANG kepayahan. Ini disebut retry storm.
Exponential backoff + jitter
async function panggilDenganRetry(fn, maksPercobaan = 3) {
  let jedaDasar = 100;   // ms

  for (let percobaan = 0; percobaan < maksPercobaan; percobaan++) {
    try {
      return await fn();
    } catch (err) {
      if (!bolehDiulang(err) || percobaan === maksPercobaan - 1) throw err;

      // Backoff eksponensial: 100ms, 200ms, 400ms, ...
      const jeda = jedaDasar * Math.pow(2, percobaan);
      // Jitter: acak PENUH agar klien tidak serempak
      const jedaAcak = Math.random() * jeda;

      await tidur(jedaAcak);
    }
  }
}

function bolehDiulang(err) {
  // JANGAN ulangi 4xx -- permintaannya memang salah,
  // diulang seribu kali pun tetap salah.
  if (err.status >= 400 && err.status < 500 && err.status !== 429) return false;
  return true;   // 5xx, timeout, kegagalan jaringan
}
๐Ÿšซ Dua aturan retry yang mutlak

1. Hanya ulangi operasi yang idempoten. Mengulang GET aman. Mengulang "buat pesanan" bisa menghasilkan pesanan ganda kecuali Anda memakai kunci idempotensi.

2. Jangan retry berlapis. Bila gateway mengulang 3 kali, dan tiap layanan di bawahnya juga mengulang 3 kali, satu permintaan pengguna bisa berubah menjadi 27 permintaan ke layanan terdalam. Tetapkan retry di satu lapisan saja, biasanya yang paling dekat dengan dependensi.

4. Circuit Breaker

Bila sebuah layanan jelas-jelas sedang mati, terus-menerus memanggilnya hanya membuang waktu dan menghabiskan thread. Circuit breaker berhenti mencoba untuk sementara.

          gagal melewati ambang
  CLOSED โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€> OPEN
    โ†‘                              โ”‚
    โ”‚ percobaan berhasil           โ”‚ setelah jeda pemulihan
    โ”‚                              โ†“
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ HALF-OPEN
              percobaan gagal โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ (kembali ke OPEN)
KeadaanPerilaku
CLOSEDNormal. Semua panggilan diteruskan, kegagalan dihitung.
OPENSemua panggilan langsung ditolak tanpa menyentuh jaringan. Cepat gagal, sumber daya terjaga.
HALF-OPENBeberapa panggilan percobaan diizinkan lewat untuk menguji apakah layanan sudah pulih.
Parameter yang perlu disetel
ambangKegagalan       : 50%      (persentase, bukan jumlah absolut)
jumlahPanggilanMinimum: 20       (jangan buka circuit karena 1 dari 2 gagal)
jendelaPengamatan     : 60 detik
jedaSebelumHalfOpen   : 30 detik
panggilanPercobaan    : 3
๐Ÿ“Œ Circuit breaker tanpa fallback hanya memindahkan masalah

Menolak permintaan dengan cepat memang melindungi sistem Anda, tetapi pengguna tetap melihat error. Pasangkan selalu dengan fallback yang bermakna: tampilkan data dari cache meski agak basi, tampilkan rekomendasi umum alih-alih personal, atau terima pesanan lalu proses pembayarannya secara asinkron. Degradasi anggun jauh lebih baik daripada halaman error.

5. Bulkhead โ€” Isolasi Sumber Daya

Nama ini diambil dari sekat kedap air di lambung kapal: satu kompartemen bocor tidak menenggelamkan seluruh kapal.

TANPA bulkhead -- satu kolam untuk semua:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Thread pool: 100               โ”‚
โ”‚  Layanan Rekomendasi melambat   โ”‚
โ”‚  -> 100 thread habis menunggu   โ”‚
โ”‚  -> Checkout ikut mati          โ”‚  โŒ padahal checkout tak terkait
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

DENGAN bulkhead -- kolam terpisah:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Pembayaran   โ”‚ Rekomendasi  โ”‚ Pencarian    โ”‚
โ”‚ 40 thread    โ”‚ 20 thread    โ”‚ 40 thread    โ”‚
โ”‚ โœ… sehat      โ”‚ โŒ habis      โ”‚ โœ… sehat      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Fitur rekomendasi mati, checkout tetap jalan.
๐Ÿ’ก Bulkhead juga berlaku untuk koneksi database

Bila laporan analitik dan transaksi checkout memakai kolam koneksi yang sama, satu kueri laporan yang berat bisa menghabiskan seluruh koneksi dan melumpuhkan checkout. Pisahkan kolam koneksinya โ€” atau lebih baik lagi, pisahkan ke replika baca tersendiri.

6. Rate Limiting & Load Shedding

# Rate limiting: membatasi berapa banyak yang boleh masuk
#   - Token bucket  : mengizinkan lonjakan sesaat, cocok untuk API publik
#   - Sliding window: lebih ketat dan akurat
#   - Balas 429 Too Many Requests + header Retry-After

# Load shedding: membuang permintaan saat sistem KEPAYAHAN
if (panjangAntrian > ambangKritis) {
  return balas503("Sedang sibuk, coba beberapa saat lagi");
}
๐Ÿ’ก Melayani 70% pengguna lebih baik daripada gagal untuk 100%

Load shedding terasa kejam, tetapi sistem yang menerima semua permintaan saat kelebihan beban akan melambat untuk semua orang sampai akhirnya mati total. Menolak sebagian permintaan lebih awal menjaga sisanya tetap terlayani dengan latensi normal.

7. Menguji Ketahanan: Chaos Engineering

Pola-pola di atas hanya berguna bila benar-benar berfungsi saat dibutuhkan. Satu-satunya cara memastikannya adalah sengaja membuat kegagalan.

  • Suntikkan latensi โ€” tambahkan 5 detik pada satu dependensi, lihat apakah timeout Anda bekerja.
  • Matikan satu instance โ€” apakah load balancer mengalihkan trafik dengan benar?
  • Putuskan jaringan antara dua layanan โ€” apakah circuit breaker terbuka?
  • Habiskan CPU atau memori pada satu node โ€” apakah probe kesehatan mendeteksinya?
๐Ÿ“Œ Mulai dari lingkungan staging, dengan hipotesis

Chaos engineering bukan merusak secara acak. Tuliskan dulu hipotesisnya: "bila Layanan Rekomendasi mati, halaman produk tetap tampil dalam 2 detik tanpa bagian rekomendasi". Jalankan eksperimennya. Bila hipotesis salah, Anda baru saja menemukan bug produksi sebelum pengguna menemukannya.