tutorial

36 Rate Limiting di Interceptor

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 IDBatas RequestBurst MaxStatus AwalStatus Setelah 10x Request
client-a5 req/s10✅ Allowed❌ Blocked (Rate Limit)
client-b5 req/s10✅ 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

comments powered by Disqus

Topik Terhangat

programming
205
tutorial
72
tips-and-trick
43
jaringan
28
hardware
11
linux
4
kubernetes
1