モデルへの扉はひとつに。第5回 アイデンティティ、quota、chargeback
カスタムメトリクスのdimensionは5つしか使えず、time seriesは掛け算で増える。第5回はカーディナリティに耐えるテナントのアイデンティティ、quota、chargebackを構築する。

chargebackの設計を決めるのは、会計上の好みではなくAzure Monitorの制限だ。カスタムメトリクスに許されるdimensionキーは10個、API Managementはそのうち5個を既定で使っており、ポリシーが生むアクティブなtime seriesは、追加した各dimensionの一意な値の積になる。値が10個ずつの3つのdimensionで1,000のtime series、上限はリージョンごと、subscriptionごとに12時間で50,000のアクティブなtime seriesだ。そのメトリクスにユーザーIDやリクエストIDを載せれば、プラットフォーム自身のテレメトリが障害そのものになる。第4回で3つのリクエスト経路が揃った。本稿は、このシリーズが存在する理由である問いに答える。どのチームが何を使ったのか、そして1チームが他の全員のquotaを使い尽くすのをゲートウェイはどう止めるのか。
2つのクレデンシャル、2つの異なる仕事
API Managementは2つの機構を提供しており、補完関係にあるのに択一のものとして扱われがちだ。
subscription keyは、product、quota、分析の目的でコンシューマーを識別する。Ocp-Apim-Subscription-Keyヘッダー、またはヘッダーが無いときだけ確認されるsubscription-keyクエリパラメータで届き、どちらの名前もAPIごとに設定できる。keyはペアで発行されるため、アプリケーションはkey Aからkey Bへ移り、中断を最小限にAを再生成できる。
keyについては、無いことにするのではなく設計で織り込むべき点が2つある。第一に、API Managementにはsubscription keyの組み込みライフサイクルがない。有効期限も自動ローテーションもない。ローテーションはAzure PowerShellやSDKで自分で組むワークフローであり、組まなければkeyは永遠に生き続ける。第二に、keyは既定でバックエンドへ渡されるため、バックエンドの監視ログと、そのログの送り先すべてに残りうる。inboundセクションの末尾でそれを剥ぎ取るset-headerは2行の修正で、放置すれば監査人に発見されるログ内クレデンシャルの指摘を防ぐ。
Microsoft Entra IDのJWTは別の問いに答える。この呼び出し元はいま認可されているか。ネイティブに期限切れになり、中央で失効でき、claimを運ぶ。アプリケーションはOAuth2のclient credentialsフローで取得し、ゲートウェイは他の何よりも先にそれを検証する。
<validate-jwt header-name="Authorization" require-scheme="Bearer"
require-expiration-time="true" require-signed-tokens="true"
clock-skew="60"
output-token-variable-name="jwt"
failed-validation-httpcode="401"
failed-validation-error-message="Invalid or missing token.">
<openid-config url="https://login.microsoftonline.com/{{tenant-id}}/v2.0/.well-known/openid-configuration" />
<audiences>
<audience>api://genai-gateway</audience>
</audiences>
<required-claims>
<claim name="roles" match="any">
<value>model.invoke</value>
</claim>
</required-claims>
</validate-jwt>
output-token-variable-nameがこのポリシーの働きどころだ。検証済みのtokenがポリシー変数に入るため、後続のポリシーは呼び出し元が制御するヘッダーを信用する代わりに、そこからclaimを読める。これがすぐに効いてくる。次の判断が、どの値でquotaをキーにするかだからだ。
匿名アクセスの罠
これを生む設定が無害に見えるだけに、はっきり述べておく価値がある。subscription keyなしでAPIを呼べるかどうかは、独立した2つの設定で決まる。API自体がsubscriptionを要求するか、そしてそのAPIが割り当てられたすべてのproductが要求するかだ。APIがsubscriptionを要求しなければ、productスコープのkeyでも通る場合であっても、APIコンテキストではkeyなしの呼び出しが許可される。ゲートウェイは言われたとおりに動いているだけで、意図を伝えた者が誰もいなかった。
有料のモデルキャパシティの前に立つゲートウェイのルールは、すべてのAPIがsubscriptionを要求し、すべてのproductがsubscriptionを要求すること、そしてvalidate-jwtポリシーが常に走ることだ。未認証の呼び出しは二重に失敗する。keyのスコープも重要だ。サービススコープや全APIスコープのkeyはインスタンス内のすべてのAPIを開くため、プラットフォームチーム自身のツールには正しく、コンシューマーには誤りだ。コンシューマーにはproductスコープのkeyを渡す。
quotaと、何に対して数えるか
第1回のtoken制限ポリシーはcounter-keyを取り、その選択こそがテナンシーモデルのすべてだ。subscription IDをキーにすればコンシューマーごとに1つのバケットになり、それが第1回のインシデントに必要だったものだ。マーケティングのbatch jobがカスタマーアシスタントのquotaを使い尽くせないのは、別々のsubscriptionが別々のcounterを持つからだ。
<llm-token-limit counter-key="@(context.Subscription.Id)"
tokens-per-minute="{{tpm_tier}}"
estimate-prompt-tokens="true"
remaining-tokens-variable-name="remainingTokens"
remaining-tokens-header-name="x-remaining-tokens" />
残りの許容量をレスポンスヘッダーで返すのは、2つの追加属性に見合う。自分の余裕が見えるクライアントは429を受ける前にバックオフでき、スロットリングをめぐるサポートの会話は、双方に見える数字から始められる。
productが階層になる。分あたりの制限が高く日次quotaが控えめなインタラクティブ用product、その逆のbatch用product、そしてノートブックの暴走ループがインシデントではなく厄介事で済む程度に制限の低いサンドボックス用product。テナントとは、Entra IDアプリケーションと1つ以上のproduct subscriptionの組であり、第2回の30-tenantsレイヤーのTerraformでプロビジョニングされる。チケットでは決してない。
dimensionの予算
いよいよ冒頭の制約を、それが導く設計ルールとして述べる。Azure Monitorはカスタムメトリクスのdimensionキーを10個に制限し、API ManagementはRegion、Service ID、Service Name、Service Typeを含む既定で5個を使う。残るのはポリシーあたり最大5個のカスタムdimensionだ。より大きな制限は組み合わせにある。アクティブなtime seriesは期間内の各dimensionの一意な値の積に等しく、値が10個ずつの3つのdimensionが1,000を差し出し、リージョンの天井はsubscriptionごとに12時間で50,000のアクティブなtime seriesだ。同じリージョンの複数のAPI Managementインスタンスは、同じリージョン合計に寄与する。
帰結は、プラットフォーム自身のドキュメントに書き込む価値のあるきれいな分割だ。
- メトリクスは低カーディナリティのdimensionだけを運ぶ。テナント、モデルalias、環境。3つのdimension、有界な値の集合、素早く読み込まれるダッシュボード、正しい対象で発火するアラート。
- ログがそれ以外のすべてを運ぶ。リクエストID、そもそも取得するならユーザーID、プロンプトとcompletionのサイズ、レイテンシ、どのバックエンドが処理したか。リクエスト単位のアトリビューションが住むのはApplication InsightsとLog Analyticsで、月次のchargeback jobはそこへクエリを投げる。
<llm-emit-token-metric namespace="llm-metrics">
<dimension name="Tenant" value="@(context.Subscription.Name)" />
<dimension name="ModelAlias" value="@(context.Request.MatchedParameters.GetValueOrDefault("model","unknown"))" />
<dimension name="Environment" value="{{env}}" />
</llm-emit-token-metric>
意図して3つのdimensionだ。誰かが求めてくる4つ目はユーザーIDで、答えはノーだ。それはログレコードに属する。そこならカーディナリティの代償はリージョン全体のメトリクス予算ではなくストレージで済む。
財務との議論に耐えるchargeback
月次のjobは普通のデータエンジニアリングであり、その信頼性は精密であることではなく、精度について明示的であることに懸かっている。第3回と第4回が3つのクラスを確立しており、chargebackのドキュメントはそれを名指しする。
- 非streamingの同期: モデル自身の
usageから計測。正確。 - streamingの同期: プロンプトとcompletionのtokenはゲートウェイで推定。streamingはポリシー設定にかかわらず推定を強いるからだ。近似であり、ずれは独立したメトリクスとして追跡する。
- batch: control planeが提出したので、job自身のrequest countと出力ファイル内のレスポンスごとのusageから。正確。
jobはテナント単位の数字と、照合の1行を生む。帰属させた支出の合計と、その期間の実際のリソースコストの突き合わせだ。数パーセントの残差が月をまたいで安定しているなら、それは推定の誠実なコストだ。増え続ける残差はバグで、最も多いのはゲートウェイを迂回する経路を見つけた呼び出し元だ。残差を隠さず公開することが残りの数字を信じられるものにし、第1回が警告したバイパス経路の検出器を兼ねる。
チームが自前のゲートウェイを必要とするとき
まれに、コンシューマーの要件が共有インフラに収まらないことがある。独自のネットワーク分離を要する規制対象ワークロードや、他の誰のものにも触れずに自分のAPIを管理しなければならないチームだ。API Managementのworkspaceはこのために存在し、選択は2つの間にある。v2階層で追加のゲートウェイコストなしに使え、組み込み機能へアクセスできるサービス既定のマネージドゲートウェイか、Basic v2、Standard v2、Premium、Premium v2で使える独立したworkspace gatewayか。後者は強いランタイム分離と、独立したスケーリング、ホスト名、ネットワーク設定を買えるが、対価は追加コスト、長いデプロイ時間、対応リージョンの少なさだ。
これが可逆かどうかは1つの詳細で決まる。workspace gatewayの仮想ネットワーク設定はゲートウェイの作成時にしか設定できず、後から変更できない。そのネットワーク設定はAPI Managementインスタンスのものからも独立している。分離なしで作られたworkspace gatewayを、後で分離が必要になったチームが直すには、作り直すしかない。
注視すべき失敗モード
- ポリシー内の高カーディナリティdimension。エラーにはならない。リージョンのアクティブなtime series予算を静かに食い潰し、最初の症状は他チームのカスタムメトリクスが現れなくなることだ。
- 期限切れしないkey。組み込みの有効期限もローテーションもない。ローテーションのワークフローがなければ、廃止済みアプリケーションに発行されたクレデンシャルはまだ使え、それを報告するものは何もない。
- バックエンドのログに残るsubscription key。既定の挙動は転送だ。inboundで剥ぎ取り、決めつけずにバックエンドのログを一度確認する。
- subscriptionを要求しないAPI。APIコンテキストでの匿名アクセスであり、開放ではなく寛容に読める設定から辿り着いてしまう。
- 残差なしで公開されるchargeback。開示しなかった食い違いをプロダクトオーナーに一度見つけられれば、以後のすべての数字が交渉の対象になる。
第6回が受け継ぐもの
実在のアイデンティティを持つテナント、持ちこたえるquota、そして精度が明記されたチームごとの月次の数字。これで次の問いが正確に立つ。最も安いtokenは送られなかったtokenであり、semantic cachingはこのシリーズで初めて、請求書を帰属させるだけでなく減らす機構だ。しかもそれは上の数字の意味を静かに変える。キャッシュヒットは、コストゼロでありながら誰かのレポートには現れなければならないリクエストだからだ。
次に読む
- モデルへの扉はひとつに。第6回 semantic cachingとその失敗モード。最も安いtokenは送られなかったtokenになり、それを実現する機構が正しさの境界に変わる回。
- モデルへの扉はひとつに。第4回 非同期処理をリクエストパスの外へ。このchargebackモデルが依存するbatchの会計が築かれた場所。
- あなたのLLM請求額はオブザーバビリティの問題だ。特定のゲートウェイに入る前の、アトリビューション論の一般版。
これを大規模に運用する際のインフラおよびプラットフォーム側の話はercan.cloudのフィールドノートに、ハブはercanermis.comにある。
参考資料
Ercan の他のサイト
同じ著者、別の領域のサイトが2つ。