システム設計:分散型レートリミッターの構築

はじめに
一般公開されているAPIには、レート制限が不可欠です。レート制限がなければ、システムはブルートフォース攻撃、DDoS攻撃、そして単一の攻撃的なクライアントが他のすべてのユーザーの体験を低下させる「ノイジーネイバー問題」に対して脆弱になります。
単一サーバー向けのレートリミッターの設計は比較的簡単です。しかし、毎秒数百万リクエストを処理するグローバルに分散されたマイクロサービスアーキテクチャ向けのものを設計することは、古典的なシステム設計の課題です。この記事では、必要とされるアーキテクチャとアルゴリズムを詳細に解説します。
1. 適切なアルゴリズムの選択
レートリミッターの心臓部は、リクエストをカウントするために使用するアルゴリズムです。
トークンバケット
- 仕組み: トークンを保持するバケットを想像してください。トークンは固定レートで追加されます。すべてのリクエストは1トークンを消費します。バケットが空の場合、リクエストは破棄されます。
- 長所: メモリ効率が良く、短期的なトラフィックのバーストを許容します。
- 短所: バーストサイズと持続的なレートの調整が難しい場合があります。
固定ウィンドウカウンター
- 仕組み: 時間を固定のウィンドウ(例:12:00から12:01)に分割します。リクエストごとにカウンターをインクリメントします。次のウィンドウの開始時にリセットされます。
- 長所: 実装が非常に簡単です。
- 短所: ウィンドウの境界でトラフィックが急増する可能性があります。クライアントは12:00:59に全クォータを送信し、12:01:01に再び送信することで、短時間で許容レートを実質的に2倍にすることができてしまいます。
スライディングウィンドウログ
- 仕組み: ユーザーごとのすべてのリクエストのタイムスタンプログを保持します。時間枠内でログサイズが制限を超えた場合、リクエストを破棄します。
- 長所: 非常に高い精度を誇ります。
- 短所: ログの維持と整理にかなりのメモリと計算リソースを消費します。大規模なスケールには不向きです。
スライディングウィンドウカウンター(最善策)
- 仕組み: ハイブリッドアプローチです。前の固定ウィンドウと現在の固定ウィンドウのカウンターを追跡し、現在の時刻がウィンドウと重なる部分に基づいて加重平均を計算します。
- 長所: 境界でのスパイクを平滑化し、メモリ効率が非常に高く、精度も高いです。
2. 分散システムのためのアーキテクチャ
マルチサーバー構成では、状態の維持が最大の課題となります。ロードバランサーがユーザーAの最初のリクエストをサーバー1に、2番目のリクエストをサーバー2にルーティングした場合、それらはどのようにしてレート制限カウンターを共有するのでしょうか?
選択肢A:スティッキーセッション
特定のユーザーのすべてのトラフィックを同じサーバーにルーティングします。サーバーはレート制限の状態をローカルメモリに保持します。
- 評価: 悪いアイデアです。負荷を不均等に分散させ、サーバーがクラッシュした際にグレースフルにフェイルオーバーしません。
選択肢B:中央集権型データストア (Redis)
すべてのサーバーが中央集権型のRedisクラスターに対して読み書きを行います。Redisは(インメモリであるため)レイテンシ要件を満たすのに十分高速です。
- 評価: 標準的なアプローチです。ただし、ネットワークレイテンシと競合状態が発生する可能性があります。
3. Redisにおける競合状態の処理
同じユーザーに対する2つのリクエストが同時に2つの異なるAPIサーバーに到達した場合、両方がカウンターを4として読み取り、両方が5にインクリメントし、両方がリクエストを許可してしまい、制限を回避する可能性があります。
解決策:Luaスクリプト
Redisでは、Luaスクリプトをアトミックに実行できます。カウンターを取得し、制限を確認し、インクリメントし、TTLを更新するという一連の操作を単一のアトミックな操作として実行するスクリプトを書くことができます。
-- Simple Token Bucket Lua Script
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then
return 0 -- Rejected
else
redis.call('INCRBY', key, 1)
redis.call('EXPIRE', key, 60)
return 1 -- Allowed
end
4. パフォーマンス最適化:ローカルキャッシュ + 非同期同期
Redisのレイテンシさえ許容できない超高スループット環境では、ハイブリッドアプローチを使用できます。
- APIサーバーはローカルに、インメモリのレートリミッターを保持します。
- 数秒ごとにローカルのカウントを中央のRedisクラスターに非同期で同期します。
- トレードオフ: これは厳密な精度をパフォーマンスと引き換えにします。クライアントは同期ウィンドウの間にわずかに制限を超える可能性がありますが、システムは高い可用性と速度を維持します。
結論
分散型レートリミッターの構築には、厳格な適用とシステムレイテンシのバランスを取る必要があります。ほとんどの現代的なAPIでは、アトミックなRedis Luaスクリプトを介して実装されたスライディングウィンドウアルゴリズムを組み合わせることで、精度、パフォーマンス、スケーラビリティの最良のバランスを提供します。
こちらもおすすめです
- How to Harden Firefox on Linux (Beginner's Guide)
- VPN vs Proxy: Differences, Fingerprinting, Leaks, and Privacy Testing
- The 5 Best Playwright Alternatives for E2E Testing in 2026
- Cypress to Playwright Migration Consulting: How to Upgrade Your Testing Strategy
よくある質問
ブラウザで動く無料の開発者ツール
AI CLIログの整形、Cron式の作成、JWTの解析、chmod権限の計算をオフラインで実行。
関連記事

本番環境のSQLite: WALモード、高並行性、そして実践的なPRAGMA設定
高スループットな本番環境でSQLiteをマスターしましょう。先行書き込みログ(WAL)、busy_timeoutのチューニング、読み書きの同時実行性、そして実用的なベンチマークについて解説します。
続きを読む
マイクロフロントエンドによるSystem Designの未来
Webpack Module Federation、single-spa、共有デザインシステム、独立したCI/CD、およびエッジSSRルーティングを使用して、スケーラブルなエンタープライズマイクロフロントエンドを構築します。
続きを読む
BigQuery + Cloud Run: 本番向けのサーバーレスデータ取込パイプライン構築
Google Cloud 上でサーバーレスなデータ取込を本番品質で構築する実践ガイド。BigQuery Storage Write API、パーティショニングとクラスタリングの設計、Cloud Run 上の非同期 FastAPI レシーバ、Terraform による IaC 全体、実測に基づくコスト分析、そして深夜3時に呼ばれる障害モードまで扱います。
続きを読む