連載の現在地:Phase 2「費用と安定」(4 / 4本目) — 使われ始めたら、財布と稼働を守る
1. 公開 → 2. 費用と安定 → 3. 安全 → 4. 品質 → 5. 自動化
こんな悩みの人へ
無料で使えるAI機能を、一部のユーザーが大量に使っている。プランごとに「1日10回まで」「月1,000回まで」のような上限を付けたい。回数を数える仕組みは作ったものの、同時にアクセスされると数がずれる。そんな方に向けた記事です。
結論
上限は、二段構えにします。
まず、回数の上限は、Cloudflare Durable Objectsでユーザーごとに数えます。 認証済みのユーザーIDでカウンターを分け、読み取りと更新を一体として扱います。次に、金額の上限は、AI GatewayのSpend Limitsで設定します。 ユーザーIDを付けて送れば、ユーザー単位で利用額の上限を持たせられます。
回数で使いすぎを防ぎ、金額で想定外の高額利用を止める、という役割分担です。
利用者 --> [1. Durable Objects] --> [2. AI Gateway] --> LLM API
1日の「回数」を 月の「金額」の上限で
厳密に数える ストッパーをかけるなぜユーザーごとの上限は難しいのか
ユーザーごとの上限が難しい理由の1つは、APIキーは1本でも、使う人は大勢いることです。プロバイダーの上限は、キーやプロジェクトといった単位でかかります。アプリの利用者ごとの上限を実現できるかどうかは、プロバイダーの機能と、識別情報の送り方によります。
同時アクセスで数がずれる問題もあります。「現在の回数を読む→1を足して書き込む」を複数のリクエストが同時に行うと、カウントが漏れてしまいます。
そもそも、回数だけでは費用を守れません。1回の利用でも、長文を送られれば費用は大きくなります。
解決策の比較
方法 | 費用 | 手間 | 向いている状況 |
|---|---|---|---|
Durable Objectsで回数を数える | 無料枠あり | 中くらい | 回数を正確に数えたい |
KVで回数を数える | 無料枠あり | 少ない | おおよその回数で足りる。書き込みが少ない |
AI GatewayのSpend Limits | 中心機能は無料 | 少ない | ユーザーやチームごとに金額の上限を持たせたい |
アプリのデータベースで数える | 既存の構成による | 中くらい | すでに会員データベースがある |
どれを選ぶか
回数を正確に数えたいなら、Durable Objectsを使います。厳密でなくてよく、書き込みも少ないならKVでも構いませんが、反映に時間差がある点に注意してください。金額にも上限を設けたい場合は、Spend Limitsを併用します。同時実行による予算超過に備えて、余裕のある額に設定しておきます。
手順
1. プランごとの上限を決める
プラン | 1日の回数 | 1か月の金額の上限 |
|---|---|---|
無料 | 10回 | 1ドル |
有料 | 200回 | 20ドル |
これは自社アプリのプラン設計例で、Cloudflareの料金プランではありません。金額は、同時実行による超過分も見込んで、事業として許容できる額より低めに設定します。
2. ユーザーごとのカウンターを作る
Durable Objectsで、ユーザー1人につき1つのカウンターを作ります。以下の例では、ストレージの読み取りと書き込みの間に外部通信などの別のI/Oを挟みません。Durable Objectsのストレージ操作に対する同時実行制御により、読み取りと更新が競合しない形にします。単にDurable Objectを使うだけで、すべての非同期処理が順番になるわけではありません。
import { DurableObject } from "cloudflare:workers";
export class UsageCounter extends DurableObject {
async consume(limit: number, day: string) {
const key = `count:${day}`;
const used = (await this.ctx.storage.get<number>(key)) ?? 0;
if (used >= limit) return { ok: false, remaining: 0 };
await this.ctx.storage.put(key, used + 1);
return { ok: true, remaining: limit - used - 1 };
}
}3. AIを呼ぶ前に、カウンターを確認する
日付は、日本時間で区切ります。ここでの利用回数はLLM呼び出し前に受け付けた回数です。LLMが失敗した場合も消費される設計なので、成功回数だけを数える場合は別に精算の仕組みが必要です。
getByName(user.id) で、同じユーザーのリクエストは、どこから来ても同じ1つのカウンターに届きます。カウンターが世界に1か所しかないので、同時に送られても数がずれません。ユーザーごとに別のカウンターなので、ほかのユーザーの処理を待つこともありません。
const day = new Date(Date.now() + 9 * 3600 * 1000).toISOString().slice(0, 10); // 日本時間の日付
const counter = env.USAGE.getByName(user.id);
const { ok, remaining } = await counter.consume(user.plan === "paid" ? 200 : 10, day);
if (!ok) {
return Response.json(
{ message: "本日の利用上限に達しました。明日0時(日本時間)に回復します。" },
{ status: 429 },
);
}
// ここでLLMを呼ぶ現在のWranglerでは、設定ファイルの durable_objects.bindings と exports にクラスを登録します。新しいDurable ObjectにはSQLiteストレージを使います。
{
"durable_objects": {
"bindings": [{ "name": "USAGE", "class_name": "UsageCounter" }]
},
"exports": {
"UsageCounter": { "type": "durable-object", "storage": "sqlite" }
}
}4. 金額の上限を、AI Gatewayで二重にかける
LLMへのリクエストに、ユーザーIDをメタデータとして付けて送ります(付け方は「LLM APIの請求額が読めない」参照)。AI Gatewayでゲートウェイを選び、「Settings」→「Spend limits」でルールを追加します。「Limit by metadata」のキーをリクエストと同じ userId にし、「Split by value」を選んで、金額と期間を設定します。ユーザーIDには、サーバー側で認証した値を使います。Spend Limitsはベータ機能で、同時リクエストでは予算超過が生じる場合があります。
5. 画面に残り回数を表示する
remaining を画面に出すと、上限に達したときの問い合わせが減ります。
更新日:2026-11-03
つまずきやすい点
KVには、反映の時間差があります。書き込んだ値がすべての場所で読めるようになるまでに時間がかかることがあるため、正確な回数制限には向きません。無料プランの書き込みは1日1,000回まで、という制限もあります。
ログインしていない利用者の扱いも、決めておく必要があります。ユーザーIDがない場合は、IPアドレスで数えるか、そもそも使えないようにします。ボット対策は「ボットにAPIを食いつぶされる」を参照してください。
日付の区切りにも注意が必要です。この例の toISOString() はUTCで日付を返すため、集計キーを作る前に9時間ずらし、日本時間の0時で区切っています。ユーザーID・プラン・日付はサーバー側で決め、利用者の申告値をそのまま使わないようにします。
また、Spend Limitsの対象は、料金が分かっているモデルだけです。新しいモデルなどで料金が登録されていない場合は、金額では止まりません。


