Dalam pengembangan microservice modern, kita tak hanya berbicara tentang performa dan skalabilitas, tetapi juga bagaimana melindungi service dari abuse dan lonjakan trafik berlebih. Salah satu mekanisme penting untuk ini adalah rate limiting.
Artikel ini membahas bagaimana menerapkan rate limiting secara elegan di level interceptor pada gRPC dengan bahasa Go. Kita akan membahas konsep, implementasi, dan praktik terbaiknya agar service kamu tetap tangguh dalam menghadapi beban tinggi.
Apa Itu Rate Limiting?
Rate limiting adalah teknik untuk membatasi jumlah permintaan (request) yang dapat dilakukan oleh client dalam jangka waktu tertentu. Misalnya:
- 10 request per detik per user
- 1000 request per menit per IP address
Tujuan utamanya:
- 🚫 Mencegah abuse dan spam
- 🧱 Melindungi service dari overloading
- 🧭 Menjaga fairness antara client
Pendekatan Rate Limiting di gRPC
gRPC menyediakan dukungan interceptor di sisi server (dan client). Kita dapat menyisipkan rate limiter di UnaryServerInterceptor
agar setiap request terlebih dulu dicek apakah masih dalam batas yang diizinkan.
Untuk implementasi di Go, kita bisa gunakan:
golang.org/x/time/rate
(leaky bucket)- Pustaka open-source seperti
uber-go/ratelimit
Contoh Skenario: 5 Request per Detik per Client
Instalasi Dependency
go get golang.org/x/time/rate
Implementasi Interceptor Rate Limiter
package middleware
import (
"context"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"google.golang.org/grpc/codes"
"golang.org/x/time/rate"
)
// rateLimiter menyimpan limiter per client (misal: per API key atau IP)
var rateLimiter = struct {
sync.RWMutex
clients map[string]*rate.Limiter
}{clients: make(map[string]*rate.Limiter)}
func getClientLimiter(clientID string) *rate.Limiter {
rateLimiter.RLock()
limiter, exists := rateLimiter.clients[clientID]
rateLimiter.RUnlock()
if !exists {
limiter = rate.NewLimiter(5, 10) // 5 req/s, burst max 10
rateLimiter.Lock()
rateLimiter.clients[clientID] = limiter
rateLimiter.Unlock()
}
return limiter
}
func RateLimitInterceptor() grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "metadata not found")
}
clientID := "anonymous"
if vals := md.Get("x-api-key"); len(vals) > 0 {
clientID = vals[0]
}
limiter := getClientLimiter(clientID)
if !limiter.Allow() {
return nil, status.Errorf(codes.ResourceExhausted, "rate limit exceeded")
}
return handler(ctx, req)
}
}
Integrasi dengan Server gRPC
s := grpc.NewServer(
grpc.UnaryInterceptor(RateLimitInterceptor()),
)
pb.RegisterMyServiceServer(s, &MyService{})
Tabel Simulasi Client
Client ID | Batas Request | Burst Max | Status Awal | Status Setelah 10x Request |
---|---|---|---|---|
client-a | 5 req/s | 10 | ✅ Allowed | ❌ Blocked (Rate Limit) |
client-b | 5 req/s | 10 | ✅ Allowed | ✅ Allowed (karena jeda 200ms) |
Grafik Simulasi Waktu vs Status Permintaan
gantt title Simulasi Permintaan Client A (Limit 5 req/s, burst 10) dateFormat X section Status Request ke-1 :done, 1, 1s Request ke-2 :done, 2, 1s Request ke-3 :done, 3, 1s Request ke-4 :done, 4, 1s Request ke-5 :done, 5, 1s Request ke-6 :done, 6, 1s Request ke-7 :done, 7, 1s Request ke-8 :done, 8, 1s Request ke-9 :done, 9, 1s Request ke-10 :done, 10, 1s Request ke-11 :crit, 11, 1s Request ke-12 :crit, 12, 1s
Praktik Terbaik
✅ Gunakan metadata atau JWT claims sebagai key: API key, user ID, atau IP address. ✅ Gunakan burst size bijak: Batasi agar tidak terjadi spike. ✅ Reset map limiter secara periodik: Hindari memory leak dari map yang terus tumbuh. ✅ Logging & metrics: Tambahkan log untuk request yang ditolak karena limit, serta expose metrics (Prometheus).
Bonus: Visual Alur Permintaan
flowchart TD Client -->|Request + Metadata| Interceptor Interceptor -->|"Check Allow()"| Handler Interceptor -->|Reject if Exceeded| ErrorResponse
Studi Kasus: Limit Berdasarkan Role
Jika kamu punya JWT atau metadata yang mengandung role user, kamu bisa membuat rate limit berbeda:
if role == "free" {
limiter = rate.NewLimiter(1, 3)
} else if role == "premium" {
limiter = rate.NewLimiter(10, 20)
}
Dengan begitu, kamu bisa mengatur tingkatan layanan (tiered access) sesuai role atau subscription.
Kesimpulan
Rate limiting adalah lapisan penting dalam membangun API yang aman, adil, dan stabil. Dengan menempatkannya di interceptor gRPC, kamu memastikan bahwa validasi trafik terjadi di entry point sistem.
Gunakan pendekatan ini untuk:
- 🔐 API publik
- 🧩 Internal microservice antar tim
- 🧰 Gateway gRPC sebagai agregator
13 Membuat Resolver Pertama di graphql-go
Artikel Terhangat
36 Rate Limiting di Interceptor
07 Jul 2025
13 Membuat Resolver Pertama di graphql-go
07 Jul 2025
35 Validasi Request menggunakan Interceptor
07 Jul 2025
12 Menulis File Skema GraphQL Pertama Anda
07 Jul 2025
34 Logging Interceptor dengan Context
07 Jul 2025
33 Menulis Stream Interceptor Sendiri
07 Jul 2025

36 Rate Limiting di Interceptor

13 Membuat Resolver Pertama di graphql-go

35 Validasi Request menggunakan Interceptor

12 Menulis File Skema GraphQL Pertama Anda

34 Logging Interceptor dengan Context
