2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Envoy Gateway 1.3.0 – 新機能「Rate Limiting with Cost」の概要

Posted at

BackendTrafficPolicy CRD における Cost Specifier

Envoy Gateway v1.3.0では、BackendTrafficPolicyカスタムリソースに定義するレートリミット規則(RateLimitRule)に**コスト指定子(Cost specifier)**が追加されました。各レートリミットルールにオプションでcostフィールドを設定でき、リクエストおよびレスポンスそれぞれに対して「コスト(消費量)」を指定できます。デフォルトではcostを指定しない場合、リクエストごとに1カウントを消費し、レスポンス時には何も消費しません。

costフィールドは以下の2つのサブフィールドから構成されます:

  • request: リクエスト受信時に消費するコストを指定します。例えばrequestコストを「5」に設定すれば、そのルールにマッチしたリクエストは一度に5カウント消費します。指定しない場合はデフォルトで1を消費します。Envoyがリクエストを受信した際、ここで指定された数だけレートリミットカウンタを減算し、**残高が不足していればそのリクエストを制限(ブロック)**します。

  • response: レスポンス送信後(ストリーム終了時)に追加で消費するコストを指定します。例えばresponseコストを「10」とすれば、該当リクエストのレスポンス完了後にさらに10カウント消費します。レスポンス時のコストは現在処理中のリクエストには適用されず、そのリクエストは一旦通過しますが、その後カウンタが減算されることで次回以降のリクエストに影響します。responseを指定しなければ、レスポンス時の減算は行われません(デフォルト動作)。このresponseコスト指定は現在**グローバルレートリミット(全Envoy間で共有される制限)**の場合にのみサポートされ、ローカルレートリミットには適用できません。

Cost Specifierの詳細仕様

コストの値指定には**固定値(Number)動的メタデータ(Metadata)**の2種類がサポートされています。各request/responseサブフィールドはfromでソースを指定し、以下のように設定します:

  • from: "Number" – 固定の数値を指定します。この場合numberフィールドに正の整数値を設定し、その値がコストとして使用されます。例えばrequest.from: "Number", request.number: 3とすれば、リクエスト時に毎回3カウント消費します。特別なケースとして、number: 0と設定するとカウンタのチェックのみを行い減算しない動作になります。これは後述のように「予算が残っているか確認するが実際には消費しない」用途で利用できます。

  • from: "Metadata" – Envoyの動的メタデータから値を取得してコストとします。この場合、metadataフィールドでnamespace(メタデータの名前空間)とkey(キー)を指定します。Envoyフィルタチェーン中の他のフィルタ(Luaスクリプト、External Processingフィルタ等)がリクエストまたはレスポンス処理中に動的メタデータに数値をセットしておき、レートリミットフィルタがそれを参照してコストとして使用します。例えばresponse.from: "Metadata", response.metadata: {namespace: "extproc", key: "token_usage"}のように設定すれば、External Processingフィルタ等がレスポンスボディ解析で設定したextproc名前空間のtoken_usageというメタデータ値を、レスポンス完了時のコストとして消費できます。

「Rate Limiting with Cost」の動作原理

