Timeout, retry + backoff, circuit breaker
Empat alat saat dependency lambat atau gagal: kapan menyerah, kapan mengulang, kapan berhenti mencoba, dan bagaimana mengurung kerusakan supaya tidak menjalar
Sebelumnya, di lesson 0021, Anda melindungi service dari client yang terlalu rajin. Lesson ini membahas sisi sebaliknya: service Anda menjadi client bagi database, payment gateway, atau service lain, dan dependency itu sedang bermasalah.
1. Timeout: menyerah tepat waktu
Panggilan tanpa timeout bisa menunggu selamanya. Dokumentasi gRPC menyebutnya langsung: secara default gRPC tidak memasang deadline, sehingga client bisa menunggu response "effectively forever"; selalu pasang deadline yang realistis. [1] Thread dan koneksi yang menunggu tidak bisa melayani request lain, jadi satu dependency yang macet bisa membuat seluruh service macet.
Berapa angkanya? AWS memulai dari metrik latency dependency: pilih tingkat false timeout yang dapat diterima (misalnya 0,1%), lalu pakai percentile yang sesuai di dependency itu (p99.9). [2] Ini menyambung ke percentile di lesson 0003: timeout yang baik diturunkan dari angka yang diukur, bukan ditebak.
2. Retry: hanya yang aman, dengan jeda yang makin panjang
Banyak kegagalan bersifat sementara (koneksi putus, server sedang restart), jadi mencoba lagi sering berhasil. Dua syaratnya:
- Operasinya idempotent. Timeout tidak berarti efek samping belum terjadi; AWS memandang API dengan efek samping tidak aman di-retry kecuali idempotent. [2] Di HTTP,
PUT,DELETE, dan method aman sepertiGETidempotent;POSTtidak. [3] UntukPOST /payments, kirim idempotency key (lesson 0017). - Jedanya makin panjang dan acak. Exponential backoff: waktu tunggu dilipatgandakan setiap percobaan dan diberi batas atas. Jitter menambah keacakan supaya client yang gagal bersamaan tidak mengulang bersamaan lalu membebani server lagi. [2] SRE Book: "Always use randomized exponential backoff when scheduling retries." [4]
Bahaya retry berlapis. Kalau request user melewati lima layer service dan setiap layer mencoba 3 kali, beban ke database saat ia mulai gagal naik 243 kali lipat (3⁵), sehingga database hampir tidak mungkin pulih. [2] Solusinya: retry di satu layer saja, dan pasang retry budget, misalnya maksimal 60 retry per menit per proses; kalau budget habis, request langsung gagal. [4]
Latihan 1 — Timeout dan retry
Jawab tanpa scroll ke atas. Soal yang sama akan muncul lagi, diacak, di review 0026.
Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil review 0026.
3. Circuit breaker: berhenti mencoba sementara
Retry membantu kalau gangguannya singkat. Kalau dependency mati sepuluh menit, retry hanya membuang waktu dan menambah beban. Circuit breaker adalah proxy di depan panggilan yang menghitung kegagalan dan punya tiga state [5]:
- Closed: request lewat seperti biasa, kegagalan dihitung. Kalau kegagalan dalam periode tertentu melewati ambang, pindah ke Open dan timer mulai berjalan.
- Open: request langsung gagal tanpa memanggil dependency (fail fast). Service bisa mengembalikan fallback, misalnya rekomendasi default.
- Half-Open: saat timer habis, sejumlah kecil request dicoba. Berhasil → Closed dan counter di-reset; gagal → kembali Open. State ini mencegah service yang baru pulih langsung dibanjiri.
4. Bulkhead: mengurung kerusakan
Nama pola ini diambil dari sekat lambung kapal: kalau lambung bocor, hanya satu ruang yang kebanjiran dan kapal tidak tenggelam. Di software, setiap dependency diberi sumber daya sendiri, misalnya connection pool per service yang dipanggil. Kalau satu service mulai gagal, yang habis hanya pool miliknya, dan panggilan ke service lain tetap berjalan. [6] Contoh: service rekomendasi yang lambat tidak boleh menghabiskan koneksi yang dibutuhkan checkout.
Latihan 2 — Circuit breaker dan bulkhead
5. Kapan tidak perlu
Timeout selalu perlu dan hampir gratis: satu baris konfigurasi di HTTP client, driver database, atau gRPC. Retry dengan backoff juga murah kalau library atau SDK sudah menyediakannya. Yang perlu dibenarkan dengan angka adalah dua sisanya.
Sinyal yang membenarkan circuit breaker dan bulkhead: dependency yang gagal pernah menyeret endpoint lain (p99 semua endpoint naik bersamaan saat satu dependency lambat), pool koneksi atau thread habis saat insiden, atau dependency eksternal dengan riwayat outage panjang. Monolith yang hanya memanggil satu database belum butuh keduanya: kalau database mati, tidak ada yang bisa diselamatkan dengan breaker.
Biaya dan risiko: ambang dan timer circuit breaker harus disetel; terlalu sensitif membuat breaker terbuka saat gangguan kecil dan menolak request yang sebenarnya bisa berhasil. Setiap instance punya state breaker sendiri, sehingga perilakunya sulit diuji. Bulkhead mengunci kapasitas per dependency, jadi sebagian pool menganggur saat traffic normal.
Alternatif lebih murah: timeout yang ketat + retry terbatas dengan jitter + batas ukuran connection pool. Kombinasi ini sudah mencegah sebagian besar cascading failure sebelum Anda butuh breaker.
Latihan lisan — Ucapkan dengan suara keras
Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai. Soal ini juga ada di bank soal dan bisa ditarik review 0026.
"Service checkout memanggil payment gateway yang kadang lambat atau gagal. Bagaimana Anda membuat panggilan itu tahan gangguan tanpa memperparah masalah?" (60 detik)
Bandingkan dengan jawaban contoh dan checklist-nya
"Pertama timeout: tanpa timeout thread menunggu selamanya. Saya ambil dari latency payment gateway, misalnya sedikit di atas p99.9-nya. Kedua retry, tapi hanya untuk error sementara dan hanya karena request membawa idempotency key, karena POST pembayaran tidak idempotent dan timeout tidak berarti tagihan gagal. Retry pakai exponential backoff dengan jitter, misalnya 100, 200, 400 milidetik yang diacak, supaya client tidak mengulang serentak, dan dibatasi tiga kali di satu layer saja, karena retry di lima layer bisa melipatgandakan beban 243 kali. Ketiga circuit breaker: kalau error rate melewati ambang, breaker Open dan checkout langsung gagal cepat atau menawarkan 'coba lagi nanti', lalu Half-Open untuk uji. Terakhir bulkhead: pool koneksi terpisah untuk payment supaya endpoint lain tidak ikut macet. Timeout dan retry terbatas itu murah dan wajib; breaker dan bulkhead saya tambah kalau dependency ini terbukti bisa menyeret seluruh service."
Checklist: apakah Anda menyebut…
- timeout berdasarkan latency dependency (misalnya p99/p99.9)?
- retry hanya untuk operasi idempotent atau dengan idempotency key?
- exponential backoff dengan jitter supaya retry tidak serentak?
- batas retry di satu layer, karena retry berlapis saling mengalikan?
- circuit breaker: Closed, Open, Half-Open untuk gagal cepat?
- bulkhead atau pool terpisah, ditambahkan karena ada risiko yang terukur?
Lulus jika minimal 4 dari 6.
Berikutnya, lesson 0023: semua keputusan di atas butuh angka (p99.9, error rate, ambang). Dari mana angka itu datang, dan berapa banyak kegagalan yang boleh terjadi?
Istilah: GLOSSARY.md (bagian "Reliabilitas & API") · Indeks: semua lesson System Design
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "Apa beda full jitter dan equal jitter?", "Di layer mana sebaiknya retry dipasang kalau ada API gateway?"
Sumber: [1] gRPC: Deadlines · [2] AWS Builders' Library: Timeouts, retries, and backoff with jitter · [3] RFC 9110 HTTP Semantics, §9.2.2 Idempotent Methods · [4] Google SRE Book, ch. 22: Addressing Cascading Failures · [5] Azure Architecture Center: Circuit Breaker pattern · [6] Azure Architecture Center: Bulkhead pattern