67. Retry Mechanism di Client gRPC: Membuat Koneksi Lebih Andal di Layanan Mikro
gRPC telah menjadi fondasi komunikasi yang populer di antara microservices modern, baik di lingkungan cloud-native maupun ekosistem enterprise konvensional. Dengan fitur seperti strong typing, contract-first API, streaming, serta performa tinggi berbasis protokol Protobuf, gRPC mampu menangani berbagai kebutuhan komunikasi service-to-service. Namun, apa yang terjadi jika koneksi antara client dengan server tidak stabil? Jawabannya: kita perlu menerapkan retry mechanism agar permintaan tidak mudah gagal dan aplikasi tetap robust.
Pada artikel ke–67 seri teknik perbaikan ketersediaan layanan ini, kita akan membahas cara kerja retry mechanism di client gRPC (bahasa Go), mengapa penting ada, bagaimana mengintegrasikan, dan cara mengoptimalkannya supaya tidak jadi “bumerang” di sistem terdistribusi.
Mengapa Perlu Retry di Client?
Dalam sistem terdistribusi, kegagalan adalah hal biasa—bisa karena jaringan lambat, DNS timeout, instance server di-restart, atau BGP error. Retry mechanism menjadi alat pertama dalam “toolbox” engineer untuk mengatasi kegagalan sementara (transient error). Targetnya: maximalkan peluang sukses tanpa membebani server secara berlebihan.
Tabel 1 – Contoh Error dan Kapan Melakukan Retry
| Kode Error gRPC | Nama Error | Diyarankan Retry? |
|---|---|---|
UNAVAILABLE | Layanan tidak tersedia | Ya |
DEADLINE_EXCEEDED | Timeout | Ya, dengan caution |
RESOURCE_EXHAUSTED | Pembatasan quota | Tidak |
INVALID_ARGUMENT | Input klien salah | Tidak |
INTERNAL | Eror internal server | Bisa,cermati |
Garis besarnya: retry cocok untuk error yang sifatnya temporer dan biasanya selesai dalam waktu singkat.
Retry di gRPC: Autopilot atau Manual?
Secara default, library gRPC tidak otomatis meretry permintaan yang gagal. Pendekatannya adalah:
- Grpc Retry Policy: Sedikit “ ajaib”, sejak v1.8, gRPC mendukung retry policy pada level client via Service Config, sayangnya hanya diimplementasikan penuh pada client gRPC versi C++/Java/Python, sementara Go baru “experimental” (hingga pertengahan 2024) dan pluginnya agak terbatas.
- Manual Retry di Client: Khusus di Go dan TypeScript misalnya, praktik yang umum adalah membungkus pemanggilan RPC dalam blok retry sendiri.
Di sini, kita akan fokus pada pendekatan manual supaya mengerti apa yang terjadi di bawah kapnya dan mudah disesuaikan kebutuhan.
Prinsip Dasar Retry yang Baik
Beberapa pedoman agar retry kita tidak menjadi sumber masalah baru:
- Exponential backoff: Setiap kali gagal, jeda retry diperpanjang. Tujuannya: tidak membanjiri server yang sedang bermasalah.
- Jitter: Tambahkan randomisasi di delay agar “stampede effect” tidak terjadi jika banyak client retry bersamaan.
- Batas maksimal percobaan (Max attempts): Hindari retry tak berujung.
- Idempotence-aware: Pastikan method RPC idempotent untuk menghindari double-effect.
Ilustrasi Flow Retry Mechanism
flowchart TD
A[Start gRPC Call] --> B{RPC Return Error?}
B -- No --> S[Success]
B -- Yes --> C{Retryable Error?}
C -- No --> F[Fail & Exit]
C -- Yes --> D[Wait (Backoff + Jitter)]
D --> E{Max Retry?}
E -- No --> A
E -- Yes --> F
Studi Kasus: Retry Mechanism Pada gRPC Client di Go
Anggap kita punya microservice Go yang melakukan panggilan ke katalog produk via gRPC. Fungsi GetProduct dipanggil oleh consumer api, dan kadang error Unavailable terjadi karena maintenance instance katalog.
Mari kita implementasikan retry mechanism sederhana:
1. Definisikan Retryable Error
func isRetryableError(err error) bool {
if err == nil {
return false
}
s, ok := status.FromError(err)
if !ok {
return false
}
switch s.Code() {
case codes.Unavailable, codes.DeadlineExceeded:
return true
default:
return false
}
}
2. Retry Helper (Exponential Backoff + Jitter)
import (
"math/rand"
"time"
)
func getBackoffDelay(attempt int, baseDelay time.Duration, maxDelay time.Duration) time.Duration {
// Exponential backoff: baseDelay * 2^attempt
delay := baseDelay * (1 << attempt)
if delay > maxDelay {
delay = maxDelay
}
// Tambah jitter, misal sampai 20% dari delay
jitter := time.Duration(rand.Int63n(int64(delay) / 5))
return delay + jitter
}
3. Implementasi Retry Wrapper
func CallWithRetry(ctx context.Context, call func() error, maxAttempts int, baseDelay, maxDelay time.Duration) error {
var err error
for attempt := 0; attempt < maxAttempts; attempt++ {
err = call()
if !isRetryableError(err) {
return err
}
if attempt < maxAttempts-1 {
delay := getBackoffDelay(attempt, baseDelay, maxDelay)
// Bisa juga cek ctx.Done() di sini untuk abort jika context dibatalkan
time.Sleep(delay)
}
}
return err
}
4. Pemanggilan RPC dengan Retry
maxAttempts := 5
baseDelay := 200 * time.Millisecond
maxDelay := 3 * time.Second
err := CallWithRetry(ctx, func() error {
// Call gRPC stub
_, rpcErr := productClient.GetProduct(ctx, &pb.GetProductRequest{ProductId: "sku-123"})
return rpcErr
}, maxAttempts, baseDelay, maxDelay)
if err != nil {
log.Printf("gagal GetProduct setelah retry: %v", err)
}
Simulasi: Apa Efek Retry pada Traffic & Latency
Andaikan ada 1000 client yang memanggil API gRPC dalam 1 waktu, dan 30% permintaan gagal sementara (timeout). Dengan max 5 attempts, total traffic potensial:
| Percobaan ke- | Client masih gagal | Permintaan total |
|---|---|---|
| 1 | 300 | 1000 |
| 2 | 90 (30% dari 300) | 300 |
| 3 | 27 | 90 |
| 4 | 8 | 27 |
| 5 | 2 | 8 |
Total permintaan: Hampir 1.425 permintaan — bertambah 42.5% dibanding tanpa retry.
Hati-hati: Kalau traffic sangat tinggi dan hampir semua error, retry tanpa pembatasan bisa memperparah beban (thundering herd).
Optimasi & Best Practice
- Gunakan context dengan deadline: Retry hanya dilakukan dalam batas waktu.
- Beri ciri tracing dalam retry: Logging retry attempt dan error (dengan correlation ID misalnya).
- Kombinasikan dengan circuit breaker: Jika error rate tinggi, circuit breaker open untuk sementara, retry tidak dilakukan.
- Hindari retry pada operation yang tidak idempotent (insert, transfer uang, dll).
- Atur retry di sisi client maupun Service Config (bila bahasa dan versi library/infra mendukung).
Kesimpulan
Retry mechanism di client gRPC adalah strategi penting untuk membuat aplikasi layanan kita lebih tahan banting terhadap kegagalan jaringan atau server sementara. Namun, implementasinya butuh perencanaan matang—hindari asal retry tanpa batas dan pastikan hanya pada error yang tepat serta operation yang aman diulang. Dengan mengadopsi best-practice seperti exponential backoff, jitter, dan pengawasan pada traffic, engineering team dapat mencegah sistem jadi “overload” saat downtime, sembari memberi pengalaman terbaik untuk user akhir.
Investasi waktu pada retry yang bijak akan menghemat ratusan jam debugging incident di production.
Referensi Lebih Lanjut
- grpc-go Retry Support (experimental)
- Google Cloud: Best Practice for Retry
- Exponential Backoff and Jitter
Selamat bereksperimen—dan semoga komunikasi antar layanan di sistem Anda semakin andal! 🚀