AIToday

LLM API支出の原子的上限をRedisで実装

Hacker News2日前
LLM API支出の原子的上限をRedisで実装

要点

新しいオープンソースnpmパッケージ「llm-budget-cap」は、Redisベースの原子的支出制限を提供し、LLM API呼び出しの暴走による予期しない請求を防ぐ。単純なレート制限の手法にあるレース条件バグをカウンタ増分とTTL満期を単一のLuaスクリプトにラップすることで解決し、上限付近の2つの同時リクエストが両方通り抜けることを確実に防ぐ。Node.jsフレームワークで動作し、Redisが利用不可になった場合も安全に失敗する。リクエストは計測されないまま続行され、機能が破壊されることはない。

こういう要約が、毎朝あなたのメールに届きます。

無料で登録 →

3つのポイント

  • 何が起きたか

    開発者がllm-budget-capというオープンソースnpmパッケージをリリースした。LLM API呼び出し(OpenAI、Gemini、Anthropic)に対して、単一のLuaスクリプト内で原子的なRedisカウンタとTTLを実行することで、ハードな支出上限を強制し、複数のリクエストが同時に上限を突破するレース条件を防ぐ。

  • なぜ重要か

    バグ、スクレイパー、分散攻撃に起因する制御されないLLM API呼び出しにより、請求額は一晩で数千ドルに跳ね上がる可能性がある。単純なGET後SETアプローチは上限付近での高トラフィック下で失敗する。なぜなら、2つの同時リクエストが両方のコマンド実行前に同じカウントを読み込み、一方だけが通るべき場合でも両方が通ってしまうからだ。Lua原子的アプローチはこのギャップを完全に塞ぐ。

  • 注目点

    本パッケージは依存関係がゼロ(既に持っているioredisのみ使用)で、ユーザー別またはIP別の上限を設定可能、設定可能な時間ウィンドウ(デフォルト24時間)に対応する。デフォルトではフェイルオープンで、Redisがダウンした場合もメトリクスを計測しないまま動作を続行する。MITライセンスで、npm上で利用可能。

詳細

開発者はllm-budget-capをnpmパッケージとしてリリースし、制御されないLLM API使用による予期しない請求を防ぐよう設計した。コア仕組みは単純明快だ。LLM API(OpenAI、Gemini、Anthropic、または互換性のあるサービス)を呼び出す前に、cap.checkAndIncrement()を呼び出す。これは予算が残っているかを原子的に確認しカウンタを増分する。カウンタが上限を超えた場合、判定はallowed: falseを返し、アプリケーションはリクエストを429エラーで拒否する。そうでなければ、有料API呼び出しに進む。

本パッケージの重大な革新は、Luaスクリプトを通じた原子性の保証である。素朴な実装—GETでカウンタを読み取り、上限を確認してから、SETPで増分値を書き戻す—にはレース条件がある。カウンタが499で上限が500のほぼ同時に到着した2つのリクエストは、いずれかが書き込む前に両方499を読み込む。両方とも499 < 500を見て両方続行する決定をし、予算が後1件しか許さなくても両方が高額な呼び出しを行う。高トラフィックで上限付近の場合、これは理論的ではなく、本番環境で起きる。Luaスクリプトは増分(INCR)とTTLリセット(PEXPIRE)を単一の分割不可能な操作で実行する。current = redis.call("INCR", key)。currentが1であるか、キーがTTLを持たない場合、PEXPIREを設定する。currentを返す。すべての同時リクエストは一意のカウンタ値を受け取るため、上限は提案ではなく保証である。

APIは最小限だ。BudgetCapをインポートし、redis(ioredisクライアント)、key(カウンタ名)、limit(ウィンドウごとの最大呼び出し数)でインスタンス化する。デフォルトウィンドウは24時間(86400000ミリ秒)。windowMsを渡してカスタマイズする。グローバル上限にはcheckAndIncrement()を呼び出し、ユーザー、テナント、IP別に上限を強制するにはcheckAndIncrement(userId)を呼び出す(キーはkey:userIdとして組み合わせられる)。メソッドはallowed(真偽値)、count(増分後のカウンタ)、remaining(上限からcountを引いた値)、degraded(Redisが失敗した場合は真)を持つBudgetCapDecisionオブジェクトを返す。デフォルトではRedisに到達不可の場合、操作はallowed: trueおよびdegraded: trueで続行される。このフェイルオープンモードは、Redisの障害中に機能を生存させるための、一時的な計測されない使用とのトレードオフである。failOpen: falseを設定するとRedisエラーが投げられる。

