API rate limiting, bir istemcinin belirli bir zaman diliminde API'ne kaç istek gönderebileceğini sınırlama tekniğidir. Amaç tek değildir: kötüye kullanımı ve kaba kuvvet saldırılarını engellemek, altyapı maliyetlerini öngörülebilir tutmak, tek bir istemcinin tüm kaynakları tüketip diğerlerini aç bırakmasını önlemek ve adil bir kullanım dağılımı sağlamak. Doğru kurgulanmış bir sınırlama katmanı, API'ni hem daha güvenli hem de daha kararlı hale getirir.
Neden rate limiting'e ihtiyacın var?
Sınırlamasız bir endpoint, birkaç senaryoda hızla devre dışı kalabilir. Bir istemcideki sonsuz döngü, kötü niyetli bir bot ya da ücretsiz katmanı suistimal eden bir kullanıcı saniyede yüzlerce istek gönderebilir. Bu durumlar şunlara yol açar:
- Kaynak tükenmesi: Veritabanı bağlantıları, CPU ve bellek bir istemci tarafından doldurulur.
- Maliyet patlaması: Bulut faturaları ve üçüncü taraf API çağrıları kontrolsüz büyür.
- Güvenlik riski: Parola deneme, OTP brute-force ve scraping kolaylaşır.
- Adaletsizlik: Agresif bir kullanıcı, diğer herkesin deneyimini bozar.
Token bucket algoritması
Token bucket, esnekliği nedeniyle en yaygın kullanılan yöntemdir. Mantığı basittir: her istemcinin sabit kapasiteli bir kovası vardır ve bu kovaya belirli bir hızda jeton eklenir. Her istek bir jeton tüketir; kova boşsa istek reddedilir. Kovanın bir kapasitesi olduğu için kısa süreli ani yükleri (burst) tolere eder, ama uzun vadede ortalama hızı sabit tutar.
class TokenBucket {
constructor(capacity, refillPerSecond) {
this.capacity = capacity;
this.tokens = capacity;
this.refillRate = refillPerSecond;
this.last = Date.now();
}
tryRemove(count = 1) {
const now = Date.now();
const elapsed = (now - this.last) / 1000;
this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
this.last = now;
if (this.tokens >= count) {
this.tokens -= count;
return true;
}
return false;
}
}
Bu yaklaşımda capacity izin verilen burst büyüklüğünü, refillRate ise sürdürülebilir ortalama hızı belirler. Örneğin kapasite 20 ve doluş hızı saniyede 5 ise, istemci kısa sürede 20 isteğe kadar patlama yapabilir ama uzun vadede saniyede 5 isteğe yerleşir.
Sliding window algoritması
Basit bir "sabit pencere" sayacı (örneğin dakikada 100 istek) sınır kenarında sorun yaratır: pencerenin son saniyesinde 100, yeni pencerenin ilk saniyesinde 100 istek gönderilirse iki saniyede 200 istek geçer. Sliding window bunu kayan bir zaman aralığıyla çözer. En doğru biçim, her isteğin zaman damgasını saklayıp pencere dışına düşenleri eleyen sliding window log'dur. Redis'te bunu sıralı küme (sorted set) ile uygulamak yaygındır:
-- ZADD ile zaman damgasını ekle, eski olanları temizle, say
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now)
redis.call('PEXPIRE', key, window)
return 1
end
return 0
Log yöntemi en hassas olandır ama her istemci için tüm zaman damgalarını saklar. Bellek hassasiysen, sliding window counter denilen yaklaşımı tercih edebilirsin: mevcut ve bir önceki sabit pencerenin sayaçlarını ağırlıklandırarak yaklaşık bir değer hesaplar; çok daha az bellek kullanır, küçük bir hata payıyla.
Dağıtık ortamda sayaçları paylaşmak
Birden fazla uygulama sunucun varsa, her sunucunun kendi bellek içi sayacını tutması sınırı fiilen sunucu sayısıyla çarpar. Çözüm, sayaçları merkezi ve hızlı bir depoda — genellikle Redis'te — tutmaktır. Redis'in atomik komutları (INCR, sorted set işlemleri) ve tek bir round-trip'te çalışan Lua scriptleri, yarış koşulları olmadan tutarlı sayım sağlar. Birçok framework bunu hazır sunar: örneğin Laravel'de RateLimiter facade'ı ve throttle middleware'i, Express tarafında express-rate-limit + Redis store, NGINX'te ise limit_req direktifi.
Doğru HTTP yanıtları ve başlıklar
Bir isteği reddederken doğru sinyalleri vermek, iyi bir API tasarımının parçasıdır. Standart 429 Too Many Requests durum kodunu döndür ve istemciye ne zaman tekrar deneyebileceğini söyle:
Retry-After: kaç saniye sonra tekrar denenebileceği.RateLimit-Limit: pencere başına izin verilen toplam istek.RateLimit-Remaining: kalan istek hakkı.RateLimit-Reset: sayaç sıfırlanana kadar kalan süre.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
Content-Type: application/json
{"error":"rate_limit_exceeded","message":"Çok fazla istek. 30 saniye sonra tekrar deneyin."}
İstemcilerin bu başlıkları okuyup üstel geri çekilme (exponential backoff) ile yeniden denemesini teşvik et. Böylece reddedilen istemci, sınır kalkar kalkmaz topluca geri gelip yeni bir yük dalgası oluşturmaz.
Pratik ipuçları
- Anahtarı doğru seç: Kimliği doğrulanmış kullanıcılarda kullanıcı/API anahtarı bazında, anonim trafikte IP bazında sınırla. IP tek başına proxy/NAT arkasında yanıltıcı olabilir.
- Katmanlı limitler: Pahalı endpoint'lere (arama, dışa aktarma, giriş) daha sıkı, hafif okuma uçlarına daha gevşek limit koy.
- Fail-open mu fail-closed mu: Redis çökerse isteği reddetmek mi geçirmek mi istediğine bilinçli karar ver.
- Gözlemle: 429 oranını ve hangi anahtarların sınıra takıldığını metrikle; gerçek kötüye kullanımı meşru yoğunluktan ayırt et.
Sık Sorulan Sorular
Token bucket mı yoksa sliding window mı kullanmalıyım?
Kısa süreli ani yüklere (burst) izin verip ortalama hızı korumak istiyorsan token bucket idealdir ve uygulaması basittir. Pencere kenarındaki ikiye katlanma sorununu mutlak olarak ortadan kaldırmak ve çok kesin sayım istiyorsan sliding window log tercih et. Çoğu API için token bucket fazlasıyla yeterlidir.
Rate limit'i nerede uygulamalıyım?
İdeal olan katmanlıdır: kaba bir koruma için ön taraftaki NGINX/CDN/API gateway katmanında, iş mantığına özgü ince limitler için uygulama katmanında. Uygulama katmanı, kullanıcı kimliği gibi bağlamı bildiği için daha akıllı kararlar verebilir.
429 ile 503 arasındaki fark nedir?
429 Too Many Requests istemcinin kendi sınırını aştığını söyler; sorumluluk istemcidedir. 503 Service Unavailable ise sunucunun genel olarak aşırı yüklü veya bakımda olduğunu belirtir. Rate limiting için her zaman 429 doğru koddur.
API'ne sağlam bir rate limiting katmanı mı kurmak istiyorsun? Token bucket'tan Redis tabanlı dağıtık sayaçlara kadar mevcut altyapına uygun bir çözüm tasarlayabilirim. Benimle iletişime geç ve projeni birlikte güvenli hale getirelim.