Rate Limiting with Cost機能は、Envoy本体の新拡張機能を活用して実現されています。Envoyのグローバルレートリミットフィルタに、リクエスト完了後にカウントを減算する機能apply_on_stream_done オプション)と、ヒット数加算(hits_addend)の動的指定が追加されたことにより、Envoy Gateway側でコスト指定子が扱えるようになりました。具体的な動作は次の通りです:

  • リクエスト到着時、Envoy GatewayはBackendTrafficPolicyで定義されたRateLimitルールに基づき、requestコストをグローバルレートリミットサービスに問い合わせます。from: Numberの場合は指定値、from: Metadataの場合はリクエストに付随するメタデータから取得した値(例えば特定のリクエストヘッダを事前に別フィルタで動的メタデータに写しておく)をhits_addendとして送信し、レートリミットサービス上のカウンタを減算します。この時点で上限を超えるようならリクエストは即座に429などで拒否されます。

  • レスポンス送信時(ストリーム終了時)、もしresponseコストが設定されていれば、Envoyは改めてその値分のカウント減算処理を行います。apply_on_stream_doneオプションにより、このレスポンス時の処理は**「実行するだけで現在のリクエスト自体はブロックしない」(fire-and-forgetのような動作)と定義されています。したがって、レスポンス時のコストによってそのリクエスト後の残り予算が減る形になり、次のリクエスト以降に制限が反映されます。特にストリーミングレスポンスなどレスポンスボディの内容で使用量が決まる場合**(例: OpenAI APIのトークン消費量)、External ProcessingフィルタやLuaフィルタでレスポンスを解析し、最終行で使用量を動的メタデータに記録⇒レートリミットフィルタがストリーム完了時にそれを読み取ってカウント減算、という連携が可能です。この方法なら、レスポンス全体を送り終えるまで現在のリクエストを遮ることなく、終了後に正確なコストだけを課金できます。

  • 予算超過の事前チェック: レスポンスコストのみで制限を行いたい場合でも、すでに予算がゼロの場合のリクエストを許可してしまうと使い放題になってしまいます。これを防ぐため、リクエスト時に減算しないチェックとしてrequestコストに0を指定する使い方が議論されています。request.number: 0とすると**「カウンタ残高の照会のみ」**を行い(残がなければブロック)つつ、実際の消費は行わないため、トークンベースで残高ゼロのときはリクエスト自体を拒否し、それ以外のケースではレスポンス終了時に実際の使用量を減算する、といった構成が可能です。

