
API Rate Limiting Katmanında Token Bucket vs Sliding Window: Pragmatik Karar Matrisi

Giriş
Mikroservis mimarilerinde veya API Gateway katmanlarında rate limiting, arka uç sistemlerinizi (backend) aşırı yüklenmelerden, “noisy neighbor” (gürültülü komşu) problemlerinden ve dağıtık hizmet aksatma (DDoS) saldırılarından koruyan son savunma hattıdır. Saniyede 45.000 istek (RPS) karşılayan bir ödeme sistemi gateway’inde, yanlış seçilmiş bir rate limiting algoritması p99 gecikmelerini 12ms’den 850ms seviyelerine fırlatabilir. Dağıtık bir sistemde 850ms’lik bir duraksama, connection pool’ların tükenmesi ve ardışık timeout hatalarıyla sonuçlanarak tüm sistemin çökmesine (cascading failure) neden olur.
Çoğu mühendislik ekibi, gereksinimleri tam analiz etmeden varsayılan rate limiting kütüphanelerini sisteme dahil etme eğilimindedir. Oysa bir e-ticaret platformunun ödeme uç noktası ile bir public GraphQL API’sinin trafik karakteristikleri tamamen farklıdır. Bir uç nokta anlık trafik sıçramalarını (burst) tolere etmeliyken, diğeri saniye başına kesin bir tavan sınır (hard limit) uygulamalıdır.
Bu karar rehberinde, production ortamlarında standart haline gelmiş iki temel algoritmayı; Token Bucket ve Sliding Window yaklaşımlarını, bellek tüketimi (memory footprint), işlem karmaşıklığı (computational overhead) ve doğruluk payı metrikleri üzerinden parçalarına ayıracağız.
İçindekiler
- Rate Limiting Sistemlerinde Temel Metrikler ve Sınırlar
- Token Bucket Algoritması: Burst Trafik Yönetimi
- Sliding Window Algoritması: Hassas ve Dağıtık Sınırlar
- Pragmatik Karar Matrisi: Hangi Senaryoda Hangisi?
- Production Notları ve Fallback Stratejileri
- Sık Sorulan Sorular
- Sonuç
Rate Limiting Sistemlerinde Temel Metrikler ve Sınırlar
Karar sürecine geçmeden önce, bir algoritmanın production ortamındaki maliyetini belirleyen üç temel kısıtı tanımlamamız gerekiyor:
- Bellek Ayak İzi (Memory Footprint): Algoritma, her benzersiz kullanıcı veya IP adresi için state (durum) tutmak zorundadır. Algoritmanın O(1) veya O(N) bellek karmaşıklığına sahip olması, 10 milyon aktif kullanıcılı bir sistemde Redis kümenizin (cluster) 50 MB mı yoksa 12 GB mı RAM tüketeceğini belirler.
- Atomik İşlem Zorunluluğu: Dağıtık bir mimaride, 5 farklı API sunucusu aynı anda Redis’e erişirken “race condition” (yarış durumu) oluşmamalıdır. Çözüm genellikle Lua scriptleri üzerinden Redis üzerinde atomik işlemler yapmaktır.
- Ağ Gecikmesi (Network Round Trip): Rate limiting kararı saniyenin binde biri süresinde verilmelidir. Algoritma, state okuma ve yazma işlemlerini tek bir ağ çağrısında (single RTT) bitirmelidir.
Token Bucket Algoritması: Burst Trafik Yönetimi
Token Bucket algoritması, bir kovanın (bucket) belirli bir kapasiteye (capacity) sahip olması ve bu kovanın belirli bir periyotta (refill rate) jetonlarla (token) doldurulması mantığına dayanır. Gelen her API isteği, kovadan bir jeton tüketir. Eğer kovada jeton yoksa, istek HTTP 429 (Too Many Requests) koduyla reddedilir.
Nasıl Çalışır ve Neden Kullanılır?
En büyük avantajı ani trafik sıçramalarına (burst) izin vermesidir. Örneğin, limitiniz 1 dakikada 60 istek ise, sistem saniyede 1 jeton üretecek şekilde ayarlanır. Kova kapasitesi 60’tır. Bir kullanıcı 1 saat boyunca hiç istek yapmazsa kova 60 jetonla dolar. Kullanıcı bir anda saniyenin onda biri (100ms) süresinde 60 istek gönderdiğinde, sistem bu 60 isteğin tamamını tek seferde kabul eder. Sonraki istekler için saniyede 1 jeton üretilmesini beklemek zorundadır.
Bu algoritma, kullanıcıların sayfa yüklenirken arka arkaya 10-15 statik asset veya asenkron API çağrısı yaptığı web frontend’leri için idealdir.
Redis Üzerinde O(1) Token Bucket Uygulaması
Aşağıdaki Lua betiği (script), Redis üzerinde atomik olarak Token Bucket işletimini sağlar. İstemci tarafında saat kayması (NTP drift) sorunlarını önlemek için zaman damgası hesaplamaları Redis’in TIME komutu üzerinden değil, parametre olarak istemciden alınır (veya tersi kurgulanabilir, ancak atomiklik esastır).
-- KEYS[1] : rate_limit_key (Örn: "rl:bucket:user_123")
-- ARGV[1] : kova_kapasitesi (Örn: 100)
-- ARGV[2] : dolum_orani_saniye_basina (Örn: 10)
-- ARGV[3] : su_anki_zaman_saniye (unix timestamp)
-- ARGV[4] : talep_edilen_jeton_miktari (Örn: 1)
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local bucket = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(bucket[1])
local last_refill = tonumber(bucket[2])
if tokens == nil then
tokens = capacity
last_refill = now
else
local time_passed = math.max(0, now - last_refill)
local new_tokens = time_passed * refill_rate
tokens = math.min(capacity, tokens + new_tokens)
-- Yenilenen token varsa last_refill güncellenir
if new_tokens > 0 then
last_refill = now
end
end
if tokens >= requested then
tokens = tokens - requested
redis.call('HMSET', key, 'tokens', tokens, 'last_refill', last_refill)
redis.call('EXPIRE', key, math.ceil(capacity / refill_rate) + 1)
return 1 -- İzin verildi
else
-- Jeton yetersiz
return 0 -- HTTP 429
end
Bellek Analizi: Bu yaklaşım her kullanıcı için sadece bir Hash haritasında 2 alan (tokens ve last_refill) tutar. Toplam boyut anahtar başına yaklaşık 48 byte’tır. 1 milyon kullanıcı için Redis üzerinde sadece ~48 MB RAM gerektirir. O(1) karmaşıklığı sayesinde Redis işlem süresi (execution time) 0.8ms seviyelerindedir.
Sliding Window Algoritması: Hassas ve Dağıtık Sınırlar
Fixed Window (Sabit Pencere) algoritmalarının en büyük sorunu “sınır aşımı” (boundary) krizidir. 1 dakikada 100 istek limitiniz varsa, kullanıcı 00:59 saniyesinde 100 istek, 01:01 saniyesinde 100 istek daha yapabilir. Sistem 2 saniye içinde 200 isteği backend’e iletir. Backend’in anlık kapasitesi bu %200’lük yükü kaldıramayabilir.
Sliding Window (Kayan Pencere) bu problemi çözer. Ancak Sliding Window’un kendi içinde iki varyasyonu vardır: Log ve Counter.
Sliding Window Log: Neden Uzak Durmalısınız?
Bu yöntemde, gelen her isteğin zaman damgası Redis’te bir Sorted Set (ZSET) içinde tutulur. Limit kontrolü yapılacağı zaman, mevcut zamandan 1 dakika öncesi hesaplanır, bu aralığın dışında kalan eski zaman damgaları ZSET’ten silinir (`ZREMRANGEBYSCORE`) ve kalan eleman sayısı sayılır (`ZCARD`).
Matematiksel Felaket: Limit 1 dakikada 10.000 istek ise, bir kullanıcı için Redis’te 10.000 adet 64-bit float (zaman damgası) tutmanız gerekir. Kullanıcı başına ortalama 85 KB bellek harcanır. 100.000 aktif bağlantıda 8.5 GB RAM tüketilir. Çöp toplama (garbage collection) ve ZSET operasyonları O(log(N) + M) karmaşıklığındadır. Saniyede binlerce istek alan sistemlerde p99 gecikmeleri 45ms’nin üzerine çıkar.
Sliding Window Counter: Pragmatik Çözüm
Cloudflare’in altyapısında tercih ettiği Sliding Window Counter mimarisi, Log yönteminin bellek felaketini O(1) karmaşıklığıyla çözer. Yaklaşım şudur: Zaman sabit 1 dakikalık pencerelere bölünür. Her pencere için sadece toplam istek sayısı tutulur. Mevcut penceredeki oran, bir önceki pencerenin ağırlığı ile toplanarak tahmin (approximation) edilir.
Formül:
Tahmini İstek = (Önceki Pencere İstek Sayısı * ((60 - Mevcut Saniye) / 60)) + Mevcut Pencere İstek Sayısı
Örnek Senaryo: Limit dakikada 100 istek.
Önceki dakika (10:00:00 – 10:00:59) toplam istek: 80
Mevcut dakika (10:01:00 – 10:01:15) toplam istek: 20
Şu anki saniye: 15 (Mevcut dakikanın %25’i tamamlandı, %75’i önceki dakikanın ağırlığı)
Tahmini Yük: (80 * 0.75) + 20 = 60 + 20 = 80 istek.
Limit 100 olduğu için işleme izin verilir.
Bellek Analizi: Sadece 2 integer değişken tutulur: prev_window_count ve curr_window_count. Toplam RAM maliyeti Token Bucket ile neredeyse aynıdır (kullanıcı başına ~32 byte). Gecikme süresi 1.1ms civarındadır.
Pragmatik Karar Matrisi: Hangi Senaryoda Hangisi?
Her iki algoritma da O(1) bellek ve O(1) işlem süresine optimize edilebilir (Sliding Window Counter baz alındığında). Tercih, API’nizin tüketim karakteristiğine bağlıdır.
| Kriter / Karakteristik | Token Bucket | Sliding Window (Counter) |
|---|---|---|
| Burst Trafik Toleransı | Yüksek. Biriken tokenlar anında tüketilebilir. | Düşük/Yok. Trafik zaman ekseninde pürüzsüzleştirilir (smooth). |
| Kullanım Senaryosu | Web Frontend API’leri, Asenkron Toplu İşlemler (Batch Processing). | Ödeme Gateway’leri, Public SMS/Email API’leri, B2B entegrasyonlar. |
| Sınır İhlali (Boundary Problem) | Yok. Jeton bittiğinde kesin ret. | Matematiksel ağırlıklandırma nedeniyle %1-2 sapma payı (approximation error) olabilir. |
| Backend Koruması | Anlık sıçramalar (spike) backend’e doğrudan yansır. Veritabanı CPU’su anlık %100’e vurabilir. | Trafik çok dengeli iletildiğinden backend üzerindeki load pürüzsüz (flat) kalır. |
“Eğer X ise -> Y” Karar Kuralları
- Eğer API’nizi tüketen istemciler SPA (Single Page Application) ise ve sayfa geçişlerinde paralel 15-20 HTTP isteği fırlatıyorsa -> Token Bucket kullanın. Kullanıcının sayfa yükleme hızını (UX) cezalandırmamış olursunuz.
- Eğer arka uç (backend) sisteminiz legacy, soğuk başlangıç (cold start) süresi yüksek veya anlık veritabanı bağlantı limitleri (connection pool starvation) katıysa -> Sliding Window Counter kullanın.
- Eğer public bir SaaS API sunuyorsanız (örn. Twilio, Stripe) ve müşterilerinizin saniye başına gönderim limitlerini kesin bir şekilde faturaya yansıtıyorsanız -> Sliding Window Counter kullanın.
- Eğer video veya büyük dosya indirme/yükleme uç noktalarınız varsa (bandwidth bazlı rate limit) -> Token Bucket kullanın. Tokenları istek sayısı değil, byte cinsinden eksiltin.
Production Notları ve Fallback Stratejileri
Production ortamında sadece algoritmayı seçmek yetmez, felaket senaryolarını da planlamanız gerekir.
1. Redis Çökerse Ne Olacak? (Fail-Open vs Fail-Closed)
API Gateway’iniz Redis cluster’a ulaşamazsa veya Redis tarafında CPU %100 olursa, iki seçeneğiniz vardır:
- Fail-Closed: Tüm istekleri reddet (HTTP 503 Service Unavailable). Rate limiting’in koruduğu backend zaten çökmenin eşiğindeyse mecburidir.
- Fail-Open: Gelen tüm trafiğe izin ver ve işlemi pas geç. Genellikle tercih edilen budur, zira Redis’teki 5 saniyelik bir ağ kesintisinin tüm sistemi durdurması (single point of failure) istenmez. Fallback olarak API Gateway (örn. Envoy, Kong veya Nginx) katmanında in-memory ve lokal (cluster-aware olmayan) basit bir RateLimiter devreye sokulmalıdır.
Tavsiye: Redis sorgularına kesinlikle bir timeout ekleyin. P99 yanıt süreniz 2ms iken timeout değeriniz maksimum 10ms olmalıdır. Redis yanıt vermezse Fail-Open senaryosunu işletin ve bir Prometheus metriği (örn.
rate_limit_redis_timeout_total) fırlatın.
2. HTTP Yanıt Başlıkları (Headers)
İstemcilere, ne zaman tekrar istek yapabileceklerini açıkça belirtmeniz gerekir. Hem Token Bucket hem de Sliding Window implementasyonlarında şu başlıkları döndürmek endüstri standardıdır (RFC 6585):
X-RateLimit-Limit: Periyottaki maksimum sınır.X-RateLimit-Remaining: Kalan limit.X-RateLimit-Reset: Limitin sıfırlanacağı Unix timestamp zaman damgası.- HTTP 429 döndüğünüzde:
Retry-After: 15(15 saniye sonra tekrar dene).
3. Redis Pipeline ve Hash Tags
Eğer Redis Cluster kullanıyorsanız ve Lua betiğinizde birden fazla key okuyup yazıyorsanız, bu key’lerin aynı shard (slot) üzerinde olduğundan emin olmalısınız. Rate limiting key’lerinizi tanımlarken süslü parantezler kullanarak Hash Tags uygulayın. Örn: rl:bucket:{user_id_8472}. Bu sayede bu kullanıcıya ait tüm metriklerin aynı Redis düğümünde işlenmesini garanti edersiniz.
Sık Sorulan Sorular
Nginx Ingress katmanında mı, yoksa Application kodunda mı Rate Limiting yapmalıyım?
Uygulama katmanına (örn. Spring Boot, Node.js) ulaşmadan önce, trafiği network sınırında (Edge) kesmek her zaman en az CPU ve bellek maliyetini yaratır. Mümkünse Envoy, Nginx veya Kong gibi Gateway katmanlarında Lua pluginleri veya yerleşik filtreler aracılığıyla rate limiting uygulayın. Uygulama katmanında sadece spesifik business kuralı gerektiren limitler (örn: “Bir kullanıcı günde sadece 3 kez şifre sıfırlama SMS’i gönderebilir”) tutulmalıdır.
Sliding Window Counter algoritmasındaki ağırlık hesaplaması (approximation) ne kadar tutarlı?
Cloudflare metriklerine göre bu matematiksel formül, gerçek trafik simülasyonlarında %1’den daha az bir sapma oranı göstermektedir. Özellikle yüksek hacimli (saniyede 10.000+ istek) sistemlerde bu %1’lik sapma, Sliding Window Log’un yarattığı devasa bellek ve gecikme maliyeti karşısında rahatlıkla göze alınabilir bir trade-off’tur.
B2B ve B2C limitleri için aynı havuzu mu kullanmalıyım?
Hayır. “Tiered Rate Limiting” (Kademeli Sınırlandırma) kurgulamalısınız. Ücretsiz bir son kullanıcı IP bazlı 10 RPS limitine tabi tutulurken, API anahtarı (API Key) kullanan ücretli bir B2B müşterisi API Key üzerinden 1000 RPS limitine sahip olmalıdır. Key dizaynınız rl:ip:192.168.1.1 ve rl:apikey:pk_live_12345 şeklinde ayrışmalıdır.
IP bazlı sınırlandırmada IPv6 ve NAT arkasındaki kullanıcıları nasıl yönetiriz?
Mobil ağlar (CGNAT) veya kurumsal ağlar arkasındaki binlerce kullanıcı aynı IPv4 adresi ile gelebilir. Salt IP bazlı limit, aynı ofisteki 50 kişiyi bloklamanıza neden olabilir. Bu nedenle Rate Limiting anahtarınızı (key) IP + User-Agent veya eğer kullanıcı giriş yapmışsa kesinlikle User_ID (Authorization token’dan extract edilerek) üzerinden kurgulayın. IPv6 için genellikle adresin ilk /64 bloğu tek bir cihaz veya yerel ağ olarak kabul edilerek kısıtlanır.
Sonuç
API Rate Limiting, basit bir “sınırı aşanı engelle” mantığından çok daha fazlasıdır. Sistemi ani yüklerden korumak ve kaynakları adil dağıtmak (fairness) için doğru matematiksel modelin seçilmesini gerektirir. Token Bucket algoritması, web arayüzleri ve esnek tüketim senaryolarında pürüzsüz bir kullanıcı deneyimi sunarken; Sliding Window Counter algoritması, sunucu kaynaklarının milimetrik hesaplandığı katı arka uç sistemlerinde bellek israfını ve sınır ihlallerini ortadan kaldırır.
Aksiyon Planı: Mevcut rate limiting katmanınızın bellek tüketimini ve p99 işlem süresini ölçün. Eğer kullanıcı başına 100 byte’tan fazla bellek harcayan veya limit hesaplama süresi 5ms’yi aşan bir kütüphane kullanıyorsanız, yukarıdaki Redis O(1) mimarilerinden birine geçiş yaparak altyapı maliyetlerinizde dramatik bir düşüş elde edebilirsiniz.
Bunları da beğenebilirsiniz

REST API Tasarımında Offset mi Cursor Pagination mı? Veri Büyüklüğüne Göre Karar Matrisi
Milyar satırlık tablolarda API p99 gecikmesini 1400ms’den 45ms’ye düşüren pagination stratejileri. DB execution planları ve production trade-off analizi.

Dağıtık Sistemlerde Cardinality Explosion: Prometheus ve Mimir ile Maliyet Yönetimi
Dağıtık sistemlerde metrik maliyetlerini artıran yüksek kardinalite sorununu çözmek ve Prometheus ile Grafana Mimir üzerinden depolama optimizasyonu sağlamak için teknik stratejileri keşfedin.