本パッケージは外部依存関係がゼロである—ioredis(または互換性のあるRedisクライアントにeval メソッド付き)のみを使用し、ほとんどのNode.jsアプリケーションが既に含まれている。フレームワーク固有のコードなしにあらゆるNode.jsフレームワークと連携する。Luaスクリプトは監査または再利用のための定数(ATOMIC_BUDGET_CAP_LUA)としてエクスポートされる。開発者は異なるeval署名を持つnode-redis v4向けのラッパーアダプタを提供する。開発にはESLint、TypeScriptタイプチェック、Vitestユニットテスト(原子性テスト用の実Redisインスタンス付き)、ESM、CommonJS、TypeScript定義へのビルドが含まれる。ライセンスはMIT。

背景と解説

本パッケージは具体的な本番運用の痛点を解決する。LLM搭載機能を発売した開発者は、請求時に計測されないコール迴路または分散スクレイパーが数千ドルを消費していたことを発見する。基本的なIP別スロットルは攻撃者が多数のIPに要求を分散させるため失敗する。標準的な対処法—時間ウィンドウでリセットするRedisカウンタ—は単純に見えるが、負荷下で実行すると異なる。増分とTTL満期が別々のコマンドで起きると、上限付近でほぼ同時に到着した2つのリクエストが両方、新しい値のいずれかが書き込まれる前に古いカウンタ値を読み込み、一方だけが通るべき場合でも両方が通ってしまう。Lua原子的アプローチはINCRとPEXPIREをRedisの観点から分割不可能にすることで、このギャップを塞ぎ、すべての呼び出しが次の呼び出しがそれを読む前にカウンタを増分することを保証する。

本パッケージは意図的に最小限である。1つのクラス、1つのメソッド、ほとんどのNode.js開発者が既に使用しているioredisクライアント以外の第三者依存関係はない。また、実用的なフェイルオープンデフォルトも含まれている—Redisが利用不可になった場合、上限は計測されない状態に低下し、機能は破壊されない。ただしこれは切り替え可能である。Luaスクリプト自体はエクスポートされ、監査可能で、ライブラリはフレームワーク固有の結合なしにあらゆるNode.jsフレームワーク(Express、Fastify、NestJS)と連携する。

よくある質問

単純なGET+SETアプローチが持つレース条件をこれはどう防ぐのか?
GET+SETは2つのコマンド間に別のリクエストが同じカウント値を読み込める窓がある。上限500で現在499の場合、2つの同時リクエストが両方499を読み込み、両方続行する決定をし、両方増分する。その結果、支払いは501件だが、カウンタは500のままとなる。Luaスクリプトは、INCRとPEXPIREを単一のRedis操作として原子的に実行するため、checkAndIncrementの呼び出しごとに、一意で増加し続けるカウンタ値が読み込まれる。
Redisがダウンした場合、何が起きるのか?
デフォルト(failOpen: true)では、checkAndIncrementは{ allowed: true, degraded: true }を返し、操作は計測されないまま続行される。これはRedisの障害中に機能を生存させるための、短期間の計測されない使用のトレードオフである。failOpen: falseの場合、Redisエラーが投げられ、呼び出し元が処理方法を決定する。
グローバルではなくユーザー別またはIP別に支出をキャップできるのか?
可能である。subKeyをcheckAndIncrement(userId)に渡すと、カウンタキーはkey:subKeyとして組み合わせられる。また、カスタムwindowMsを設定することもできる(デフォルト24時間、任意の持続時間をミリ秒単位で指定)。

「大規模言語モデル」の最新ニュースを、毎朝7時にお届けします

AIが要約して、あなたの選んだトピックだけを1日1通。LINE・Email・Slackで届きます。

登録無料・30秒で完了・いつでも解除できます

ディスカッション

まだコメントがありません。最初のコメントを投稿しましょう!

ログインして議論に参加

関連記事

AIニュースを毎日お届け

200以上のソースから厳選したAIニュースを毎日無料でお届けします。

無料で始める

登録無料・30秒で完了・いつでも解除できます