Rate limiting, API''nizi DDoS''dan, yanlışlıkla oluşan sonsuz döngülerden ve paylaşılan kaynakları tek kullanıcının tüketmesinden korur. Ayrıca paid tier sistemlerde kullanım kotalarını enforcer eder. Bu yazı 4 ana algoritmayı ve Redis ile dağıtık implementasyonunu anlatır.
Fixed Window Counter
İlgili rehberler: REST API güvenlik · GraphQL vs REST · KEYDAL API geliştirme hizmetleri
En basit: her kullanıcı için dakika başı counter tut, limit''i aşınca reddet. Kolaydır ama boundary problemi var — minute sonunda burst yapan kullanıcı 2 dakikanın limitini eriştirebilir.
async function fixedWindow(userId, maxPerMinute = 60) {
const key = `rl:fixed:${userId}:${Math.floor(Date.now() / 60000)}`;
const count = await redis.incr(key);
if (count === 1) await redis.expire(key, 60);
return count <= maxPerMinute;
}
// Sorun: dakikanın 59. saniyesinde 60 istek, 00. saniyede 60 istek
// → 2 saniye içinde 120 istek geçer
Sliding Window Log
Her isteğin zaman damgasını bir sorted set''te tut; limit kontrolü son 60 saniyede kaç istek var. Tam doğru ama memory maliyeti yüksek (her istek için bir entry).
async function slidingLog(userId, maxPerMinute = 60) {
const key = `rl:log:${userId}`;
const now = Date.now();
const windowStart = now - 60000;
const pipe = redis.pipeline();
pipe.zremrangebyscore(key, 0, windowStart); // eski girişleri sil
pipe.zcard(key); // şu anki count
pipe.zadd(key, now, `${now}-${Math.random()}`);// yeni giriş
pipe.expire(key, 60);
const [, [, count]] = await pipe.exec();
return count < maxPerMinute;
}
Sliding Window Counter
Fixed window + interpolation. Geçen dakika + bu dakika''nın ağırlıklı ortalaması. Bellek verimli, boundary problemi yok — çoğu production sisteminde kullanılan algoritma.
async function slidingCounter(userId, maxPerMinute = 60) {
const now = Date.now();
const currentMin = Math.floor(now / 60000);
const prevMin = currentMin - 1;
const percentInCurrentMin = (now % 60000) / 60000;
const [curCount, prevCount] = await redis.mget(
`rl:sw:${userId}:${currentMin}`,
`rl:sw:${userId}:${prevMin}`
);
const weighted = (parseInt(prevCount) || 0) * (1 - percentInCurrentMin)
+ (parseInt(curCount) || 0);
if (weighted >= maxPerMinute) return false;
await redis.multi()
.incr(`rl:sw:${userId}:${currentMin}`)
.expire(`rl:sw:${userId}:${currentMin}`, 120)
.exec();
return true;
}
Token Bucket
Her kullanıcının bir "kovası" var, kapasite N. Saniyede R token eklenir. Her istek 1 token harcar. Kova boşsa reddedilir. Burst''e izin verir ama sürekli rate''e tolerans yok — AWS, Stripe gibi çoğu büyük API bunu kullanır.
-- Atomic token bucket (Redis Lua script)
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local rate = tonumber(ARGV[2]) -- tokens per second
local now = tonumber(ARGV[3])
local cost = tonumber(ARGV[4]) or 1
local bucket = redis.call('HMGET', key, 'tokens', 'last')
local tokens = tonumber(bucket[1]) or capacity
local last = tonumber(bucket[2]) or now
-- Token ekle (elapsed zamana göre)
local elapsed = math.max(0, now - last)
tokens = math.min(capacity, tokens + (elapsed * rate / 1000))
if tokens < cost then
redis.call('HSET', key, 'tokens', tokens, 'last', now)
redis.call('EXPIRE', key, 300)
return 0
end
tokens = tokens - cost
redis.call('HSET', key, 'tokens', tokens, 'last', now)
redis.call('EXPIRE', key, 300)
return 1
const LUA_SCRIPT = `...` // yukarıdaki
const sha = await redis.script('load', LUA_SCRIPT);
async function tokenBucket(userId, capacity = 10, rate = 1) {
const result = await redis.evalsha(sha, 1,
`rl:tb:${userId}`, capacity, rate, Date.now());
return result === 1;
}
Leaky Bucket
Token bucket''un kuzeni — istekler bir kuyruğa girer, belirli rate''te çıkar. Buffered istek kavramı olduğu için burst''u düzleştirir. Genelde traffic shaping için kullanılır.
Hangi Algoritmayı Seçmeli?
| Algoritma | Burst | Kesinlik | Memory | Use Case |
|---|---|---|---|---|
| Fixed Window | İzin verir | Düşük | O(1) | Basit sistem |
| Sliding Log | Hayır | Tam | O(n) | Az trafik |
| Sliding Counter | Sınırlı | Yüksek | O(1) | Genel amaçlı |
| Token Bucket | İzin verir | Yüksek | O(1) | Public API |
| Leaky Bucket | Hayır | Yüksek | O(1) | Traffic shaping |
Katmanlı Rate Limit
// Express middleware — çok katmanlı
const rateLimit = require('express-rate-limit');
// 1) Global — IP başına
app.use(rateLimit({ windowMs: 60000, max: 300 }));
// 2) Auth — brute force koruması
app.use('/login', rateLimit({ windowMs: 900000, max: 5 }));
// 3) Authenticated user — plan bazlı
app.use('/api', async (req, res, next) => {
if (!req.user) return next();
const limits = {
free: { rpm: 60, burst: 10 },
pro: { rpm: 300, burst: 50 },
ent: { rpm: 3000, burst: 500 }
};
const limit = limits[req.user.plan];
const ok = await tokenBucket(req.user.id, limit.burst, limit.rpm / 60);
if (!ok) return res.status(429).json({ error: 'Rate limit exceeded' });
next();
});
Response Headers
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1713398400
Retry-After: 30
# 429 response'unda:
HTTP/1.1 429 Too Many Requests
Retry-After: 15
{
"error": "rate_limit_exceeded",
"retryAfter": 15
}
Enterprise: Plan + Quota
Rate limit (saniyelik) + quota (aylık) ikilisi SaaS ürünlerin standart modeli. Rate limit Redis''te, quota PostgreSQL''de (aylık kümülatif counter) tutulur.
API Tasarım Prensipleri ve Güvenli Endpoint Mimarisi
Profesyonel API tasarımı dört unsuru bir araya getirir: doğru protokol seçimi (REST CRUD için ideal, GraphQL flexible queries için, gRPC microservice-to-microservice için), kimlik doğrulama (OAuth 2.0 / OIDC, JWT access token, refresh token rotation), rate limiting (token bucket, sliding window, IP/user/API key bazlı) ve versiyonlama (URL versiyon /v1/, header versiyon, deprecation sürecleri). API endpoint güvenliği için input validation, prepared statement, CORS politikası, idempotency-key (POST için), webhook signature verification ve OpenAPI/Swagger dokümantasyonu modern standartlardır. Yüksek trafikli API'lerde Redis ile rate limit + cache, Kafka veya RabbitMQ ile asenkron iş kuyruğu, OpenTelemetry ile dağıtık izleme önerilir.
Dağıtık rate limit, quota yönetimi ve DDoS koruması için iletişime geçin