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の
RateLimitRule
にCost
フィールド(上記の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対応)