•10分で読める

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

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

はじめに

Audio Briefing
0:00 / --:--

一般公開されているAPIには、レート制限が不可欠です。レート制限がなければ、システムはブルートフォース攻撃、DDoS攻撃、そして単一の攻撃的なクライアントが他のすべてのユーザーの体験を低下させる「ノイジーネイバー問題」に対して脆弱になります。

単一サーバー向けのレートリミッターの設計は比較的簡単です。しかし、毎秒数百万リクエストを処理するグローバルに分散されたマイクロサービスアーキテクチャ向けのものを設計することは、古典的なシステム設計の課題です。この記事では、必要とされるアーキテクチャとアルゴリズムを詳細に解説します。

1. 適切なアルゴリズムの選択

レートリミッターの心臓部は、リクエストをカウントするために使用するアルゴリズムです。

トークンバケット

  • 仕組み: トークンを保持するバケットを想像してください。トークンは固定レートで追加されます。すべてのリクエストは1トークンを消費します。バケットが空の場合、リクエストは破棄されます。
  • 長所: メモリ効率が良く、短期的なトラフィックのバーストを許容します。
  • 短所: バーストサイズと持続的なレートの調整が難しい場合があります。

固定ウィンドウカウンター

  • 仕組み: 時間を固定のウィンドウ(例:12:00から12:01)に分割します。リクエストごとにカウンターをインクリメントします。次のウィンドウの開始時にリセットされます。
  • 長所: 実装が非常に簡単です。
  • 短所: ウィンドウの境界でトラフィックが急増する可能性があります。クライアントは12:00:59に全クォータを送信し、12:01:01に再び送信することで、短時間で許容レートを実質的に2倍にすることができてしまいます。

スライディングウィンドウログ

  • 仕組み: ユーザーごとのすべてのリクエストのタイムスタンプログを保持します。時間枠内でログサイズが制限を超えた場合、リクエストを破棄します。
  • 長所: 非常に高い精度を誇ります。
  • 短所: ログの維持と整理にかなりのメモリと計算リソースを消費します。大規模なスケールには不向きです。

スライディングウィンドウカウンター(最善策)

  • 仕組み: ハイブリッドアプローチです。前の固定ウィンドウと現在の固定ウィンドウのカウンターを追跡し、現在の時刻がウィンドウと重なる部分に基づいて加重平均を計算します。
  • 長所: 境界でのスパイクを平滑化し、メモリ効率が非常に高く、精度も高いです。
Advertisement

2. 分散システムのためのアーキテクチャ

マルチサーバー構成では、状態の維持が最大の課題となります。ロードバランサーがユーザーAの最初のリクエストをサーバー1に、2番目のリクエストをサーバー2にルーティングした場合、それらはどのようにしてレート制限カウンターを共有するのでしょうか?

選択肢A:スティッキーセッション

特定のユーザーのすべてのトラフィックを同じサーバーにルーティングします。サーバーはレート制限の状態をローカルメモリに保持します。

  • 評価: 悪いアイデアです。負荷を不均等に分散させ、サーバーがクラッシュした際にグレースフルにフェイルオーバーしません。

選択肢B:中央集権型データストア (Redis)

すべてのサーバーが中央集権型のRedisクラスターに対して読み書きを行います。Redisは(インメモリであるため)レイテンシ要件を満たすのに十分高速です。

  • 評価: 標準的なアプローチです。ただし、ネットワークレイテンシと競合状態が発生する可能性があります。

3. Redisにおける競合状態の処理

同じユーザーに対する2つのリクエストが同時に2つの異なるAPIサーバーに到達した場合、両方がカウンターを4として読み取り、両方が5にインクリメントし、両方がリクエストを許可してしまい、制限を回避する可能性があります。

解決策:Luaスクリプト

Redisでは、Luaスクリプトをアトミックに実行できます。カウンターを取得し、制限を確認し、インクリメントし、TTLを更新するという一連の操作を単一のアトミックな操作として実行するスクリプトを書くことができます。

lua
-- 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スクリプトを介して実装されたスライディングウィンドウアルゴリズムを組み合わせることで、精度、パフォーマンス、スケーラビリティの最良のバランスを提供します。

こちらもおすすめです

よくある質問

固定ウィンドウカウンターは、境界エッジでトラフィックのバーストスパイクを許容します。ユーザーはウィンドウAの最後の1秒で全クォータを使い切り、直後にウィンドウBの最初の1秒で再び全クォータを送信できます。スライディングウィンドウカウンターは、前と現在のウィンドウ間の加重平均を計算し、最小限のメモリオーバーヘッド(クライアントごとに2つの整数キー)で境界でのバーストを平滑化します。
標準的なGETに続くINCRコマンドは、複数のワーカーサーバーがインクリメントする前に同じカウンター値を読み取ってしまう競合状態を作り出し、クライアントが制限を超えることを許してしまいます。Luaスクリプトの実行は、シングルスレッドのRedisエンジン上でアトミックに行われ、しきい値の検証、インクリメント、TTLの失効が不可分な操作として行われることを保証します。
高可用性アーキテクチャでは、設定可能なフェイルオープンまたはフェイルクローズモードを持つサーキットブレーカーを実装します。標準的な読み取り/クエリAPIは、Redisがタイムアウトした場合(5msを超えるなど)にユーザーのアップタイムを維持するためにフェイルオープンします。一方、ログインパスワード検証、MFA検証、クレジットカード処理などのセキュリティ上重要なエンドポイントは、ブルートフォース攻撃をブロックするためにフェイルクローズします。
すべてのAPIヒットをマルチリージョンのRedisインスタンス間で同期させると、100msを超える許容できないクロスリージョンネットワークレイテンシが発生します。先進的なアーキテクチャでは、グローバルクォータをリージョンごとに分割し(例:US-Eastに40%、EU-Centralに30%、AP-Southeastに30%)、カウンターの差分をメッセージブローカーを使用してバックグラウンドバッチで非同期に同期します。
HTTP 429 Too Many Requestsを、標準のレート制限ヘッダーと共に返すべきです:RateLimit-Limit(ウィンドウ内で許可される合計クォータ)、RateLimit-Remaining(現在のウィンドウで利用可能なトークン)、RateLimit-Reset(クォータがリセットされるまでの秒数)、そしてRetry-After(クライアントが再試行する前に待つべき最小遅延秒数)。
この記事を共有:

ニュースレター登録

最新の技術記事やシステム設計の解説をメールでお届けします。

開発者ユーティリティ

ブラウザで動く無料の開発者ツール

AI CLIログの整形、Cron式の作成、JWTの解析、chmod権限の計算をオフラインで実行。

ツール一覧を見る
Advertisement
BigQuery + Cloud Run: 本番向けのサーバーレスデータ取込パイプライン構築
gcp

BigQuery + Cloud Run: 本番向けのサーバーレスデータ取込パイプライン構築

Google Cloud 上でサーバーレスなデータ取込を本番品質で構築する実践ガイド。BigQuery Storage Write API、パーティショニングとクラスタリングの設計、Cloud Run 上の非同期 FastAPI レシーバ、Terraform による IaC 全体、実測に基づくコスト分析、そして深夜3時に呼ばれる障害モードまで扱います。

続きを読む
Dev Kits & DataToolsTagsRSSPrivacy PolicyTerms