69. Studi Kasus: Retry dan Backoff Strategy
Sebagai engineer backend atau software engineer secara umum, kita tidak bisa lepas dari interaksi dengan layanan external: API payment gateway, database, queue service, hingga third-party API yang tidak kita kontrol. Salah satu tantangan yang sering ditemui adalah flaky service, alias remote service yang kadang gagal merespon, lambat, atau gagal total dalam waktu singkat. Di sinilah strategi retry dan backoff menjadi sangat penting.
Artikel ini akan membahas konsep retry dan backoff dengan studi kasus, lengkap dengan contoh kode, simulasi, serta alur dan analisis performa.
Masalah: Remote Service yang Flaky
Misalkan kita memiliki suatu service sederhana: aplikasi e-wallet harus memvalidasi voucher dengan Third-Party Voucher Service setiap kali user melakukan redeem.
Namun, Voucher Service sering timeout atau mengembalikan error 500 Internal Server Error secara random.
Kalau sistem kita selalu gagal begitu saja ketika voucher service error, user experience jelas buruk. Tetapi jika kita retry secara naif tanpa strategi, bisa lebih parah: load melonjak dan service down karena thundering herd problem.
Retry Simpel: Hanya Sekali Lagi
Pertama-tama, mari kita mulai dengan strategi paling simple: mencoba kembali request yang gagal maksimum satu kali.
Contoh Kode (Pseudo-Python)
import requests
def validate_voucher(voucher_code):
url = f"https://voucher.example.com/voucher/{voucher_code}/validate"
for attempt in range(2): # Maksimum 2x percobaan (1x retry)
try:
response = requests.get(url, timeout=2) # timeout 2 detik
response.raise_for_status()
return response.json()['valid']
except (requests.HTTPError, requests.Timeout) as e:
print(f"Attempt {attempt+1} failed: {str(e)}")
if attempt == 1:
raise
# Langsung retry dalam loop
Kode di atas works, tapi ada banyak pertanyaan penting:
- Apakah lebih baik melakukan lebih banyak retry?
- Seberapa cepat harus melakukan retry? Langsung atau tunggu sejenak?
- Bagaimana kalau semua instance di cluster melakukan retry bersamaan?
Retry Berlebihan Bisa Berbahaya
Bayangkan jika kita melakukan 5 retry on the spot begitu gagal. Jika root cause error adalah remote service overload, retry masal justru akan memperparah keadaan. Ini dikenal dengan retry storm atau thundering herd.
Solusi naif ini bisa digambarkan dengan diagram berikut:
sequenceDiagram
participant User
participant Service
participant VoucherService
User->>Service: Redeem Voucher
Service->>VoucherService: Validate Voucher
VoucherService--xService: 500 Error/Timeout
Service->>VoucherService: Retry (immediately)
VoucherService--xService: 500 Again
...repeat...
Memperkenalkan Backoff Strategy
Backoff berarti menunggu sejenak sebelum melakukan retry. Ada beberapa pola backoff yang bisa digunakan:
- Fixed Wait: Selalu tunggu X detik setiap retry.
- Incremental Wait: Setiap retry, waktu tunggu bertambah.
- Exponential Backoff: Waktu tunggu bertambah secara eksponensial (1s, 2s, 4s, 8s, …)
- Exponential + Random Jitter: Menambahkan randomness untuk menghindari retry serentak dari seluruh client.
Mari kita implementasikan exponential backoff dengan jitter.
Studi Kasus Dengan Exponential Backoff & Jitter
Contoh Kode (Python)
import requests
import random
import time
def validate_voucher_with_backoff(voucher_code, max_retries=5, base_delay=0.5):
url = f"https://voucher.example.com/voucher/{voucher_code}/validate"
for attempt in range(max_retries):
try:
response = requests.get(url, timeout=2)
response.raise_for_status()
return response.json()['valid']
except (requests.HTTPError, requests.Timeout) as e:
print(f"Attempt {attempt+1} failed: {str(e)}")
# Exponential Backoff: delay = base_delay * 2 ^ attempt
delay = base_delay * (2 ** attempt)
# Add random jitter +/- 0..base_delay
jitter = random.uniform(0, base_delay)
total_delay = delay + jitter
print(f"Retrying in {total_delay:.2f} seconds...")
time.sleep(total_delay)
raise Exception("All attempts failed")
Visualisasi Diagram
gantt
title Retry dan Backoff Timeline
dateFormat X
section Retry Attempts
Attempt#1 :done, 0, 1
Backoff#1 :active, 1, 0.6
Attempt#2 :done, 1.6, 1
Backoff#2 :active, 2.6, 1.2
Attempt#3 :done, 3.8, 1
Simulasi: Efek Backoff dan Jitter
Bayangkan 1000 user serentak melakukan redeem voucher.
- Tanpa backoff: Semua retry dalam waktu <1 detik
- Dengan exponential backoff + jitter: Retry tersebar dalam beberapa detik, mengurangi puncak load pada Voucher Service
| Scenario | Puncak Load pada Remote Service | Waktu Sukses Rata-Rata |
|---|---|---|
| No Retry | Rendah | Rendah |
| Retry Immediate (No Backoff) | Sangat tinggi, risk storm | Lebih cepat (jika service sehat) |
| Exponential Backoff + Jitter | Lebih rendah, load tersebar | Lebih lama, tapi lebih robust |
Bagaimana Menentukan Retry dan Backoff yang Tepat?
Tidak ada satu jawaban pasti. Berikut beberapa prinsip praktis:
- Identifikasi error yang boleh di-retry. Retry hanya untuk temporary failures: timeout, 5xx, connection reset. Error seperti authentication dan validation harus fail-fast.
- Batasi jumlah retry (biasanya 3-5 kali).
- Backoff: gunakan exponential (atau setidaknya incremental), jangan fixed-delay saja.
- Jitter: randomisasikan waktu tunggu, agar tidak serentak.
- Logika terminate lebih awal: jika deadline sudah dekat, cancel all (pakai context dengan timeout di Go atau deadline di Java).
Contoh Implementasi di Go
package main
import (
"fmt"
"math/rand"
"net/http"
"time"
)
func ValidateVoucherWithBackoff(voucherCode string, maxRetries int, baseDelay time.Duration) (bool, error) {
url := fmt.Sprintf("https://voucher.example.com/voucher/%s/validate", voucherCode)
var client = &http.Client{Timeout: 2 * time.Second}
for attempt := 0; attempt < maxRetries; attempt++ {
resp, err := client.Get(url)
if err == nil && resp.StatusCode == http.StatusOK {
return true, nil // Simulasi return
}
delay := baseDelay * (1 << attempt) // Exponential
jitter := time.Duration(rand.Int63n(int64(baseDelay)))
totalDelay := delay + jitter
fmt.Printf("Attempt %d failed. Backoff %s\n", attempt+1, totalDelay)
time.Sleep(totalDelay)
}
return false, fmt.Errorf("all attempts failed")
}
Takeaways
- Retry tanpa strategi sama buruknya dengan tidak ada retry.
- Exponential Backoff + Jitter adalah pattern industry best practice untuk memitigasi masalah remote service unstable.
- Analisis skenario secara realistik: latency bertambah, tapi sistem lebih stabil.
- Instrumentasi (monitoring dan logging) wajib agar tahu rootcause — terlalu banyak retry bisa mengindikasikan masalah sistemik atau dependency.
Kesimpulan
Retry dan backoff adalah pattern sederhana tapi krusial dalam distributed system. Memahami kapan dan bagaimana melakukan retry bukan hanya urusan coding, melainkan juga tentang mendesain resilience dan reliability dalam sistem kita.
Semoga studi kasus dan contoh kode di atas jadi bekal ketika Anda berhadapan dengan unreliable upstream service — karena dalam produksi, “Trust (and retry) but verify!” adalah jurus ampuh untuk survive menghadapi dunia microservices yang tidak pernah sepenuhnya reliable.
Silakan diskusi dan share strategi retry yang pernah Anda terapkan — pengalaman battle-tested akan membuat kita engineer yang lebih tangguh.
// Cheers!