Circuit Breaker Menghentikan Panggilan Berulang ke Dependency yang Gagal

Dependency yang sudah gagal dapat menghabiskan kapasitas caller lebih besar daripada dependency yang sehat. Request menunggu timeout, retry menambah traffic, connection pool tetap terisi, dan slot worker tertahan oleh pekerjaan yang kecil kemungkinannya selesai. Circuit breaker menempatkan gate yang memiliki state di depan dependency agar caller dapat berhenti mengirim panggilan setelah bukti kegagalan mencapai batas yang dikonfigurasi.

Gate ini bukan pengganti timeout, retry, atau capacity limit. Fungsinya mengoordinasikan panggilan berulang dalam aliran request. Ketika dependency terlihat tidak sehat, breaker menggagalkan panggilan baru secara lokal selama suatu periode, alih-alih terus meminta sistem remote membuktikan kegagalan yang sama.

Closed state mencatat hasil panggilan

Breaker biasanya dimulai dalam state closed. Panggilan diteruskan ke dependency dan hasilnya masuk ke failure policy.

request
   |
   v
[ closed breaker ]
   |
   v
dependency
   |
   +-- success ------> record success
   `-- counted error -> record failure

Policy memerlukan definisi failure yang eksplisit. Connection refusal, deadline expiration, atau HTTP 503 dapat dihitung. Validation error akibat input caller yang malformed biasanya tidak seharusnya dihitung. Menganggap setiap hasil non-success sebagai kegagalan dependency dapat membuka circuit akibat masalah yang tidak dapat diperbaiki dependency.

Raw count kadang cukup, tetapi policy produksi sering memakai rolling window atau minimum sample count. Sebagai contoh, membuka circuit pada 50% failure terlalu sensitif ketika window hanya berisi dua panggilan. Minimum volume mencegah sampel kecil menghasilkan reaksi yang terlalu besar.

Breaker juga perlu membedakan slow call dari failed call jika latency menjadi bagian policy. Slow-call threshold berguna ketika request selesai tetapi menahan resource caller cukup lama hingga mengancam kapasitas. Threshold tersebut tetap perlu memiliki hubungan yang jelas dengan request deadline.

Open state menolak panggilan secara lokal

Ketika kondisi pembukaan terpenuhi, breaker berpindah ke open. Panggilan baru yang berada dalam cakupan breaker ditolak sebelum mencapai dependency.

client call
    |
    v
[ open breaker ] --x--> dependency
    |
    `--> local failure

Penolakan lokal bernilai karena berlangsung cepat dan memakai lebih sedikit kapasitas downstream. Dependency juga memperoleh ruang untuk pulih tanpa menerima aliran panggilan yang kemungkinan besar akan gagal.

Hasil lokal harus dapat dibedakan dari response aplikasi remote. Breaker-open error berarti panggilan tidak pernah dicoba. Mencatatnya seolah-olah dependency mengembalikan error akan mengacaukan remote error rate dan menyulitkan analisis insiden.

Caller juga memerlukan policy yang sengaja ditetapkan untuk local failure. Sebagian request dapat memakai cached data, degraded response, replica lain, atau jalur asynchronous. Request lain memang harus gagal. Breaker menyediakan sinyal; breaker tidak menentukan business fallback.

Cooldown saja tidak membuktikan pemulihan

Breaker yang open biasanya tetap berada dalam state tersebut selama interval yang dikonfigurasi. Berakhirnya interval tidak membuktikan dependency sudah sehat. Itu hanya menandai waktu ketika pengujian dapat dimulai kembali.

Berpindah langsung dari open ke full traffic dapat memicu recovery surge. Jika ribuan caller memiliki pekerjaan tertunda atau segera melakukan retry, dependency yang baru pulih dapat terkena beban normal sebelum menunjukkan kapasitas yang memadai.

Half-open state membatasi transisi tersebut.

open
  |
  | cooldown expires
  v