この機能の背景には、Generative AI APIなどリクエストごとにコストが異なるユースケースへの対応があります。Envoy Gatewayの開発者によるIssueでの議論でも、OpenAIやAWSのストリーミングAPIの例を挙げ、レスポンス終端で使用トークン数を取得して課金する必要があること、そのためヘッダではなく動的メタデータで値を受け渡す設計が適切であることが指摘されています。この要件に対応するためにEnvoy本体とEnvoy Gateway双方に拡張が施されました(EnvoyのPR envoyproxy/envoy#37548 等で対応)。

実装コードの変更点(PR #4957 および #5035)

  • PR #4957 (“api: adds cost specifier to RateLimitRule”): Envoy GatewayのAPI定義にコスト指定子を追加したPRです。この変更でBackendTrafficPolicyのRateLimitRuleCostフィールド(上記のrequest/response等を含む構造体)が導入されました。PRの説明によると、この機能は**Envoy側の「ストリーム終了時に予算を減算する」新機能と「ディスクリプタ毎の可変ヒット数設定(hits_addend)」を利用して実現されており、レスポンス内容に基づく「トークンベースのレート制限」**を可能にする目的があります。例えば、AI Gatewayのようにユーザごとにトークン消費量でレート制限を行うケースを想定しています。このPRではKubernetes CRDのスキーマ拡張のみを行い、実際のEnvoy設定生成(translator部分)は含まれていません(実装は次のPRで行われました)。

  • PR #5035 (“feat(translator): implement ratelimit costs”): 上記API拡張を受けて、Envoy Gateway内部での翻訳ロジック(xDS変換)にコスト指定を実際に適用する実装を行ったPRです。この変更により、BackendTrafficPolicyでcostが指定された場合にEnvoyのレートリミット設定(RateLimitカスタムHTTPフィルタ)へ適切な設定が挿入されます。具体的には、responseコストが指定された場合にはEnvoyの新しい typed_per_filter_config(RateLimitPerRoute)形式での設定を使い、各ディスクリプタにapply_on_stream_done: true(ストリーム完了時適用)やhits_addendの指定を行う対応が実装されています。これはEnvoy本体の比較的新しいバージョン機能を必要とするため、Envoy Gateway側でもEnvoy v1.33.0以降を仮定して処理しています(古いEnvoyとの下位互換にも配慮し、cost未使用時は従来通りの設定を用いるなどの実装がされています)。また、このPRではエンドツーエンドテストを拡張し、実際にリクエスト・レスポンスでカウンタが期待通り減算されること(例えばX-RateLimit-Remainingヘッダの挙動)を検証しています。なお、グローバルレートリミットサービス(envoyproxy/ratelimit)の対応も必要となるため、テストでは最新版が使われています。

関連Issueやディスカッション

Rate Limiting with Cost機能の検討経緯はGitHub上のIssueでも議論されています。Issue #4756「Usage based Rate Limiting (Counting from response header values)」では、レスポンスヘッダの値を用いた使用量ベースのレート制限というテーマで議論が行われました。この中で、ストリーミングレスポンスの場合にレスポンス終了まで使用量が確定しない問題をどう扱うかが議題となり、動的メタデータ+ストリーム終了時処理のアプローチが提案されています。また、このIssueはGenerative AI対応のエピックIssue #4748配下のタスクとして扱われ、他にもExtProcフィルタでボディから値を抽出してヘッダやメタデータに載せる機能(Issue #4758)等と合わせて検討されました。最終的に、Issue #4756はPR #5035のマージによりクローズされており、この機能がv1.3.0で実現されたことが分かります。

設定方法と使用例

設定方法としては、BackendTrafficPolicyリソース内でレートリミットを定義する際にcostフィールドを追加する形になります。例えば、ユーザごとに月間消費トークン数を制限するようなケースを想定し、1リクエストあたり最大1000トークン使用可能(それ以上は制限)とするには以下のような設定が考えられます(※簡略化した例):

apiVersion: gateway.envoyproxy.io/v1alpha1
kind: BackendTrafficPolicy
metadata:
  name: example-ratelimit
spec:
  targetRef:
    kind: HTTPRoute
    name: my-service-route
  rateLimit:
    type: Global
    global:
      rules:
      - headers:                      # ここで任意のマッチ条件を指定(例: 全リクエスト共通なら省略可)
          - name: X-User-ID           # ユーザIDヘッダごとにレートを分ける例
            present: true
        limit:
          requests: 100              # ベースのリクエスト数上限(100リクエスト/月など)
          unit: Month
        cost:
          request:
            from: Number
            number: 0                # リクエスト時は消費せず予算チェックのみ
          response:
            from: Metadata
            metadata:
              namespace: extproc     # External Processingフィルタが設定するメタデータ例
              key: tokens_used       # レスポンス内で実際に使用したトークン数

上記の例では、request.cost.numberを0にすることで現在残っているリクエスト枠が0の場合のみブロックし、そうでなければ一旦通過させます。実際のトークン消費量は、ExtProcフィルタなどがレスポンスを解析してtokens_usedというメタデータに設定し、それをresponse.costが読み取ってストリーム終了時に減算します。こうすることで、各リクエストが実際に何トークン消費したかに基づいてグローバルなレートリミットカウンタを減らすことができます。

なお、固定コストの場合は単純にfrom: Numberかつnumber: <値>を指定するだけで、例えば**「この特定APIエンドポイントは通常リクエスト5件分の重みを持つ」といったポリシーを実現できます。逆にコストを動的に変えたい場合**(例えばリクエスト内容やレスポンス内容によって異なる負荷をカウントしたい場合)はfrom: Metadataを使い、別途フィルタでそのメタデータ値を設定することで柔軟なレート制御が可能です。

まとめ

Envoy Gateway 1.3.0の「Rate Limiting with Cost」機能により、レート制限の単位を1リクエスト=1カウントに固定せず、処理内容に応じた重み付けや使用量連動の制限が可能になりました。公式のリリースノートやブログでも、この機能を「Dynamic Cost Based Rate Limiting」と呼び、動的メタデータから値を取得してコストとすることで、コストの異なるリクエスト間で公平に利用制限を行えることが強調されています。実装面ではEnvoy本体およびEnvoy Gatewayの拡張に支えられており、特にGenerative AI等のユースケースで求められるトークンベースのAPIレート制限に対応するものとなっています。

参考資料:

  • Envoy Gateway v1.3.0 リリースノート(新機能一覧)
  • Envoy Gateway APIリファレンス(BackendTrafficPolicy / RateLimitCost仕様)
  • Envoy Gateway PR #4957 説明(Cost Specifier追加の背景と目的)
  • Envoy Gateway PR #5035 説明・コメント(実装詳細やテスト)
  • 関連Issueの議論(#4756 Usage based RL、#4748 GenAI対応)
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?