API における Rate Limiting:不正利用に対する保護
Rate limiting は、API を不正利用、過剰なリソース消費、サービス拒否攻撃から保護するための重要な技術です。適切な実装により、悪意のある行動や過剰な行動を遮断しながら、正当なユーザーへの可用性を確保します。
なぜ Rate Limiting を実装するのか?
- DDoS に対する保護:サービス拒否攻撃を緩和する
- Scraping の防止:自動化されたデータ抽出を困難にする
- コストの管理:計算リソースの過剰な消費を回避する
- サービス品質の確保:リソースを公平に分配する
- Brute Force の防止:認証試行を制限する
Rate Limiting アルゴリズム
1. Token Bucket
時間の経過とともに補充されるトークンの「バケツ」を維持するアルゴリズム:
class TokenBucket {
constructor(capacity, refillRate) {
this.capacity = capacity; // Capacidade máxima do balde
this.tokens = capacity; // Tokens disponíveis
this.refillRate = refillRate; // Tokens por segundo
this.lastRefill = Date.now();
}
tryConsume(tokens = 1) {
this.refill();
if (this.tokens >= tokens) {
this.tokens -= tokens;
return true; // Requisição permitida
}
return false; // Rate limit excedido
}
refill() {
const now = Date.now();
const timePassed = (now - this.lastRefill) / 1000;
const tokensToAdd = timePassed * this.refillRate;
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
this.lastRefill = now;
}
}
// Uso: 100 requisições máximo, recarrega 10/segundo
const bucket = new TokenBucket(100, 10);
利点:制御されたバーストを許可し、トラフィックを平滑化する
欠点:実装がより複雑である
2. Leaky Bucket
バケツから水が漏れるように、一定のレートでリクエストを処理します:
- リクエストがバケツに入る
- 固定レートで処理される
- 超過分はあふれる(拒否される)
- 均一な出力を保証する
3. Fixed Window
固定の時間ウィンドウ内でリクエストをカウントします:
class FixedWindowRateLimiter {
constructor(maxRequests, windowMs) {
this.maxRequests = maxRequests;
this.windowMs = windowMs;
this.requests = new Map();
}
isAllowed(userId) {
const now = Date.now();
const windowStart = Math.floor(now / this.windowMs) * this.windowMs;
const key = \`\$:\$\`;
const count = this.requests.get(key) || 0;
if (count < this.maxRequests) {
this.requests.set(key, count + 1);
return true;
}
return false;
}
}
// 100 requisições por hora
const limiter = new FixedWindowRateLimiter(100, 60 * 60 * 1000);
問題:ウィンドウの境界で制限の最大 2 倍を許可する
4. Sliding Window Log
リクエストのタイムスタンプのログを維持します:
- 各リクエストのタイムスタンプを保存する
- ウィンドウ外のリクエストを削除する
- Fixed Window よりも正確である
- メモリ消費量がより多い
5. Sliding Window Counter
Fixed Window と平滑化を組み合わせます:
// Calcula uma média ponderada entre janelas atual e anterior
const currentWindowCount = getCurrentWindowCount(userId);
const previousWindowCount = getPreviousWindowCount(userId);
const percentageInCurrentWindow = (now - currentWindowStart) / windowSize;
const estimatedCount =
previousWindowCount * (1 - percentageInCurrentWindow) +
currentWindowCount;
return estimatedCount < maxRequests;
実践的な実装
Redis を使用(本番環境推奨)
import Redis from 'ioredis';
const redis = new Redis();
async function checkRateLimit(userId, maxRequests = 100, windowSeconds = 60) {
const key = \`rate_limit:\$\`;
const now = Date.now();
const windowStart = now - (windowSeconds * 1000);
// Remover requisições antigas
await redis.zremrangebyscore(key, 0, windowStart);
// Contar requisições na janela
const requestCount = await redis.zcard(key);
if (requestCount < maxRequests) {
// Adicionar nova requisição
await redis.zadd(key, now, \`\$-\${Math.random()}\`);
await redis.expire(key, windowSeconds);
return { allowed: true, remaining: maxRequests - requestCount - 1 };
}
return { allowed: false, remaining: 0 };
}
// Middleware Express
app.use(async (req, res, next) => {
const userId = req.user?.id || req.ip;
const result = await checkRateLimit(userId);
res.set({
'X-RateLimit-Limit': 100,
'X-RateLimit-Remaining': result.remaining,
'X-RateLimit-Reset': new Date(Date.now() + 60000).toISOString()
});
if (!result.allowed) {
return res.status(429).json({
error: 'Too Many Requests',
retryAfter: 60
});
}
next();
});
人気のライブラリ
- express-rate-limit:Express.js 用の Middleware
- rate-limiter-flexible:複数の backends に対応(Redis、Memcached、MySQL)
- Kong Rate Limiting:API Gateway 用の Plugin
- AWS API Gateway:ネイティブの rate limiting
高度な戦略
階層型 Rate Limiting
- グローバル:API の総制限(例:1M req/min)
- ユーザーごと:個別の制限(例:1000 req/min)
- Endpoint ごと:特定の制限(login:5 req/min)
- IP ごと:不正利用に対する追加の保護
動的 Rate Limiting
- システム負荷に基づいて制限を調整する
- プレミアムユーザー向けに制限を引き上げる
- インシデント発生時に制限を引き下げる
Whitelisting と Blacklisting
- 信頼できる IP/ユーザーを除外する
- 既知の攻撃者を永続的にブロックする
- レピュテーションシステムを実装する
ベストプラクティス
- 情報を含むヘッダーを返す(X-RateLimit-*)
- HTTP ステータス 429(Too Many Requests)を使用する
- Retry-After ヘッダーを含める
- API で制限を明確に文書化する
- クライアントで指数バックオフを実装する
- rate limiting のメトリクスを監視する
- 異常なパターンについてアラートを出す
- 本番環境の前に制限をテストする
監視ツール
- Grafana + Prometheus:rate limiting メトリクスを可視化する
- Datadog:監視とアラート
- CloudWatch:AWS 上の API 向け
- New Relic:rate limiting に対応した APM