half-open
  |
  +-- admit a small probe set
  |
  +-- probe policy passes --> closed
  `-- probe policy fails  --> open

Hanya sejumlah probe call yang dibatasi yang boleh lewat ketika breaker half-open. Panggilan lain ditolak atau ditangani fallback sesuai policy caller. Probe yang sukses memberi bukti untuk kembali ke closed; probe yang gagal mengembalikan breaker ke open.

Probe concurrency mengubah tekanan saat pemulihan

Satu probe sederhana, tetapi pemulihan menjadi sensitif terhadap satu request yang kebetulan gagal. Banyak probe memberi sampel lebih luas, tetapi dapat memberi beban berarti pada dependency yang baru kembali dapat dijangkau.

Karena itu, probe limit merupakan bagian dari breaker policy, bukan efek samping jumlah thread. Sebuah service dapat mengizinkan beberapa probe dan mensyaratkan success ratio tertentu sebelum kembali closed. Service lain dapat memakai satu probe untuk dependency yang mahal.

Probe call tetap harus memakai deadline normal. Half-open probe tanpa deadline terbatas dapat membuat breaker tertahan ketika operasi remote menggantung.

Policy juga perlu menetapkan perilaku concurrent state transition. Jika beberapa request melihat cooldown berakhir pada saat yang sama, semuanya tidak boleh berubah menjadi probe tanpa batas secara independen. Implementasi biasanya mengoordinasikan admission agar half-open limit yang dikonfigurasi tetap berlaku.

Scope breaker menentukan blast radius

Breaker membutuhkan key. Satu breaker global untuk seluruh remote service mudah dioperasikan, tetapi dapat menolak traffic sehat ketika hanya satu shard, tenant, endpoint, atau replica yang gagal.

Di sisi lain, breaker per request key dapat menghasilkan state cardinality sangat besar dan traffic per breaker yang terlalu sedikit untuk membentuk sinyal berguna.

Scope yang tepat biasanya mengikuti failure domain. Contohnya:

  • satu breaker per upstream service ketika kegagalan mencakup seluruh service;
  • satu per region atau cluster ketika routing domain dapat gagal secara independen;
  • satu per endpoint ketika operasi memiliki karakteristik kegagalan yang sangat berbeda;
  • satu per replica ketika caller dapat mengalihkan traffic dari instance yang tidak sehat.

Scope metric sebaiknya sama dengan scope keputusan. Dashboard service-wide dapat menyembunyikan satu regional breaker yang open jika seluruh region digabung menjadi rata-rata yang terlihat sehat.

Retry dan breaker memerlukan satu batas policy yang jelas

Retry dapat menambah bukti yang dilihat breaker. Jika satu logical request menjalankan tiga attempt yang semuanya gagal, menghitung ketiganya sebagai demand independen dapat membuka breaker lebih cepat daripada hanya menghitung hasil akhir logical request.

Tidak ada satu model yang selalu tepat. Pilihannya bergantung pada hal yang dilindungi breaker.

Breaker di sekitar setiap physical attempt melindungi dependency dari traffic attempt berulang. Breaker di luar retry loop melihat hasil logical request, tetapi retry mechanism masih dapat terus mengirim attempt sampai breaker luar terbuka.

breaker outside retry:
breaker -> retry loop -> dependency

breaker around attempts:
retry loop -> breaker -> dependency

Penempatannya harus disengaja. Retry budget, backoff, dan breaker threshold perlu dievaluasi bersama karena masing-masing mengubah traffic yang diterima mekanisme lain.

Breaker yang open juga tidak seharusnya memicu retry agresif. Melakukan retry langsung terhadap local rejection dapat mengubah penolakan murah menjadi busy work di sisi caller dan memicu burst saat circuit kembali closed.

Timeout tetap membatasi setiap panggilan

Breaker bereaksi terhadap rangkaian hasil. Breaker tidak memberi batas waktu pada panggilan yang sudah diizinkan lewat.

Setiap remote attempt tetap memerlukan deadline atau timeout yang sesuai dengan operasinya. Tanpa batas tersebut, dependency yang menerima connection tetapi tidak pernah menyelesaikan request dapat menahan resource caller sementara breaker memperoleh terlalu sedikit bukti kegagalan untuk segera terbuka.

Timeout dan breaker bekerja pada lapisan berbeda:

timeout         -> bounds one admitted attempt
retry policy    -> controls additional attempts
circuit breaker -> gates a stream of attempts
capacity limit  -> bounds concurrent resource use

Menggabungkannya lebih kokoh daripada meminta satu mekanisme meniru keempat peran tersebut.

Metric perlu menampilkan state dan pekerjaan yang ditolak

Breaker dapat menurunkan remote error sekaligus meningkatkan local rejection, sehingga dependency error rate saja tidak cukup untuk mengevaluasinya.

Telemetry yang berguna mencakup:

  • state breaker saat ini dan durasi di setiap state;
  • transisi closed ke open, open ke half-open, dan half-open ke closed;
  • panggilan yang diizinkan, ditolak lokal, dan ditangani fallback;
  • counted failure berdasarkan kategori;
  • half-open probe yang diizinkan, sukses, gagal, dan timeout;
  • rolling sample size serta threshold yang dipakai untuk keputusan.

State transition layak memiliki event record karena interval open yang singkat dapat hilang dalam agregasi metric yang kasar. Pada saat yang sama, transition log memerlukan rate control jika banyak breaker key dapat berubah state bersamaan.

Dashboard sebaiknya memisahkan remote failure dari breaker rejection. Selama outage, volume remote request dapat turun tajam setelah breaker terbuka. Penurunan tersebut merupakan proteksi yang diharapkan, bukan bukti bahwa dependency telah pulih.

Pemulihan memerlukan hysteresis

Membuka dan menutup breaker berdasarkan instantaneous threshold yang sama dapat membuat state berosilasi di sekitar batas. Rolling window, cooldown, half-open probe yang dibatasi, dan close criteria yang terpisah menyediakan hysteresis.

Hysteresis penting secara operasional. Breaker perlu bereaksi cukup cepat terhadap kegagalan persisten tanpa berganti state pada setiap fluktuasi singkat. Nilai tepatnya bergantung pada request volume, biaya dependency, sasaran latency, dan durasi transient fault yang diperkirakan.

Circuit breaker paling berguna ketika failure classification, scope, timing, probe admission, dan interaksinya dengan retry ditetapkan secara eksplisit. Kontribusi utamanya bukan mendeteksi satu panggilan gagal. Breaker mengubah bukti kegagalan berulang menjadi penolakan lokal sementara, lalu memulihkan traffic melalui jalur recovery yang terkendali.