次に出やすい悩み
回数と金額で守れるようになったら、次は、ログインしていない利用者や、悪意のある自動アクセスへの備えです。ログインなしの利用者やボットが上限をすり抜けてくる場合は、Phase 3の「ボットにAPIを食いつぶされる」が次の一手になります。全体の費用と内訳をあらためて確認したいなら「LLM APIの請求額が読めない」を、自分で返した429と、プロバイダーが返した429を分けて扱いたいなら「レート制限やエラーで、AIの機能が止まる」を参考にしてください。
Phase 2「費用と安定」は、この記事で一区切りです。次のPhase 3では、悪用や情報漏えいのリスクを潰します。最初の1本は「APIキーの漏えいが怖い」です。
「無料プランの上限を何回にすればいいか」のような、料金プランを考える段階からご相談いただけます。プランに合わせた利用制限の設計はCloudflareインフラ構築をご覧ください。
よくある質問
Q. KVでカウンターを作れば、利用回数を厳密に制限できますか?
KVには反映の時間差があり、厳密な回数制限には向きません。利用枠の確認と加算を一体に扱える仕組みが必要です。本文ではDurable Objectsを使い、ストレージの読み書きの間に外部通信を挟まない例を示しています。KVの整合性の説明も確認してください。
Q. LLMの呼び出しが失敗しても、回数を消費しますか?
本文の例は、LLMを呼ぶ前に受け付けた回数を数えるため、LLMが失敗した場合も消費します。成功した回数だけを数えたい場合は、枠の確保と精算、失敗時に戻す条件を別に設計します。再試行で同じ処理を二重に数えないかも確認してください。
Q. 画面から送られたユーザーIDやプランを、そのまま使ってよいですか?
利用者の申告値をそのまま信用せず、サーバー側で認証したIDと契約情報から決めます。利用枠の対象日やリセット時刻もサーバー側で揃えます。本文の例は日本時間の0時で区切る設計なので、ほかのタイムゾーンを使う場合は集計ルールも見直してください。
Q. 回数制限を設ければ、金額の上限も守れますか?
1回あたりの入出力やモデルによって費用が変わるため、回数だけでは金額の管理になりません。利用者別の回数制限と金額の制限を組み合わせます。Spend Limitsには同時実行時の超過や対象モデルの条件があるため、事業として許容できる額より余裕を持たせて設定します。
執筆者情報
IT顧問、AI・業務アプリ開発、SESを通じて、企業のIT活用を支援しています。代表の藤澤 昌隆は、2007年からシステム開発・運用に携わり、アクセンチュアでの業務ITコンサルティングを経て、2019年にパブロフ株式会社を設立しました。
AI活用の支援事例として、営業工程を支える7つの業務別AIアプリの構築を公開しています。アプリの開発に加え、入力・出力の形式、評価基準、テストケースの整備にも取り組んでいます。
記事の内容に関するご質問や誤りのご指摘は、お問い合わせ窓口へお寄せください。
