API レート制限&スロットリングエキスパート は何をしますか?
トークンバケット、スライディングウィンドウ、固定ウィンドウアルゴリズムと、Redis でバックアップされた分散カウンターを使用して、公平で効率的なレート制限を実装します。
アクティベーショントリガー
アクティブ化条件: 「レート制限」、「スロットリング」、「トークンバケット」、「スライディングウィンドウ」、「API 悪用」、「DDoS 保護」、「クォータ管理」、「429 Too Many Requests」、「リクエスト制限」
対象外: API ゲートウェイ設定 → api-gateway-reverse-proxy-expert | キャッシュ戦略 → cache-strategy-invalidation-expert | WAF/ファイアウォールルール → 関連するセキュリティスキル
クイックスタート
- 制限を定義 — API キー、IP、ユーザーティアごとのリクエスト/分
- アルゴリズムを選択 — スライディングウィンドウログ (高精度)、トークンバケット (バースト対応)、固定ウィンドウ (シンプル)
- Redis を使用 —
MULTI/EXECまたは Lua スクリプトによるアトミック操作で分散レート制限を実現 - 適切なヘッダーを返す —
X-RateLimit-Limit、X-RateLimit-Remaining、Retry-After - ティアを区別 — フリー/プロ/エンタープライズで異なる制限を設定
コア機能
| ドメイン | テクノロジー |
|---|---|
| アルゴリズム | トークンバケット、スライディングウィンドウログ、スライディングウィンドウカウンター、固定ウィンドウ |
| ストレージ | Redis 7.4+、Valkey、DragonflyDB、インメモリ (単一ノード) |
| ライブラリ | rate-limiter-flexible、@upstash/ratelimit、express-rate-limit |
| ゲートウェイプラグイン | Kong rate-limiting、Nginx limit_req、Traefik ratelimit |
| 標準仕様 | RFC 6585 (429)、RateLimit ヘッダー (draft-ietf-httpapi-ratelimit) |
アーキテクチャパターン
スライディングウィンドウカウンター (Redis Lua)
-- Redis Lua スクリプト: スライディングウィンドウレート制限
-- KEYS[1] = レート制限キー
-- ARGV[1] = ウィンドウサイズ (秒)
-- ARGV[2] = 最大リクエスト数
-- ARGV[3] = 現在のタイムスタンプ
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local now = 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 .. '-' .. math.random(1000000))
redis.call('EXPIRE', key, window)
return {1, limit - count - 1} -- 許可、残り
else
return {0, 0} -- 拒否、残り0
end
マルチティアレート制限
リクエスト → IP レート制限 (100/分)
│ 通過
↓
認証チェック → API キーレート制限 (ティアベース)
│ フリー: 60/分
│ プロ: 600/分
│ エンタープライズ: 6000/分
↓ 通過
エンドポイントレート制限 (ルートごと)
│ POST /upload: 10/分
│ GET /search: 120/分
↓ 通過
リクエストを処理
レスポンスヘッダー (IETF ドラフト標準)
// ミドルウェア: レート制限ヘッダーを付加
function rateLimitHeaders(limit: number, remaining: number, resetAt: number) {
return {
'RateLimit-Limit': limit.toString(),
'RateLimit-Remaining': Math.max(0, remaining).toString(),
'RateLimit-Reset': Math.ceil((resetAt - Date.now()) / 1000).toString(),
};
}
// 429 レスポンス時:
res.status(429).set({
...rateLimitHeaders(limit, 0, resetAt),
'Retry-After': Math.ceil((resetAt - Date.now()) / 1000).toString(),
}).json({ error: 'Too Many Requests', retryAfter: resetAt });
アンチパターン
- 分散システムでのインメモリカウンター — レート制限はインスタンス間で共有される必要があります。Redis または同等のものを使用してください
- Retry-After ヘッダーの欠如 —
Retry-Afterなしの 429 レスポンスはクライアントに推測を強いるため、シグナル過多 (thundering herd) を引き起こします - IP のみによるレート制限 — IP 制限は認証済みの悪用を見逃し、共有 IP (NAT、VPN) を罰します。API キー制限と組み合わせてください
- バーストなしのハードカットオフ — トークンバケットは平均レートを維持しながら小さなバーストを許可します。純粋な固定ウィンドウは硬直的すぎます
- 内部 API にレート制限がない — 動作が不安定な内部サービスがデータベースにカスケード障害を引き起こす可能性があります。常に制限してください
品質チェックリスト
- レート制限は分散ストレージ (Redis/Valkey) を使用しており、インメモリではない
- すべてのレスポンスに
RateLimit-Limit、RateLimit-Remaining、RateLimit-Resetヘッダーがある - 429 レスポンスに
Retry-Afterヘッダーがある - 認証ティア (フリー/プロ/エンタープライズ) ごとに異なる制限がある
- 高コストな操作 (アップロード、検索) に対してエンドポイントごとの制限がある
- カウンターインクリメントに Lua スクリプトまたはアトミックパイプラインを使用している (競合状態なし)
- レート制限キーに ID (API キー) とスコープ (エンドポイント) の両方を含めている
- モニタリングダッシュボードでレート制限のヒット、拒否、上位利用者が表示される
- グレースフルデグラデーション: 可能な場合はハードリジェクトではなく、キューイングまたは速度低下を行う