Menghadapi "Retry Storms": Mengapa Retry Saja Tidak Cukup dalam Sistem Terdistribusi
Pernahkah Anda mengalami situasi di mana satu servis kecil mengalami sedikit perlambatan, namun tiba-tiba seluruh infrastruktur tumbang seperti kartu domino? Jika Anda bekerja dengan sistem terdistribusi, kemungkinan besar Anda baru saja menyaksikan apa yang disebut sebagai Retry Storm.
Secara intuitif, ketika sebuah request gagal, hal pertama yang kita lakukan adalah mencoba lagi (retry). Logikanya sederhana: mungkin itu hanya gangguan jaringan sesaat atau glitch kecil. Namun, dalam skala besar, perilaku intuitif ini bisa menjadi senjata makan tuan.
Anatomi Sebuah Bencana
Bayangkan sebuah skenario sederhana. Anda memiliki servis API yang berkomunikasi dengan database PostgreSQL. Tiba-tiba, database mengalami lonjakan beban yang membuat response time meningkat dari 100ms menjadi 2 detik.
Jika klien (baik itu frontend React atau servis backend lain) dikonfigurasi untuk melakukan retry segera setelah timeout (misalnya timeout 1 detik), maka terjadi hal berikut:
- Request pertama timeout.
- Klien mengirim request kedua (retry) sementara request pertama mungkin masih diproses oleh database.
- Beban database meningkat dua kali lipat.
- Response time semakin lambat, memicu lebih banyak timeout.
- Lebih banyak retry dikirimkan.
Dalam hitungan detik, sistem Anda tidak lagi berjuang melawan beban asli, melainkan berjuang melawan gelombang request retry yang diciptakan oleh sistem Anda sendiri. Inilah yang kita sebut Retry Storm.
Solusi yang Lebih Cerdas dari Sekadar "Try Again"
Untuk menghindari skenario mengerikan ini, kita perlu menerapkan beberapa strategi mitigasi yang lebih terukur.
1. Exponential Backoff
Jangan melakukan retry dengan interval yang tetap. Jika retry pertama gagal setelah 1 detik, tunggu 2 detik untuk retry kedua, 4 detik untuk ketiga, dan seterusnya. Ini memberikan ruang bagi sistem yang sedang tertekan untuk "bernapas" dan memulihkan diri.
2. Menambahkan Jitter (Randomness)
Exponential backoff saja tidak cukup. Jika 1.000 klien mengalami timeout pada saat yang bersamaan, mereka semua akan melakukan retry pada interval yang sama (misalnya tepat di detik ke-2, ke-4, dst). Ini menciptakan lonjakan beban yang teratur (spiky traffic).
Solusinya adalah Jitter. Tambahkan angka acak kecil pada interval backoff Anda. Alih-alih tepat 2 detik, buatlah menjadi antara 1.8 hingga 2.2 detik. Ini akan menyebarkan beban request secara lebih merata.
3. Circuit Breaker Pattern
Ada titik di mana melakukan retry justru memperburuk keadaan. Di sinilah Circuit Breaker berperan. Jika tingkat kegagalan sebuah servis mencapai ambang batas tertentu, "sirkuit" akan terbuka (open), dan semua request selanjutnya akan langsung gagal tanpa mencoba menghubungi servis tujuan.
Setelah jangka waktu tertentu, sirkuit akan masuk ke mode half-open untuk menguji apakah servis sudah pulih. Jika berhasil, sirkuit tertutup kembali dan trafik normal dialirkan.
4. Implementasi di Sisi Backend (Golang & PostgreSQL)
Dalam implementasi menggunakan Golang, penggunaan context.WithTimeout sangatlah krusial. Pastikan timeout di sisi klien sedikit lebih panjang daripada timeout di sisi server untuk menghindari ghost requests yang terus berjalan di backend meskipun klien sudah menyerah.
Di sisi PostgreSQL, penggunaan connection pooling (seperti PgBouncer) dapat membantu mencegah database tumbang akibat terlalu banyak koneksi yang terbuka selama terjadi retry storm.
Kesimpulan
Retry adalah mekanisme yang penting untuk mencapai reliability, tetapi tanpa strategi yang tepat, ia bisa menjadi penyebab utama outage skala besar. Kunci utamanya adalah tidak membanjiri sistem yang sudah sakit dengan lebih banyak permintaan.
Apakah Anda pernah mengalami kejadian serupa di produksi? Atau mungkin Anda punya strategi lain untuk menangani kegagalan servis yang lebih efektif? Mari berdiskusi di kolom komentar.
Top comments (0)