API Rate Limiting ve Throttling Stratejileri
API Rate Limiting Nedir ve Neden Gereklidir?
Modern yazılım mimarisinde API'ler, sistemler arası iletişimin temel yapı taşlarıdır. Ancak sınırsız erişime açık bir API, kısa sürede aşırı yüklenmeye, performans düşüşüne ve hatta hizmet kesintilerine yol açabilir. Rate limiting, belirli bir zaman diliminde bir istemcinin yapabileceği istek sayısını sınırlandırma mekanizmasıdır. Throttling ise isteklerin hızını kontrol altına alarak sisteme düzgün bir akış sağlamayı amaçlar.
Rate limiting uygulamanın başlıca nedenleri şunlardır:
- DDoS ve brute-force saldırılarına karşı koruma: Kötü niyetli isteklerin sistemi çökertmesini engeller.
- Kaynak adaleti: Tüm kullanıcıların API kaynaklarından eşit şekilde faydalanmasını sağlar.
- Maliyet kontrolü: Özellikle bulut tabanlı altyapılarda gereksiz kaynak tüketimini önler.
- Hizmet kalitesinin korunması: Yanıt sürelerinin kabul edilebilir düzeyde kalmasını garanti eder.
Temel Rate Limiting Algoritmaları
1. Fixed Window (Sabit Pencere)
En basit yöntemdir. Belirli bir zaman penceresi (örneğin 1 dakika) tanımlanır ve bu pencere içinde izin verilen maksimum istek sayısı belirlenir. Pencere sona erdiğinde sayaç sıfırlanır.
# Python ile basit Fixed Window implementasyonu
import time
from collections import defaultdict
class FixedWindowLimiter:
def __init__(self, max_requests, window_seconds):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.requests = defaultdict(list)
def is_allowed(self, client_id):
now = time.time()
window_start = now - self.window_seconds
# Pencere dışındaki istekleri temizle
self.requests[client_id] = [
t for t in self.requests[client_id] if t > window_start
]
if len(self.requests[client_id]) < self.max_requests:
self.requests[client_id].append(now)
return True
return False
limiter = FixedWindowLimiter(max_requests=100, window_seconds=60)
Dezavantajı: Pencere sınırında yoğunlaşma (burst) sorunu yaşanabilir. Örneğin, 59. saniyede 100 ve 61. saniyede 100 istek gelirse, 2 saniyelik dilimde 200 istek işlenmiş olur.
2. Sliding Window Log (Kayan Pencere Günlüğü)
Her isteğin zaman damgası kaydedilir ve mevcut zamandan geriye doğru pencere boyutu kadar bakılarak istek sayısı hesaplanır. Fixed window'daki burst sorununu çözer ancak bellek kullanımı daha yüksektir.
3. Sliding Window Counter (Kayan Pencere Sayacı)
Fixed window ve sliding window log yaklaşımlarının hibrit versiyonudur. Önceki ve mevcut pencerenin ağırlıklı ortalamasını alarak daha hassas bir hesaplama yapar. Bellek açısından verimlidir.
4. Token Bucket (Jeton Kovası)
En yaygın kullanılan algoritmalardan biridir. Sabit bir hızda jetonlar kovaya eklenir, her istek bir jeton harcar. Kova doluyken yeni jeton eklenmez, kova boşken istekler reddedilir. Burst trafiğe belirli ölçüde izin verirken uzun vadede ortalama hızı kontrol altında tutar.
# Token Bucket implementasyonu
import time
class TokenBucket:
def __init__(self, capacity, refill_rate):
self.capacity = capacity # Kova kapasitesi
self.refill_rate = refill_rate # Saniyede eklenen jeton
self.tokens = capacity
self.last_refill = time.time()
def _refill(self):
now = time.time()
elapsed = now - self.last_refill
new_tokens = elapsed * self.refill_rate
self.tokens = min(self.capacity, self.tokens + new_tokens)
self.last_refill = now
def consume(self, tokens=1):
self._refill()
if self.tokens >= tokens:
self.tokens -= tokens
return True
return False
# Saniyede 10 istek, burst için 50 kapasiteli kova
bucket = TokenBucket(capacity=50, refill_rate=10)
5. Leaky Bucket (Sızdıran Kova)
Token bucket'a benzer ancak istekleri sabit bir hızda işler. Gelen istekler bir kuyruğa eklenir ve kuyruktan sabit hızda çekilir. Kuyruk dolduğunda yeni istekler reddedilir. Çıkış trafiğinin düzgün olması gereken senaryolarda idealdir.
Dağıtık Sistemlerde Rate Limiting
Tek bir sunucu üzerinde rate limiting uygulamak görece basittir. Ancak birden fazla sunucu veya mikroservis mimarisi söz konusu olduğunda merkezi bir çözüm gerekir. Bu noktada Redis en popüler seçenektir.
# Redis ile dağıtık rate limiting (Sliding Window)
import redis
import time
r = redis.Redis(host='localhost', port=6379)
def is_rate_limited(client_id, max_requests=100, window=60):
key = f"rate_limit:{client_id}"
now = time.time()
pipe = r.pipeline()
pipe.zremrangebyscore(key, 0, now - window) # Eski kayıtları sil
pipe.zadd(key, {str(now): now}) # Yeni isteği ekle
pipe.zcard(key) # Toplam sayıyı al
pipe.expire(key, window) # TTL ayarla
results = pipe.execute()
return results[2] > max_requests
Redis'in MULTI/EXEC veya Lua scripting desteği sayesinde atomik işlemler yapılabilir. Bu, race condition sorunlarını ortadan kaldırır.
HTTP Başlıkları ile İstemci Bilgilendirme
İyi tasarlanmış bir rate limiting sistemi, istemcilere durumları hakkında bilgi verir. Standart HTTP başlıkları şunlardır:
X-RateLimit-Limit: Pencere başına izin verilen toplam istek sayısıX-RateLimit-Remaining: Kalan istek hakkıX-RateLimit-Reset: Sayacın sıfırlanacağı zaman (Unix timestamp)Retry-After: 429 yanıtında, istemcinin tekrar denemeden önce beklemesi gereken süre (saniye)
# Express.js middleware örneği
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711612800
Retry-After: 45
{
"error": "rate_limit_exceeded",
"message": "Istek limiti asildi. Lutfen 45 saniye sonra tekrar deneyin."
}
Throttling Stratejileri
Throttling, rate limiting'den farklı olarak istekleri tamamen reddetmek yerine hızlarını kontrol eder:
- Hard Throttling: Limit aşıldığında istek kesinlikle reddedilir.
- Soft Throttling: Belirli bir yüzdeye kadar (ör. %10) limit aşımına izin verilir.
- Elastic/Dynamic Throttling: Sunucu yüküne göre limitler dinamik olarak ayarlanır. Kaynak kullanımı düşükken daha fazla isteğe izin verilir.
Katmanlı Rate Limiting
Üretim ortamlarında tek bir limit yerine birden fazla katman uygulamak en iyi pratiktir:
- Global limit: Tüm API için saniyede maksimum 10.000 istek
- Kullanıcı bazlı limit: Her kullanıcı için dakikada 100 istek
- Endpoint bazlı limit: Kritik endpoint'ler için daha düşük limitler (ör.
/auth/loginiçin dakikada 5 istek) - IP bazlı limit: Aynı IP adresinden gelen istekleri sınırlama
API Gateway Seviyesinde Uygulama
Popüler API gateway çözümleri yerleşik rate limiting desteği sunar:
- NGINX:
limit_req_zonevelimit_reqdirektifleri ile leaky bucket algoritması uygular. - Kong: Rate Limiting eklentisi ile Redis destekli dağıtık limitler sunar.
- AWS API Gateway: Kullanım planları ve API anahtarları ile entegre throttling sağlar.
# NGINX rate limiting konfigürasyonu
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
}
İstemci Tarafı: Rate Limit ile Başa Çıkma
API tüketicisi olarak rate limit'lerle doğru şekilde başa çıkmak da önemlidir. Exponential backoff stratejisi en yaygın yaklaşımdır:
import time
import random
import requests
def api_call_with_backoff(url, max_retries=5):
for attempt in range(max_retries):
response = requests.get(url)
if response.status_code != 429:
return response
# Exponential backoff + jitter
wait = (2 ** attempt) + random.uniform(0, 1)
retry_after = response.headers.get('Retry-After')
if retry_after:
wait = max(wait, float(retry_after))
print(f"Rate limited. {wait:.1f}s bekleniyor...")
time.sleep(wait)
raise Exception("Maksimum deneme sayisi asildi")
Jitter (rastgele gecikme) eklemek kritik öneme sahiptir. Aksi halde tüm istemciler aynı anda tekrar deneyerek thundering herd sorununa yol açar.
Sonuç ve En İyi Pratikler
- Rate limiting stratejinizi API tasarımının başından itibaren planlayın; sonradan eklemeye çalışmak çok daha zordur.
- İstemcilere her zaman anlamlı HTTP başlıkları ve hata mesajları döndürün.
- Farklı kullanıcı tipleri için farklı limitler tanımlayın (free vs. premium planlar).
- Rate limit metriklerini izleyin ve uyarılar kurun. Sürekli limit aşımı yaşayan kullanıcılar, API tasarımında iyileştirme fırsatına işaret edebilir.
- Dağıtık ortamlarda Redis gibi merkezi bir depo kullanarak tutarlılığı sağlayın.
- Algoritma seçiminde token bucket çoğu senaryo için iyi bir başlangıç noktasıdır; burst trafiğe izin verirken ortalama hızı kontrol altında tutar.