API Hız Sınırlaması: Kalıplar ve En İyi Uygulamalar
Her genel API uç noktası bir hedeftir; botlar, kazıyıcılar ve kötü aktörler, yayına girdiğiniz anda sunucunuza zarar verecektir. Hız sınırlaması olmaksızın, hatalı davranan tek bir istemci, veritabanı bağlantılarınızı tüketebilir, bulut faturanızı artırabilir ve her meşru kullanıcı için hizmeti devre dışı bırakabilir. Hız sınırlaması isteğe bağlı değildir; herhangi bir üretim API'sinin ilk savunma hattıdır.
Bu kılavuz dört ana hız sınırlayıcı algoritmayı, bunların değiş tokuşlarını ve bunların Redis ile NestJS'de doğru şekilde nasıl uygulanacağını açıklamaktadır. Ortak senaryolar için kopyala-yapıştır yapılandırmaları ve uç nokta başına doğru stratejiyi seçmeye yönelik zihinsel bir modelle ayrılacaksınız.
Önemli Çıkarımlar
- Jeton kovası kontrollü patlamaya izin verirken, sabit pencere sayaçları uygulaması en ucuz olanlardır
- Kayan pencere günlüğü en doğru olanıdır ancak yoğun bellek harcar; sürgülü pencere sayacı en iyi dengedir
- Birden fazla API replikası çalıştırdığınızda Redis tek doğru yedekleme deposudur
- NestJS
@nestjs/throttlerözel depolama adaptörlerini destekler — tek bir yapılandırma değişikliğiyle Redis'te takas yapın- Her zaman
Retry-After,X-RateLimit-Limit,X-RateLimit-RemainingveX-RateLimit-Resetbaşlıklarını döndür- Sınırları uç nokta hassasiyetine göre farklılaştırın: kimlik doğrulama (5/dak) ve okuma API'leri (1000/dak)
- Anonim trafik için IP tabanlı sınırları ve kimliği doğrulanmış istekler için kullanıcı tabanlı sınırları kullanın
- İstekleri asla sessizce bırakmayın — her zaman yararlı bir mesajla
429 Too Many Requestsdeğerini döndürün
Dört Çekirdekli Algoritma
Sabit Pencere Sayacı
En basit yaklaşım: istekleri sabit bir zaman penceresinde sayın, sınırda sıfırlayın.
// Fixed window: 100 requests per minute
// Window resets at :00, :01, :02 ...
const windowKey = `ratelimit:${userId}:${Math.floor(Date.now() / 60000)}`;
const count = await redis.incr(windowKey);
await redis.expire(windowKey, 60);
if (count > 100) {
throw new TooManyRequestsException();
}
Zayıflık: Sınır istismarı. Bir müşteri 100 isteği 12:00:59'da ve 100 isteği 12:01:00'de gönderebilir; yani iki saniyede 200 istek. Çoğu API için bu kabul edilebilir. Kimlik doğrulama uç noktaları için bu geçerli değildir.
Kayan Pencere Günlüğü
Her istek zaman damgasını saklayın. Her istekte son penceredeki zaman damgalarını sayın.
const now = Date.now();
const windowStart = now - 60_000; // 60 seconds ago
// Remove old entries, add current
await redis.zremrangebyscore(key, 0, windowStart);
await redis.zadd(key, now, now.toString());
const count = await redis.zcard(key);
await redis.expire(key, 60);
if (count > 100) {
throw new TooManyRequestsException();
}
Değiştirme: Tamamen doğrudur ancak kullanıcı başına O(n) girişi saklar; burada n, istek sayısıdır. 10.000 kullanıcıda 1.000 RPS ile Redis belleğiniz hızla artıyor. Parola sıfırlama gibi düşük hacimli, yüksek güvenlikli uç noktalar için kullanın.
Sürgülü Pencere Sayacı
İki sabit pencere kullanan yaklaşık kayan pencere — bellek patlaması yok.
const now = Date.now();
const currentWindow = Math.floor(now / 60000);
const previousWindow = currentWindow - 1;
const windowProgress = (now % 60000) / 60000; // 0.0 to 1.0
const [current, previous] = await redis.mget(
`rl:${userId}:${currentWindow}`,
`rl:${userId}:${previousWindow}`
);
const estimated =
(parseInt(previous ?? '0') * (1 - windowProgress)) +
parseInt(current ?? '0');
if (estimated >= 100) {
throw new TooManyRequestsException();
}
await redis.incr(`rl:${userId}:${currentWindow}`);
await redis.expire(`rl:${userId}:${currentWindow}`, 120);
Bu Cloudflare'in kullandığı algoritmadır. Pencere başına kullanıcı başına iki Redis anahtarıyla sınır artışını minimum düzeyde ek yük ile yumuşatır.
Jeton Kovası
Uzun vadeli bir oranı korurken patlamalara izin veren altın standart. Her kullanıcının sabit bir oranda dolan bir kovası vardır. İstekler jeton tüketir.
async function consumeToken(
redis: Redis,
userId: string,
ratePerSec: number,
capacity: number
): Promise<boolean> {
const now = Date.now() / 1000;
const key = `bucket:${userId}`;
const values = await redis.hmget(key, 'tokens', 'lastRefill');
const currentTokens = parseFloat(values[0] ?? String(capacity));
const lastRefillTime = parseFloat(values[1] ?? String(now));
// Refill tokens based on elapsed time
const elapsed = now - lastRefillTime;
const refilled = Math.min(capacity, currentTokens + elapsed * ratePerSec);
if (refilled < 1) {
return false; // No tokens available
}
await redis.hset(key, 'tokens', String(refilled - 1), 'lastRefill', String(now));
await redis.expire(key, Math.ceil(capacity / ratePerSec) + 60);
return true;
}
Token kovası, sürekli kötüye kullanımı önlerken kısa aralıklarla (aynı anda 10 dosya yükleme) izin vermesi gereken API'ler için idealdir.
NestJS Kısıtlayıcı Yapılandırması
@nestjs/throttler v5, Redis depolama bağdaştırıcısıyla birlikte gelir. İşte üretime hazır bir kurulum:
pnpm add @nestjs/throttler @nestjs/throttler-storage-redis ioredis
// app.module.ts
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { ThrottlerStorageRedisService } from '@nestjs/throttler-storage-redis';
import { APP_GUARD } from '@nestjs/core';
@Module({
imports: [
ThrottlerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
throttlers: [
{ name: 'short', ttl: 1000, limit: 5 }, // 5 req/sec burst
{ name: 'medium', ttl: 60000, limit: 300 }, // 300 req/min
{ name: 'long', ttl: 3600000, limit: 5000 }, // 5000 req/hr
],
storage: new ThrottlerStorageRedisService({
host: config.get('REDIS_HOST'),
port: config.get('REDIS_PORT'),
}),
}),
}),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}
Denetleyici veya rota başına geçersiz kılma sınırları:
@Controller('auth')
export class AuthController {
// Authentication: very strict — 5 attempts per minute
@Post('login')
@Throttle({ medium: { ttl: 60000, limit: 5 } })
async login(@Body() dto: LoginDto) { /* ... */ }
// Refresh: moderate — 30 per minute
@Post('refresh')
@Throttle({ medium: { ttl: 60000, limit: 30 } })
async refresh(@Body() dto: RefreshDto) { /* ... */ }
// Skip throttling on the exchange endpoint (protected by one-time code TTL)
@Post('exchange')
@SkipThrottle()
async exchange(@Body() dto: ExchangeDto) { /* ... */ }
}
Özel Anahtar Oluşturucular
NestJS kısıtlayıcı varsayılan olarak istemci IP'sini kullanır. Nginx/Cloudflare'in arkasındaki üretimde X-Real-IP veya CF-Connecting-IP'ye ihtiyacınız var.
// throttler-behind-proxy.guard.ts
import { ThrottlerGuard } from '@nestjs/throttler';
import { Injectable, ExecutionContext } from '@nestjs/common';
@Injectable()
export class ThrottlerBehindProxyGuard extends ThrottlerGuard {
protected async getTracker(req: Record<string, any>): Promise<string> {
// Authenticated user — use userId for accurate per-user limits
if (req.user?.sub) {
return `user:${req.user.sub}`;
}
// Anonymous — use real IP from Cloudflare header
return (
req.headers['cf-connecting-ip'] ||
req.headers['x-real-ip'] ||
(req.headers['x-forwarded-for'] as string)?.split(',')[0] ||
req.ip
);
}
protected async throwThrottlingException(
context: ExecutionContext,
throttlerLimitDetail: ThrottlerLimitDetail
): Promise<void> {
const response = context.switchToHttp().getResponse();
response.header(
'Retry-After',
Math.ceil(throttlerLimitDetail.ttl / 1000)
);
await super.throwThrottlingException(context, throttlerLimitDetail);
}
}
Yanıt Başlıkları
RFC 6585 ve taslak RateLimit başlıkları, müşterilere tam olarak ne zaman yeniden denemeleri gerektiğini söyler:
// rate-limit.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class RateLimitHeadersInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
tap(() => {
const res = context.switchToHttp().getResponse();
const req = context.switchToHttp().getRequest();
// Values injected by ThrottlerGuard after evaluation
if (req.rateLimit) {
res.set({
'X-RateLimit-Limit': req.rateLimit.limit,
'X-RateLimit-Remaining': Math.max(
0,
req.rateLimit.limit - req.rateLimit.current
),
'X-RateLimit-Reset': new Date(
Date.now() + req.rateLimit.ttl
).toISOString(),
'RateLimit-Policy': `${req.rateLimit.limit};w=${Math.ceil(
req.rateLimit.ttl / 1000
)}`,
});
}
})
);
}
}
Uç Noktaya Özel Stratejiler
Farklı uç noktalar farklı limitleri garanti eder. Yaygın modeller için bir referans tablosu aşağıda verilmiştir:
| Uç Nokta Türü | Algoritma | Sınırı | Pencere |
|---|---|---|---|
| Giriş / şifre sıfırlama | Sürgülü pencere günlüğü | 5 | 15 dakika |
| OTP / 2FA doğrulaması | Sabit pencere | 3 | 10 dakika |
| Herkese açık okuma API'si | Jeton kovası | 1000 patlama, 100/s dolum | — |
| Mutasyon API'si (kimliği doğrulanmış) | Sürgülü pencere sayacı | 300 | 1 dakika |
| Web kancası alımı | Sabit pencere | 10.000 | 1 dakika |
| Dosya yükleme | Jeton kovası | 10 patlama, 1/s dolum | — |
| AI / LLM uç noktaları | Sabit pencere | 20 | 1 dakika |
| Ara (anonim) | Sabit pencere | 30 | 1 dakika |
Dağıtılmış Güvenlik için Atomik Lua Komut Dosyaları
Birden fazla API kopyanız olduğunda, artış kontrolü dizilerindeki yarış koşulları, sınırın üzerinde artışlara izin verebilir. Atomik kontrol ve artış işlemini gerçekleştirmek için redis.defineCommand yoluyla yüklenen bir Lua betiğini kullanın:
// rate-limit.service.ts
import { Injectable } from '@nestjs/common';
import Redis from 'ioredis';
@Injectable()
export class RateLimitService {
constructor(private readonly redis: Redis) {
// Define atomic increment+check as a custom Redis command
this.redis.defineCommand('rateLimitCheck', {
numberOfKeys: 1,
lua: `
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local ttlMs = tonumber(ARGV[2])
local count = redis.call('INCR', key)
if count == 1 then
redis.call('PEXPIRE', key, ttlMs)
end
if count > limit then
return {0, redis.call('PTTL', key)}
end
return {1, -1}
`,
});
}
async isAllowed(
key: string,
limit: number,
windowMs: number
): Promise<{ allowed: boolean; retryAfterMs: number }> {
const result = await (this.redis as any).rateLimitCheck(
key, limit, windowMs
) as [number, number];
return {
allowed: result[0] === 1,
retryAfterMs: result[1] > 0 ? result[1] : 0,
};
}
}
Zarif Bozulma ve Baypas Stratejileri
Hız sınırlaması dahili durum kontrollerini, izleme aracılarını veya güvenilir iş ortaklarını engellememelidir.
// trusted-bypass.guard.ts
@Injectable()
export class RateLimitWithBypassGuard extends ThrottlerBehindProxyGuard {
private readonly trustedTokens = new Set([
process.env.MONITORING_TOKEN,
process.env.PARTNER_API_TOKEN,
].filter(Boolean));
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
// Internal health checks bypass all rate limits
if (req.path === '/health' || req.path === '/ready') {
return true;
}
// Trusted API tokens bypass
const token = req.headers['x-bypass-token'];
if (token && this.trustedTokens.has(token)) {
return true;
}
return super.canActivate(context);
}
}
Aşamalı hız sınırlaması için (sert bloklamadan önce uyar), yalnızca sınırın %80'ini geçtikten sonra 429 başlığını Retry-After başlığıyla döndürün:
// In your custom guard, after counting requests:
if (count > limit * 0.9 && count <= limit) {
response.set('X-RateLimit-Warning', 'approaching limit');
}
if (count > limit) {
response.set('Retry-After', retryAfterSeconds.toString());
throw new HttpException('Rate limit exceeded', 429);
}
Test Hızı Sınırlaması
// rate-limit.spec.ts
describe('Rate Limiting', () => {
it('should block after limit exceeded', async () => {
const app = moduleRef.createNestApplication();
await app.init();
// Hit the endpoint 5 times (limit for login)
for (let i = 0; i < 5; i++) {
await request(app.getHttpServer())
.post('/auth/login')
.send({ email: '[email protected]', password: 'wrongpassword' })
.expect((res) => expect(res.status).toBeLessThan(429));
}
// 6th request should be blocked
const response = await request(app.getHttpServer())
.post('/auth/login')
.send({ email: '[email protected]', password: 'wrongpassword' });
expect(response.status).toBe(429);
expect(response.headers['retry-after']).toBeDefined();
expect(response.body.message).toContain('rate limit');
});
});
İzleme ve Uyarı
Hız sınırı olayları değerli sinyallerdir. Bunları gözlemlenebilirlik platformunuza kaydedin:
@Injectable()
export class RateLimitMetricsService {
async recordRateLimitHit(userId: string, endpoint: string, ip: string) {
await this.metricsService.increment('rate_limit.hits', {
endpoint,
user_type: userId ? 'authenticated' : 'anonymous',
});
// Alert on sustained attacks (>100 hits in 1 min from same IP)
const alertKey = `rl_alert:${ip}`;
const recentHits = await this.redis.incr(alertKey);
if (recentHits === 1) {
await this.redis.expire(alertKey, 60);
}
if (recentHits === 100) {
await this.alertService.send({
severity: 'high',
message: `Rate limit attack detected from IP ${ip}`,
endpoint,
});
}
}
}
Kontrol panelleri takibi oluşturun:
- Uç nokta başına oran sınırı isabetleri (p95, p99)
- Sınırlara ulaşan en iyi IP'ler/kullanıcılar
- Engellenen ve sunulan isteklerin yüzdesi
- Yanlış yapılandırılmış istemcileri tespit etmek için tekrar deneme süreleri
Sıkça Sorulan Sorular
Sınırı IP'ye göre mi yoksa kullanıcı kimliğine göre mi derecelendirmeliyim?
Her ikisini de kullanın. Kimliği doğrulanmamış uç noktalar için mevcut tek tanımlayıcı IP'dir. Kimliği doğrulanmış uç noktalar için her zaman kullanıcı kimliğini tercih edin; bu daha doğrudur ve paylaşılan bir IP'nin (kurumsal NAT gibi) tüm çalışanları engellemesini önler. İki aşamalı bir kontrol uygulayın: Nginx düzeyinde IP sınırı ve uygulama düzeyinde kullanıcı kimliği sınırı.
Hız sınırlaması için doğru HTTP durum kodu nedir?
RFC 6585'e göre her zaman 429 Too Many Requests. Asla 503 Service Unavailable (altyapı arızasını belirtir) veya 403 Forbidden (yetkilendirme arızasını belirtir) kullanmayın. İstemcilerin ne zaman yeniden deneyeceklerini bilmeleri için Retry-After'ü saniyeler içinde başlık olarak ekleyin.
Cloudflare veya yük dengeleyicinin arkasındaki hız sınırlamasını nasıl hallederim?
Proxy'nizi X-Real-IP veya CF-Connecting-IP ayarlayacak şekilde yapılandırın ve yalnızca proxy'nizin IP aralığına güvenin. Nginx'te: set_real_ip_from 103.21.244.0/22; real_ip_header CF-Connecting-IP;. NestJS'de app.set('trust proxy', 1) değerini ayarlayın ve NestJS'nin güvenilir proxy başlığından çözdüğü req.ip değerini okuyun.
Hız sınırlaması için en iyi Redis veri yapısı hangisidir?
Sabit/kayan pencere sayaçları için, istek başına O(1) dize anahtarında INCR + EXPIRE kullanın. Kayan pencere günlüğü için sıralanmış bir küme kullanın (ZADD, ZREMRANGEBYSCORE, ZCARD) — O(log n). Belirteç kümesi için bir karma kullanın (HSET, HGET) — O(1). Lua komut dosyaları, tüm işlemleri üç modelin tamamında atomik hale getirir.
Güvenilir sağlayıcıların web kancalarına ilişkin hız sınırlarını nasıl yönetmeliyim?
Stripe, GitHub ve benzeri sağlayıcılar bilinen IP aralıklarından web kancaları gönderir. Webhook besleme uç noktanızda CIDR aralıklarının izin verilenler listesini tutun ve bu IP'ler için hız sınırlamasını atlayın. Önce webhook imzasını doğrulayın; imza doğrulaması, hız sınırlaması değil, oradaki gerçek güvenlik katmanınızdır.
Bunun yerine Nginx düzeyinde hız sınırlaması uygulayabilir miyim?
Evet ve temel DDoS koruması için bunu yapmalısınız. Kaba IP tabanlı sınırlar (1000 istek/dak) için Nginx'te limit_req_zone kullanın. Kullanıcı başına, uç nokta başına ayrıntılı kontrol için uygulama düzeyinde hız sınırlaması katmanı. İki katman birbirini tamamlar: Nginx, uygulamanıza zarar vermeden toplu saldırıları ucuz bir şekilde yönetir ve NestJS, incelikli iş mantığı sınırlarını yönetir.
Sonraki Adımlar
Sağlam hız sınırlaması olmadan bir üretim API'si oluşturmak, ön kapınızın kilidini açık bırakmak gibidir. Bu kılavuzdaki modeller - yoğun trafik için belirteç kümesi, sorunsuz uygulama için kayan pencere sayacı, Redis destekli dağıtılmış depolama ve uygun 429 yanıtları - güvenli, ölçeklenebilir bir API'nin omurgasını oluşturur.
ECOSIRE, ilk günden itibaren hız sınırlama, Redis önbelleğe alma ve tam gözlemlenebilirlik özelliklerine sahip kurumsal düzeyde NestJS arka uçları oluşturur. Yeni bir API başlatıyorsanız veya mevcut olanı sağlamlaştırıyorsanız, dağıtımınızı nasıl hızlandırabileceğimizi görmek için arka uç mühendislik hizmetlerimizi keşfedin.
Yazan
ECOSIRE Research and Development Team
ECOSIRE'da kurumsal düzeyde dijital ürünler geliştiriyor. Odoo entegrasyonları, e-ticaret otomasyonu ve yapay zeka destekli iş çözümleri hakkında içgörüler paylaşıyor.
İlgili Makaleler
Cybersecurity Trends 2026-2027: Zero Trust, AI Threats, and Defense
The definitive guide to cybersecurity trends for 2026-2027—AI-powered attacks, zero trust implementation, supply chain security, and building resilient security programs.
Financial Services ERP Implementation: Regulatory and Security Requirements
A practitioner's guide to implementing ERP in regulated financial services firms, covering security controls, compliance validation, data governance, and phased rollout.
Government ERP Implementation: Security Clearance and Compliance
A comprehensive guide to implementing ERP in government agencies, covering FedRAMP compliance, security clearance requirements, change management, and phased deployment.