Uberのグローバル・レートリミット・システム
(uber.com)- Uberは、毎秒数億件のRPCを処理する数千のマイクロサービス環境において、グローバル・レートリミッター(Global Rate Limiter, GRL) を構築し、統合された過負荷保護の仕組みを確立
- GRLは、クライアント・アグリゲーター・コントローラーで構成される 3層フィードバックループ アーキテクチャにより、ローカルでリクエストを判定しつつ、数秒以内にグローバルな調整を実現
- 初期のトークンバケット方式から 確率的ドロップモデル へ移行し、公平性とスケーラビリティの問題を解決するとともに、ホットパスのレイテンシを最小化
- Redisベースのリミッターと比べてP99.5レイテンシを最大 90%改善 し、15倍のトラフィック急増やDDoS攻撃もサービス低下なく吸収
- Rate Limit Configurator(RLC) が過去のトラフィックパターンを分析して自動的に上限を更新することで、静的設定から自己調整型システムへ進化
従来のレート制限の問題点
- Uberの初期のマイクロサービス環境では、各チームが ビジネスロジック、カスタムミドルウェア、Redisベースのカウンター などを用いて個別にスロットリングを実装
- その結果、一貫しない設定、Redis起因の追加レイテンシ、上限変更時のサーバー再デプロイの必要性、文書化されていないリミッターによるインシデント対応の難しさといった問題が発生
- 小規模サービスのかなりの数はそもそも レート制限なしで運用 されており、各リミッターがメトリクスやエラーを異なる形で報告していたため、統合的な可観測性を確保できなかった
- Redisを 集中型カウンター として使う方式は、数十万ホスト・毎秒数億件・マルチリージョン環境では、受け入れ不可能なレイテンシとリージョン間整合性の問題を引き起こした
- シャーディングやレプリケーションを適用しても数百のRedisクラスタが必要となり、新たな障害モードも追加される
- カウンターの 定期同期 方式も、ネットワークオーバーヘッドは減らせるものの、データの鮮度低下や急激なトラフィックスパイクへの対応遅延のため採用されなかった
- 最終的に、ローカルプロキシが 集約された負荷に基づいて判断 する完全分散アーキテクチャだけが、低レイテンシとグローバルスケーラビリティを同時に達成できるとの結論に至った
統合インフラレベルのリミッターというビジョン
- 解決策は、Uberの サービスメッシュ(サービス間RPCトラフィック向けのインフラ層)にレート制限を組み込むこと
- この層にリミッターを埋め込むことで、呼び出し元の 言語やフレームワークに依存せず、すべてのリクエストを宛先到達前に検査・評価できる
- 目標は、コード変更なしで各チームが呼び出し元別・プロシージャ別のクォータを設定できる 統合レート制限サービス を提供すること
- 毎秒数億件のリクエスト、数万のサービスペア、複数地理リージョンにまたがるホスト群に対して、最小限のレイテンシ追加 でスケールする必要があった
GRLアーキテクチャ: 3層フィードバックループ
- GRLの中核は 3層フィードバックループ 構造
- Rate-limitクライアント(サービスメッシュのデータプレーン): アグリゲーターから受け取った指示に従ってローカルで各リクエストを判定し、ホストごとの毎秒リクエスト数をゾーンレベルのアグリゲーターへ報告
- アグリゲーター(ゾーン単位): 同一ゾーン内の全クライアントのメトリクスを収集し、ゾーンレベルの使用量を算出してコントローラーへ送信
- コントローラー(リージョン単位・グローバル): ゾーンデータを集約してグローバルな利用率を判断し、更新された ドロップ率の指示 をアグリゲーターとクライアントへ逆方向に伝播
- この階層的集約により、ホットパスの 低レイテンシ(判定はローカル)を保ちながら、数秒以内の グローバル調整 を同時に実現
- コントロールプレーン障害時には、クライアントは フェイルオープン で動作し、トラフィックを通し続けることで自己誘発障害を防ぐ
レート制限ロジックの進化
-
初期のトークンバケット方式
- 初期には、ネットワークデータプレーン内の各プロキシで トークンバケットアルゴリズム を適用
- 各プロキシがローカルのリクエスト数を追跡し、時間経過に応じてトークンを補充し、利用可能なトークン数に応じてリクエストを許可または拒否
- トークン補充率は、プロキシのローカル負荷とグローバル上限の比率として
ratio × limitRPSで計算 - バーストトラフィックに対応するため、未使用トークンを リングバッファ に保存し、デフォルトで10秒間(最大20秒まで設定可能)保持
- 本番環境では 公平性とスケーラビリティの問題 が明らかになった。呼び出し元数が上限を超えると容量を公平に分配できず、個別ホストのバーストトラフィックによって、グローバル上限未満でも早期ドロップが発生した
-
Drop-by-Ratioの導入
- 集約されたグローバル負荷が設定上限を超えると、クライアントが 確率的に一定割合のリクエストをドロップ する
- 例: 呼び出し元の集約RPSが上限の1.5倍であれば、すべてのインスタンスで約33%をドロップ。式は
drop_ratio = (actual_rps - limit_rps) / actual_rps - コントロールプレーンが数秒ごとに更新する グローバルドロップ信号 によって、超過トラフィックをすべての呼び出し元インスタンスで均等にスロットリング
- 数百〜数千の呼び出し元インスタンスを持つ大規模な ゲートウェイ型サービス で特に効果を発揮
-
統合確率モデルへの移行
- GRLの成熟に伴い、トークンバケットを完全に廃止し、コントロールプレーン駆動の確率的ドロップモデル に一本化
- 2つのアルゴリズムを同時運用すると、設定の複雑さとネットワークオーバーヘッドが増大したため
- 単一モデルへの統合により、設定を簡素化し、コントロールプレーン帯域幅を削減し、グローバルに一貫したメカニズム のもとで全てのレート制限判定を統合
- トレードオフとして、毎秒更新されるグローバル集約データに依存するため、2〜3秒の適用遅延 が発生
- 実運用では大半のワークロードで無視できるレベルであり、ごく短く極端なバーストでのみ影響がある
-
最終設計: コントロールプレーン指示による確率的ドロップ
- 現在のGRLでは、適用は完全に ネットワークデータプレーンのクライアント層 で行われる
- リクエスト到着時の処理フロー:
- 設定されたバケット(呼び出し元、プロシージャ、またはその両方で定義)にリクエストをマッチング
- 該当バケットに有効なドロップ率指示があれば、その比率に従って 確率的ドロップ
- ドロップ指示がなければ通常どおり転送
- ホットパスは極めて軽量で、ローカルトークン計算やリクエストごとのコントロールプレーン通信は不要。単純な確率サンプリング によりインプロセスで判定
- アグリゲーターとコントローラーは、転送プレーンの外側で複雑な計算(リクエスト数の集計、閾値比較、新たなドロップ率計算)を毎秒実行
- この設計により、毎秒数億件のリクエストへスケールしつつ、数秒以内のグローバル適用精度 を維持
上限設定
- サービスオーナーが設定ファイルでレート制限バケットを定義
- Scope: グローバル、リージョン別、ゾーン別
- マッチングルール: 呼び出し元名、プロシージャ、またはその両方
- 動作: deny(適用)または allow(テスト用シャドーモード)
- 宛先サービスに コード変更なしで 透過的に適用
運用成果
-
レイテンシ低減とオーバーヘッド排除
- GRL以前は、多くのサービスがリクエストごとにネットワーク往復を必要とする Redisベースのリミッター を使用
- サービスメッシュのデータプレーンでのローカル評価へ移行することで追加ホップを削減し、レイテンシを大幅に低減
- P50レイテンシは約 1ms短縮、P90は数十ms短縮、P99.5は数百msから数十msへと減少し、最大 90%改善
-
運用簡素化とリソース効率
- サービスメッシュのデータプレーン内にレート制限を集約することでインフラを簡素化
- クォータ適用のための専用データストアやキャッシュレイヤーが不要
- 以前はレート制限専用だった 多数のRedisインスタンスを解放 し、意味のあるコンピュート効率を実現
-
安定性向上とインシデント対応
- デプロイ後、GRLはスパイク、フェイルオーバー、リトライストーム 時に繰り返し過負荷を防止
- サービスメッシュ内で超過トラフィックを確率的にシェディングすることで、急激な流入負荷増加時でも 一貫した応答時間 を維持
- ある中核サービスは22Kから367K RPSへの 15倍のトラフィック急増 を劣化なく処理
- DDoS攻撃 を内部システムに到達する前に吸収
- インシデント対応時には、プロダクションエンジニアリングチームがGRLを使って特定の高トラフィック呼び出し元やプロシージャに ターゲット型レート制限を適用 し、コントロールプレーン更新が毎秒伝播することで数秒以内に過負荷へ対処可能
- サービス再デプロイなしで特定のトラフィックパターンを迅速かつ安全にスロットリング
- 全体規模: 毎秒約8,000万件、1,100超のサービスで動的クォータを適用
レート制限の自動化: Rate Limit Configurator(RLC)
-
手動設定の限界
- GRLによって適用は統合されたが、上限設定には依然として 手作業 が必要
- サービスオーナーがYAMLファイルで呼び出し元別・プロシージャ別のクォータを定義し、トラフィックパターンの変化に応じて調整
- 数百のマイクロサービスが継続的に進化する環境では、静的設定はすぐに 陳腐化 する
- 厳しすぎると通常トラフィックのピークでもスロットリングが発生
- 緩すぎると実容量に対して上限が過大となり、保護効果が乏しい
- 変更はダッシュボード分析と手動チューニングに依存
-
RLCの動作方式
- RLC(Rate Limit Configurator) がGRL設定を自動的に最新状態に保つ
- 固定スケジュール時、または設定変更時に即座に、次のサイクルを実行:
- Uberの可観測性プラットフォームから過去数週間の メトリクスを収集
- 過去のピークとバッファ余裕を活用して、呼び出し元・プロシージャ別の 安全な上限を計算
- 更新された設定を共有設定ストアへ書き込み
- 既存のコントロールプレーンを通じてGRLへ 新しい上限をプッシュ
- このクローズドループプロセスにより、上限は実際のトラフィックに合わせて進化し、手動介入を最小化
-
スケーラブルな設計
- RLCは当初から 複数のレート制限計算戦略 をサポートするよう設計
- デフォルトポリシーは過去RPSデータに基づくが、新しいポリシー種別をモジュール式に追加可能
- マッピング・位置情報データサービスなどでは、トラフィック予測と事前計画された容量を反映する 予測モデル を利用し、過去トレンドではなく将来負荷を予測
- 事前に定められた契約上・運用上の合意による 固定クォータ 割り当て方式もサポート
- モジュール式インターフェースにより、サービスドメインごとに準リアルタイムのトラフィックパターン、予測、静的クォータの中から適切な計算ロジックを選択可能
-
シャドーモードと検証
- 安全のため、シャドーモード で上限を生成・監視しつつ、適用しない運用が可能
- サービスオーナーは本番環境でレート制限の挙動を観察した後に有効化できる
- 専用ダッシュボードとアラートにより、観測トラフィックと仮想ドロップを可視化し、ロールアウト前の信頼性を確保
-
自動化の効果
- 数千件のレート制限ルールが手動編集なしで 自動更新 される
- 同じ式とデータソースにより、すべてのサービスにわたって 一貫したポリシー を生成
- 多様なポリシータイプにより、各チームがワークロードに最適化された計算ロジックを選択・拡張可能
- シャドーモードによって適用前の 正確性を担保
今後の方向性
- RLC導入後も、バッファチューニングの拡大、設定変更の影響範囲を小さくするための リージョン別レート制限 の導入、ライブトラフィックへの応答性を高めるための 更新頻度向上 を進めている
- Uberの スロットラーレイヤー がアプリケーションにより近い場所で追加の過負荷保護を提供
- 現在、GRLはUberの 多層安定性スタック の中核コンポーネントとして、極端な負荷下でもプラットフォームの安定性と公平性を維持している
まだコメントはありません。