はじめに
現代のクラウドネイティブアーキテクチャにおいて、レートリミットはシステムの可用性とセキュリティを確保するための不可欠な技術です。特に、大規模なユーザーベースを持つサービスでは、高トラフィックや異常なアクセスパターンへの対応が求められます。本記事では、SpotifyがEnvoyを活用したレートリミットソリューション「Time Cop」の設計と実裝、およびEnvoyフィルタの選定プロセスについて深く掘り下げます。この取り組みは、CNCF(Cloud Native Computing Foundation)のコアインフラストラクチャとプラットフォーム組織の実踐の一例でもあります。
レートリミットの定義と現狀分析
レートリミットの基本概念
レートリミットは、L7 HTTPリクエストに対して、IP、パス、ユーザーIDなどの屬性に基づき、時間窓內のリクエスト數を制御する技術です。主に以下の2つのタイプに分類されます:
- 靜的バケット:事前に定義された屬性(例:URL、アップストリームクラスター)
- 動的バケット:実行時に決定される屬性(例:IP、ユーザーID)
現有ソリューションの課題
ソリューション |
優勢 |
課題 |
ローカルレートリミット |
柔軟性があり、多様な屬性をサポート |
動的バケットのサポートが限られ、高基數のシナリオ(例:IP制限)に対応困難 |
グローバルレートリミット |
狀態をEnvoy間で共有可能、動的バケットサポート |
外部ストレージ(例:Redis)依存、操作複雑、性能劣化(OSS版は5,000 RPSまで) |
RLQS(Rate Limit Quota Service) |
レクエストパス外での実行により遅延を迴避 |
開発段階で未就業、大量の內部リクエスト処理が必要 |
クラウドサービス/CDN |
管理サービスとして即時利用可能 |
動的バケットやアップストリームクラスター屬性のサポートが限られる |
Spotifyの使用シナリオと課題
Spotifyは、月間アクティブユーザー6.75億人、日間1億リクエスト(アーティストアルバムリリース時にはさらに増加)という大規模なサービスを運営しています。動的バケット(例:ユーザーID)をサポートするレートリミットが必須ですが、現有ソリューションでは以下の問題がありました:
- ローカルレートリミットでは高基數の動的バケットを処理できない
- グローバルレートリミットではコストと複雑性が高すぎる
- RLQSは未完成で、內部リクエスト量が膨大
- クラウドサービスではEnvoy層のアップストリームクラスター屬性をサポートできない
Time Copの設計と実裝
デザイン目標
- 異常なトラフィック(例:超限リクエスト)のみを制限し、正常ユーザーへの影響を最小化
- システムの拡張性を確保し、ハードウェアリミットに依存しない
核心アーキテクチャ
- アグレゲーター(Aggregator):一貫性ハッシュを用いて動的バケットを複數のアグレゲーターに分散し、データ移動を迴避
- カウンティングメカニズム:リクエストカウントを定期的に報告(リクエストごとの通信ではなく、時間窓ごとの通信)により、內部リクエスト量を削減
- ブロッキングアクション配布:Google Cloud PubSubを介して全Envoyにブロッキングアクションを配信し、個別Envoyとの直接接続を迴避
RLQSとの比較
要素 |
RLQS |
Time Cop |
內部リクエスト量 |
毎リクエスト |
毎時間窓 |
データ分片 |
無し(データ移動必要) |
一貫性ハッシュ(データ移動不要) |
Envoyフィルタ選定とGo実裝
拡張ソリューションの評価
言語 |
特徴 |
課題 |
C++ |
API成熟、背景処理可能 |
自己ビルド必要、開発門檻高 |
Lua |
簡単な構文 |
API幅狹、背景処理不可 |
Wasm |
多言語サポート |
実験的、ドキュメント不完全 |
Go |
背景処理可能、カスタムメトリクスサポート |
初期API不完全、コミュニティ成熟度疑問 |
Go選定の理由
- 背景処理(Go Routine)によるカウントとアグリゲーション可能
- EnvoyのネイティブメトリクスサポートによりDevOps要件を満たす
- チームのGo実裝経験があり、開発効率が高い
実裝詳細
- GoでEnvoyフィルタを実裝し、GBC(Go Buffer Channel)を介してアグレゲーターと通信
- PubSubを介してブロッキングアクションを全Envoyに配信
- アグレゲーターの失敗時でも、一貫性ハッシュによりエッジケースを処理可能
システム特性とトレードオフ
- 拡張性:アグレゲーター分片と定期カウントにより負荷を削減
- ロバスト性:単一アグレゲーターの失敗時でも他のアグレゲーターが大部分のトラフィックを処理
- パフォーマンス最適化:內部リクエスト量を削減し、遅延を抑制
- ユーザー體験:緩い制限條件により、偶発的な超限による正常ユーザーへの影響を迴避
技術評価と選定プロセス
言語選択の比較
- C++:信頼性高、背景処理可能、ネイティブメトリクスサポート、長期運用に適すが初期開発効率低
- Lua:簡単だがAPI幅狹、背景処理不可、要件に合致しない
- Wasm:多言語サポートだが実験的、ドキュメント不完全、チーム信頼性低
- Go:初期API不完全だがGoroutineによる背景処理可能、チーム実裝経験あり、開発効率高
キー要件
- 背景処理とメトリクス集約のサポート
- カスタムメトリクスによる監視基準の適合
- 開発効率と生産環境での拡張性
Go選定の考慮點
- 開発利點:チームがGoを熟知、初期MVPの迅速な実裝、Envoyネイティブメトリクスサポート
- リスクと制限:フィルタが未成熟(contrib空間)、クロスプラットフォームコンパイル問題(MacBook ARMとx86_64の差異)、メトリクスラベルの不足、アグレゲーター通信のUnixドメインソケット経由
実裝過程と課題
- パフォーマンステスト結果:Goフィルタの呼び出しオーバーヘッドはC++とほぼ同等、P50遅延3ms増加、P99遅延11ms増加、全領域レートリミットサービスのtimeout8ms、偶発的な影響
- 技術的制約:メトリクス名の混亂(例:
timecop_request_count{}
)、Envoyリスナー/クラスターへの直接API呼び出し不可、アグレゲーター通信のEnvoy経由外
生産環境での導入と振り返り
- 初期成果:生産環境への導入、一部サービスでの検証成功、流量のブロックによる基本機能実現
- 後続調整:API不足、技術的負債、言語成熟度の問題により、C++へのリファクタリング決定、90%のコード移行
- C++開発の課題:IDE設定、コンパイルフロー、デバッグの複雑さ、開発速度の低下
核心的な結論
- 大規模レートリミットの困難性:現行ソリューションが要件を満たさないため、カスタムソリューションの必要性
- Envoyフィルタ開発の制約:Go、Lua、Wasmの選択肢が成熟度不足、C++が唯一安定選択肢だが開発コスト高
- コミュニティへの要請:非C++の安定フィルタ選択肢の開発を望む、今後のEnvoyの新機能(例:動的モジュール)による改善期待
キー技術詳細
- アグレゲーターのロバスト性:Envoyのクラスタヘルスチェック、アクティブヘルスチェックとスムーズな切り替え、データロスを許容しサービス可用性を最優先
- クロスプラットフォームコンパイル問題:MacBook ARMとx86_64のコンパイル差異、手動でコンパイルフラグ調整、ローカルコンパイルが現実的
- メトリクスと監視:Goフィルタのメトリクス名の不規則性、外部システムへの依存、C++版での改善予定
後続アクション
- C++リファクタリング進捗:90%のコード移行、継続的な最適化、コンパイルフロー、デバッグツール、チームスキル向上への注力
- 長期目標:開発効率とシステム安定性のバランス、Envoy新機能(例:動的モジュール)の活用による開発簡略化
- コミュニティ貢獻:非C++の安定フィルタ選択肢の開発を呼びかけ、関連技術ディスカッション(例:Lightning Talk)への參加