利用量・コスト計測とは、テナント別に処理回数やAI呼び出しを数え、課金と上限制御の根拠にする設計。
マルチテナントのAI自動化サービスで利用量を測るなら、設計の核心は先に言えます。プラン上限を弾く「即時カウント」と、請求書の根拠になる「課金記録」は、役割を分けて設計する。この2つは求める性質が違うからです。即時カウントは低レイテンシで即答することが要り、課金記録は正確さと履歴の完全性が要る。1つの数値に両方の役割を兼ねさせると、速さを優先すれば履歴の正確さが、正確さを優先すれば即応性が犠牲になりやすい。だからまず論理的に分離します。この分離は規模を問わず効きます。大規模ならRedisとイベントDBのように保存先ごと分けることが多いものの、要件を満たせるなら同一DB内の別テーブルやマテリアライズド集計でも成立します。ここでは、その分離をどう設計するかを、計測イベントの入口から請求生成まで順に追っていきます。
- 上限制御の即時カウントと請求の課金記録は要件が逆。まず論理的に分離し、規模に応じてRedis等の高速カウンタとイベントDBへ保存先も分ける
- リトライは二重計上の代表的な原因。取り込み時点で冪等キーを使い、同じイベントを二度数えない。ただしキーの粒度を誤ると別の呼び出しが1件に潰れる
- 数えるのはテナント別に複数メトリクス。「ワークフロー実行数だけ」は隠れたAPI/AI呼び出しを取りこぼすアンチパターン
なぜ「即時カウント」と「課金記録」を分けるのか
利用量計測の本番設計とは、プラン上限を判定する即時カウントと、請求の根拠になる課金記録を別レイヤーに分け、規模に応じて適した保存先で動かす設計を指します。この分離が定石とされるのは、2つのレイヤーが求める性質が逆を向いているからです。片方を優先すると、もう片方の要件が犠牲になりやすい。
制限(即時性重視)と課金(正確さ・履歴重視)は要件が逆を向く
プラン上限の判定は、リクエストが来た瞬間に「今この処理を通してよいか」を返さなければ意味がありません。求められるのは低レイテンシです。ただしクォータはプラン上の利用権に直結するため、「速ければ多少ずれてよい」と安易に流すのは危険です。誤ってブロックする、あるいは上限を超えて使わせてしまう。その誤差をどこまで許すかを決めたうえで、即応性を優先する。多くのチームがこの高速な使用量チェックにRedisのカウンタを使うのは、この即応性のためです。
一方の課金記録は、正反対の性質を要求します。請求書に載る数字が数件ずれただけでも、それは信頼問題に直結します。過去にさかのぼって「いつ・どのテナントが・何を・いくつ使ったか」を照会できる監査可能性も欠かせません。速さより、正確さと履歴の完全性。即時判定と、監査可能な課金履歴を、1つのカウンターや集計値だけで同時に満たすのは困難です。保存先が同じDBであっても、イベント履歴と即時集計は責務を分けて設計します。
単一の数値に寄せると起きる典型的な事故
即時カウントと課金記録を1つの数値で兼ねた設計は、決まったパターンで事故ります。まず「ワークフロー実行数だけを数える」設計。1回の実行の内側に、複数のAPI呼び出しやAIリクエストが隠れているのが普通です。実行回数だけ見ていると、内部で消費された量を丸ごと取りこぼします。
次に「Redisだけで課金する」設計。Redisはデータをメモリ上で保持します。永続化は設定しだいで、RDBスナップショット方式では最後のスナップショット以降の書き込みを失う可能性があり、AOF方式でも失う量は設定で変わります(後述)。単純なRedisカウンターだけを唯一の課金根拠にすると、クラッシュ時の損失窓がそのまま金額の欠損になりかねません。そして「リトライを無視する」設計。再試行が走るたびに同じ利用が二重に数えられ、請求が実態から膨らみます。この3つは、役割を分けていれば構造的に避けられる事故です。
何をテナント単位で計測するか
計測対象の棚卸しとは、テナント(tenant_id)ごとに何を1単位として数えるかを、メトリクスの種類ごとに定義する作業です。ここで対象を絞りすぎると、後段の課金精度が最初から破綻します。数えるべきものを最初に列挙しておくのが出発点。
利用量はテナント単位で、複数のメトリクスとして追跡します。送信メッセージ数、API呼び出し数、ワークフロー実行数、AIリクエスト数。これらは性質が異なり、上限に使うものと課金に使うものを分けて考える必要があります。たとえばメッセージ数はプラン上限(月あたり何通まで)の判定に直結しますが、AIリクエストは呼び出し回数だけでなく、その内側で消費されるトークン量によってコストが変動します。同じ「1回」でも、原価がまったく違うわけです。この点は後半のコスト計測で改めて掘り下げます。
計測イベントの最小構造
計測イベントには、まず「どのテナントが・どのメトリクスを・いくつ・いつ」を持たせます。ただし冪等な取り込みと監査まで見据えるなら、これに各イベントを一意に識別する event_id、生成元を表す source、計測基盤が受け取った時刻 ingested_at を加えておくのが実務的です。イベントの構造は次の形になります。
{
"event_id": "wf-8f21c:node-ai-03:call-7c91:ai_request", // 冪等キー(後述)
"source": "n8n/prod", // 生成元
"tenant_id": "tenant_42",
"metric": "ai_request", // message / api_call / workflow_run / ai_request
"quantity": 1,
"occurred_at": "2026-07-11T09:32:00Z", // 事象の発生時刻
"ingested_at": "2026-07-11T09:32:02Z" // 計測基盤が受け取った時刻
}
これは自前の利用量イベント例です。CloudEvents形式で送るなら、必須属性の specversion・id・source・type を備える必要があり、重複判定は source と id の組で行います(上の event_id は CloudEvents の id に対応づけます)。
処理の流れとしては、実処理の中に記録とカウンタ更新を組み込む形で表せます。おおまかには「ワークフロー → 使用量イベント記録 → カウンタ更新 → 処理継続」。この1本の流れの中で、即時カウント層と課金記録層の両方に書き込みが飛びます。ところが、ここに本番でいちばん事故りやすい落とし穴が潜んでいます。
RedisとDBの二重書き込みをどう整合させるか
即時カウント(Redis)と課金記録(DB)は別のストアです。別システムへの2回の書き込みは、通常は1つのACIDトランザクションにまとめられません。つまり、DBには記録できたがRedis更新に失敗した、Redisは更新できたがDB記録に失敗した、両方書けたが実処理が失敗した、実処理は成功したがレスポンスを返す前にプロセスが落ちた。こうした部分成功が起こり得ます。AWSもこれを「dual write(二重書き込み)」の問題として整理し、対策のひとつにトランザクショナル・アウトボックスを挙げています。「本番パターン」を名乗るなら、ここを素通りしてはいけません。ここで押さえたいのは、次の2つが別の目的を持つ仕組みで、排他的な二択ではないという点です。
アウトボックス(イベントを失わず配送する)。業務DBと同じトランザクションで、送信予定のイベントをoutboxテーブルにも書き込みます。別プロセスがoutboxを読んで利用イベントを配送し、そのイベントからRedisカウンターやDBの課金記録を更新する。Redisはあくまで派生的な近似値で、DBやイベント履歴からいつでも再構築できる、という位置づけにします。狙いはDB更新とイベント配送の不整合(dual write)を防ぐことで、配送は「最低1回」になりがちなので、受け手は冪等にしておきます。
リザベーション(実処理前にクォータ枠を確保する)。実処理の前にクォータを予約し、処理が成功したら確定、失敗したら予約を解放します。AIトークンのように実際の消費量が処理後にしか確定しないものは、まず見積り値で予約し、確定後に予約値と実績値の差分を調整します。狙いは同時実行時にクォータ超過をすり抜けさせないことで、上限判定を「予約+確定」の2段にします。
この2つは役割が違います。アウトボックスは利用イベントを失わず配送するための仕組み、リザベーションは実処理前のクォータ超過を防ぐための仕組みです。厳密な上限制御と確実な課金記録の両方が必要なら、両者を組み合わせます。いずれにせよ守るべきは「Redisの数値は壊れても作り直せる派生値、確定値はDB側」という非対称。二重書き込みの不整合を放置したまま速度だけ追うと、上限判定と請求のどちらもじわじわずれていきます。
即時カウントの層:Redisカウンターでクォータを制限する
即時カウント層とは、テナントごとの現在利用量を高速に加算・参照し、プラン上限を超えたら処理を止めるためのレイヤーです。ここで正本を持つのではなく、あくまで「今この瞬間に通すか止めるか」を即答する役割に徹します。
この層で定番になっているのがRedisのINCRです。INCRはアトミックな整数インクリメントで、Redis公式のドキュメントでもカウンタおよびレート制限(一定時間内の操作回数制限)のパターンとして位置づけられています。
Redis公式のINCRコマンドドキュメントは、INCRをカウンタ用途とレート制限パターンの基本操作として説明している。計算量はO(1)で、扱える値は64bit符号付き整数の範囲。(出典: Redis公式 INCRコマンドドキュメント https://redis.io/docs/latest/commands/incr/ )
アトミックであることが要点です。複数のワーカーが同じテナントのカウンタを同時に叩いても、加算が取りこぼされたり重なったりしません。計算量はO(1)で、値は64bit符号付き整数まで扱えるため、利用量カウントの規模で上限に当たる心配はまず不要。
期間ウィンドウ付きカウンターとクォータ判定
月次リセットのような期間区切りは、キー名に期間を埋め込むだけで表現できます。INCRとEXPIREを組み合わせると、時間窓ごとのカウンタが作れ、窓が変われば古いキーはRedis側が自動で失効させます。手動で前月分を消す運用は要りません。
# 期間キー方式: usage:{tenant_id}:{期間}
INCR usage:tenant_42:2026-07
EXPIRE usage:tenant_42:2026-07 <当月末までの秒数>
GET usage:tenant_42:2026-07 # 現在値をプラン上限と比較
ただし、素朴に組むと2つの穴が残ります。1つは原子性。INCRとEXPIREATを別コマンドで叩くと、INCR成功後・EXPIREAT実行前にクライアントが落ちた場合、失効時刻のないキーが残ります。もう1つは数える対象。INCRしてから上限超過を判定すると、拒否したリクエストまでカウンタに加算されてしまいます。「試行数」を数えるならそれで正しいですが、「許可した利用量」を数えるなら過大になります。
# アンチパターン: 別コマンドで叩くと原子的でない
current = redis.incr(key)
if current == 1:
redis.expireat(key, period_end_ts) # ← ここに到達する前に落ちると失効しない
if current > plan_limit:
raise QuotaExceeded() # ← 拒否した分もすでに加算済み
Redis公式も、INCRとEXPIREを別々に実行する実装には競合があるとして、Luaスクリプトなどで一体化するよう案内しています。冪等キーの確認・現在値と加算量の比較・上限以内なら加算・初回なら失効設定・処理済みイベントIDの保存を、1つのLuaにまとめると、これらを原子的に扱えます。quantity が常に1とは限らないため、汎用実装では INCRBY 相当で加算量を渡します。冪等キーには source と event_id を渡しますが、Redisで1つの文字列にまとめる時は単純連結を避けます(後述)。なお加算量(quantity)は正の整数だけを受け付け、負数・0・非整数はLua内で弾きます。取消や返金のような減算は、上限判定の経路には流さず、DB側の調整イベントとして別に扱います。
-- KEYS[1]=usage:{tenant}:period KEYS[2]=seen:{tenant}:period
-- ※Redis Cluster では両キーが同じスロットに載るよう {tenant} でハッシュタグを揃える
-- ARGV[1]=idem_key(source と event_id を衝突しない形で結合。長さ前置 or ハッシュ)
-- ARGV[2]=quantity ARGV[3]=plan_limit ARGV[4]=usage_expire_at ARGV[5]=seen_expire_at
local quantity = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
if not quantity or quantity <= 0 or quantity % 1 ~= 0 then
return redis.error_reply('quantity must be a positive integer') -- 負数・0・非整数を拒否
end
if not limit or limit < 0 or limit % 1 ~= 0 then
return redis.error_reply('invalid plan_limit') -- plan_limit の妥当性も確認
end
local current = tonumber(redis.call('GET', KEYS[1]) or '0') -- usage失効済でも0扱いで安全
if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then
return {current, 'DUPLICATE'} -- 既知イベントは加算しない(冪等)
end
if current + quantity > limit then
return {current, 'OVER_LIMIT'} -- 上限超過は加算せず拒否
end
local newval = redis.call('INCRBY', KEYS[1], quantity)
if redis.call('TTL', KEYS[1]) < 0 then
redis.call('EXPIREAT', KEYS[1], ARGV[4]) -- TTLが無ければ使用量キーに設定
end
redis.call('SADD', KEYS[2], ARGV[1]) -- 処理済みID(衝突しない結合)を記録
if redis.call('TTL', KEYS[2]) < 0 then
redis.call('EXPIREAT', KEYS[2], ARGV[5]) -- seen もTTL無ければ設定(退避/失効後の再作成を救う)
end
return {newval, 'OK'}
勘どころが3つあります。1つ目は冪等キーの作り方。source と event_id を単純に連結すると、ab+c と a+bc のように別物が同じ文字列になり、正当なイベントを重複と誤判定しかねません。長さ前置(len(source):source:len(id):id)にするか、正規化JSONのSHA-256ハッシュにして、境界が曖昧にならないようにします。2つ目は seen 集合のTTL。使用量キーの初回作成にだけ紐づけると、seen だけが退避・失効した後に SADD で作り直された seen にTTLが付かず、溜まり続けます。上のようにTTLを個別に確認し、無ければ必ず付け直します。しかも seen の失効は単純な月末(period_end)ではなく、想定する最大リトライ期間・イベント再配送期間より長くとります。月末直前に届いたイベントの再配送が翌月へずれ込んでも重複排除が効くようにするためです。ただしこれが成り立つのは、期間キーを受信時刻(ingested_at)ではなく、検証済みの occurred_at かイベント生成時に固定した billing_period から決める場合に限ります。翌月の現在時刻からキーを作ると、前月の seen 集合を参照できず重複排除が外れます。3つ目はRedis Cluster。マルチキーのLuaは全キーが同じスロットに載る必要があるため、usage:{tenant}:… と seen:{tenant}:… のようにハッシュタグ {tenant} を揃えます。
Redis 8.8では、この「加算+上限判定+期限設定」を1つのネイティブコマンドで行う INCREX が追加されました。UBOUND で上限を、ENX で「既存ウィンドウのTTLは延長せず、無いときだけ設定する」固定ウィンドウを表現できます。戻り値は「新値」と「実際に加算した量」の2要素で、上限超過なら加算されず後者が0になります。
# Redis 8.8+(OSS): 上限判定と固定ウィンドウの期限設定を1コマンドで
INCREX usage:tenant_42:2026-07 BYINT 1 UBOUND 1000 EXAT <当月末のunix秒> ENX
# 戻り値 [新値, 実際の加算]。上限超過なら加算されず [現在値, 0]
ただし2点補足が要ります。1つは対応範囲。INCREX はRedis Open Source 8.8以降で使えますが、公式の現行互換表ではRedis Software / Redis Cloudは非対応表示で、マネージドサービスやRedis互換製品では対応時期が異なります。利用環境のコマンド対応状況を確認してから使います。もう1つは冪等性。INCREX 単体では処理済みイベントIDによる重複排除まではできないため、冪等性まで同時に扱うなら引き続きLuaや別の重複排除機構(前掲の seen 集合など)が必要です。まとめると、Redis 8.7以前はLuaで一体化、8.8以降(対応環境)は上限判定と期限設定を INCREX に寄せつつ冪等性は別途、という組み方になります。
この形なら、超過したリクエストを加算しないので、クォータを通過した予約量を取りこぼしなく数えられ、失効設定の取りこぼしも起きません。ただしこれは上限判定を通した予約量であって、確定した「成功利用量」ではありません。処理の成否まで反映するなら、前述のリザベーション(成功で確定・失敗で解放)と組み合わせます。超過時の挙動は、新規リクエストを完全に止める(block)か、流量を絞る(throttle)かのどちらか。プランの性質に合わせて選びます。
Redisを課金の唯一の正本にしてはいけない理由
この層を課金の唯一の正本にしない理由は、前述の永続性に尽きます。Redisはメモリ保持が基本で、クラッシュ時には損失窓が残り得ます。しかも損失要因は永続化設定だけではありません。クラスタ構成では非同期レプリケーションとフェイルオーバーのため、マスターが書き込みに応答した直後・レプリカへ反映される前に障害が起きると、応答済みの書き込みが失われることがあります(Redis公式もクラスタは強整合性を保証しないと明記しています)。とはいえ「Redisは課金に一切使えない」とまでは言えません。Redis公式は、PostgreSQLに近いデータ安全性が要るならRDBとAOFの併用を案内しています。実務的な結論は、「単純なRedisカウンターだけを唯一の課金根拠にするのは避け、確定値は別レイヤーに残す」。ソフトクォータ(多少の超過を許容できる上限)なら、カウンタが巻き戻っても実害は限られる場合があります。一方、ハードなコスト上限や不正防止・セキュリティ上の制限では、巻き戻りが過剰利用や課金暴走につながり得るため、そこは正確な確定値に依存させます。請求はさらにシビアで、1件の欠損がそのまま金額の誤りになります。即時カウント層は「速いが揮発しうる近似値」と割り切り、確定値を課金記録層に残す。この割り切りが2層設計の背骨です。
課金記録の層:イベントをDBに残して請求・監査・月次に使う
課金記録層とは、利用イベントを1件ずつデータベースに追記し、その履歴から請求書・監査証跡・月次レポートを生成するレイヤーです。ここが唯一の正(system of record)になります。請求のためには使用量イベントをデータベースに保存し、そこからレポートや請求書を生成する。これにより正確な請求履歴、監査、月次レポートが得られます。
append-only な利用イベントテーブルと集計クエリ
この層のテーブルは、上書きせず追記だけを許す append-only を基本にします。既存行を書き換えなければ、あとから「その時点で何が記録されたか」を再現しやすくなります。テーブルの骨格は次の形です。
CREATE TABLE usage_events (
event_id TEXT NOT NULL, -- 実行ID+ノードID+呼出ID+メトリクス、または外部APIのリクエストID
source TEXT NOT NULL, -- 生成元(n8n/prod 等)
tenant_id TEXT NOT NULL,
metric TEXT NOT NULL, -- message / api_call / workflow_run / ai_request
quantity BIGINT NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL,
ingested_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (source, event_id) -- CloudEvents は source+id で一意
);
ただし、テーブルを append-only 型にしただけでは追記専用は保証されません。DDLに UPDATE/DELETE を防ぐ仕組みは無いからです。運用側で、アプリ用ロールから更新・削除権限を外し、修正は元イベントの書き換えではなく取消・調整イベントの追記で表現します。あわせて、適用した料金表とratingロジックのバージョンを保存し、必要なら監査ログや改ざん耐性のある保管先を使う。ここまで揃えて初めて、過去時点の利用量を安定して再計算できます。
quantity の型は数える単位で選びます。API呼び出し回数やトークン数のような整数量なら BIGINT で足りますが、音声秒数・GPU時間・GB時間・小数のクレジットのように小数を含みうる単位を同じ列で扱うなら、最小単位に整数化して持つか NUMERIC を使います。金額も浮動小数点は避け、最小通貨単位の整数か NUMERIC が安全です。
使用量は、この履歴を期間で切って集計して出します。即時カウンタの現在値をそのまま信じるのではなく、生イベントから再計算できる形にしておく。ここが監査可能性の源になります。とはいえ請求のたびに全件を走査する必要はなく、生イベントから再計算可能なバージョン付き集計テーブルやマテリアライズドビューを通常の参照に使い、必要なときに原資料へ遡れるようにします。ただし、この集計値がそのまま請求額になるわけではありません(この点は次項で分けて扱います)。
SELECT tenant_id, metric, SUM(quantity) AS total
FROM usage_events
WHERE occurred_at >= :month_start
AND occurred_at < :month_end
GROUP BY tenant_id, metric;
計測・料金評価・請求確定は別レイヤー
同じ1つの履歴から、請求書も、監査の問い合わせ回答も、月次のダッシュボードも導けるのが、この設計の効きどころです。「先月の請求はなぜこの金額か」と問われたら、該当期間のイベントを引くだけで根拠を示せます。集計値だけを持っていて生データを捨てていると、この再現ができません。
ここで1つ切り分けが要ります。生イベントの集計(metering=何をどれだけ使ったか)、その使用量に単価・段階料金・割引・無料枠・最低/上限額・税・端数処理を当てる評価(rating=いくらと評価するか)、そして確定した料金を請求書にする確定(invoicing)は、別のレイヤーです。生イベントを SUM(quantity) しただけの数字は使用量であって、最終請求額ではありません。単純な従量単価ならほぼ一致しますが、段階料金やクレジット・割引・税が絡むとずれます。さらに、過去の料金表が後から変わっても確定済みの請求が動かないよう、料金表のバージョンと適用期間、rating結果のスナップショット、確定した請求行の不変化のいずれかを持たせておきます。
もう1つ、月次で締める課金では occurred_at だけで月を切ると決めきれない点が残ります。月末のイベントが翌月に届く、あるいは発生時刻が誤設定・詐称されて送られてくるケースです。少なくとも、テナントごとの請求タイムゾーン、締め時刻と猶予期間(グレース)、遅延イベントを前月請求に反映するか翌月調整に回すか、occurred_at をどの生成元まで信用するか、請求確定後に届いたイベントを調整行として扱うルール、をあらかじめ決めておきます。ここを曖昧にすると、締め直後に届いた遅延イベントが宙に浮きます。課金システムでは軽視できない設計点です。
使用量計測の標準化に取り組むプロジェクトも、この考え方を土台にしています。使用量メータリングを扱うOpenMeterは、CloudEvents形式の利用イベントを取り込み、重複排除と集計を後段で行います。CloudEvents自体は、イベントの形式・属性・識別規則を定める仕様であって、保存方式や集計・請求処理までは規定しません。後段の集計・重複排除・料金適用は、それを利用する側の設計です。生イベントを一次データとして残し、加工は後から何度でもやり直せるようにする。この分業が、履歴の完全性を守ります。
リトライによる二重計上を防ぐ(冪等性の設計)
冪等性の設計とは、同じ利用イベントが複数回届いても、課金上は一度しか数えないようにする仕組みです。リトライ(再試行)は二重カウントを引き起こしうるため、計測設計では必ず考慮します。ここを外すと、他をどれだけ丁寧に作っても請求が実態から膨らみます。
冪等キーで同一イベントの再記録を弾く
二重カウント防止の実運用手法は、取り込み(ingestion)時点で冪等キーを使い、イベントを重複排除することです。設計の考え方は「過少報告するより、二重報告して後で重複を除去するほうが良い」。取りこぼしは復旧が難しいが、重複はキーさえあれば機械的に消せる、という非対称性に基づいています。
冪等キーは、各イベントを一意に識別するために使います。ここでよくある落とし穴が粒度です。「実行ID+メトリクス種別」だけにすると、同じワークフロー実行の中でAIを3回呼んでも全部が同じキーになり、DBの主キー制約で2回目・3回目が重複として落ち、1回しか課金記録に残りません。これは「実行内部の複数AI呼び出しを数える」という前半の狙いと矛盾します。少なくとも実行ID・ノードID・呼び出しID・メトリクスまで含めた粒度、あるいは外部APIが返すリクエストIDを使います。CloudEventsの仕様が求めているのも固定のキー形式ではなく、source と id の組が各イベントで一意になることです。だから前掲のテーブルも主キーを event_id 単独ではなく (source, event_id) の複合にしています。異なる生成元がたまたま同じIDを振っても、別イベントとして区別でき、正当なイベントを重複と誤判定して落とすことを避けられます。
-- 潰れるキー: wf-8f21c:ai_request (実行内の3回が1件に)
-- 一意なキー: wf-8f21c:node-ai-03:call-7c91:ai_request
INSERT INTO usage_events (event_id, source, tenant_id, metric, quantity, occurred_at)
VALUES (:event_id, :source, :tenant_id, :metric, :quantity, :occurred_at)
ON CONFLICT (source, event_id) DO NOTHING; -- source+event_id の再挿入を無視(冪等)
ここで区別すべきは、「配送の重複」と「実際に処理が複数回起きたケース」です。同じイベントがネットワーク都合で3回配送されただけなら、event_id が同一なので記録は1件に畳まれます。しかし、ユーザー操作は1回でも外部LLM APIが3回呼ばれ提供元でも3回課金されたなら、原価上はそれは3件です。前者は重複排除し、後者は別イベントとして残す。だからキーの粒度が効いてきます。用途に応じて、論理的なAIリクエスト・プロバイダへの試行・成功した生成・入力トークン・出力トークンのようにメトリクスを分けておくと、取りこぼしと二重計上の両方を避けやすくなります。
即時カウント層と課金層で二重計上の防ぎ方が違う
注意したいのが、2つの層で防ぎ方が別だという点です。課金層はDBの一意制約で弾けますが、Redisの単純なINCRには「このイベントはもう数えた」という記憶がありません。即時カウント層で冪等性を担保するには、処理済みイベントIDを短期間だけ別集合に持っておき、既知IDならINCRをスキップする、といった一手間が要ります(前掲のLuaはこの処理済みID集合を内側に持たせた形です)。ここを「課金層だけ守れば十分」と誤解すると、上限判定が過大にぶれます。層ごとに防御を用意する、と考えておくのが安全です。
AIリクエストのコスト計測とコストを下げるレバー
AIリクエストのコスト計測とは、呼び出し回数だけでなく、リクエストごとの使用量と金額を記録し、原価を可視化することです。AIリクエストは他のメトリクスと違い、1回あたりの原価が中身で変動します。LLMの多くはトークン量で、画像・音声などのAI機能では画像枚数・生成時間・音声時間といった別の使用量単位で。いずれにせよ、回数を数えるだけでは、コストの実態はつかめません。
トークン課金の計測はリクエスト単位で残す
LLM/AIのコスト計測に特化した可観測性ツールは、リクエスト単位でトークン使用量とコストを記録します。Langfuseは生成(generation)ごとの使用量とコストを追跡し、入力・出力トークンだけでなくキャッシュや音声・画像など複数の使用量タイプを扱えます。Heliconeはプロキシ経由でプロンプト・応答・トークン・コストを収集し、公式ドキュメントによればコスト算出用に300以上のモデルの価格情報を持つリポジトリを参照する構成になっています(変動しうる値のため参考値)。ただし、これらが算出する金額をそのまま顧客請求の正本にするなら注意が要ります。価格情報の更新時点、カスタム契約の単価、リージョン差、キャッシュ単価、プロバイダーの請求明細との照合。ここが合っていないと、可観測性ツールの推定額と実際の請求がずれます。
計測の実務では、リアルタイムとバッチのハイブリッドが現実解になります。ストリーミングで上限制御とダッシュボードをまかない、バッチ側のmediation層で検証・重複排除・業務ルール適用を通してから最終請求を確定する。速報値と確定値を役割分担させる、という点で、これまで見てきた即時カウントと課金記録の分離と同じ発想です。
自前LLM運用で「AIリクエスト」単価を下げる選択肢
コスト計測の先には、そもそも単価を下げるレバーもあります。ローカルでLLMを動かす構成です。n8nとローカルLLMランタイムのOllamaを組み合わせると、外部の推論APIへリクエストを送らずに処理を完結させる運用が可能になります。外部API課金が発生しないぶん、「AIリクエスト」あたりの変動費を下げられる可能性があります。
ただし、これは2つの意味で条件つきです。1つはコスト。外部API料金は消えますが、GPUの購入費や減価償却、電力、アイドル時間、保守、並列処理能力まで含めた総保有コストで見ると、利用量が少ないうちはクラウドAPIより高くつくこともあります。高い稼働率と適切なハードウェア構成があって初めて、1リクエストあたりの変動費が下がる、という条件つきの話です。もう1つはデータの経路。ローカルモデルに限定し、Web検索や外部連携といった機能を無効化している場合に限り、推論データが外部の推論APIへ出ない構成になります。外部モデルへのフォールバックや外部検索を有効にすれば、その経路ではデータが外部に出ます。「ネットワークから一切出ない」と無条件に断定するのではなく、「外部の推論APIへ送らない構成にできる」と、成立条件つきで捉えるのが正確です。なお料金・トークン単価は変動が速いため、具体的な金額は各公式の料金ページで都度確認するのが前提になります。
計測基盤を載せる自動化プラットフォームの選び方
プラットフォーム選定とは、計測・課金ロジックをどこにどこまで自前で実装できるか、という自由度で自動化基盤を選ぶ判断です。ツールによって「何を1カウントとするか」の定義が違い、これが計測設計の前提を左右します。
課金単位の定義は、サービスごとに異なります。n8nは本番のワークフロー実行(production execution)を基準に数え(ノード数は不問、手動テスト実行は通常この枠に含みません)、Zapierは成功したアクション1ステップを1タスクとして数えます。Makeは2025年8月27日にビジュアルの実行単位の呼称を「operation」から「credit」へ変更し、現在はcreditを課金単位にしています(多くのアクションが1credit、一部のAI機能などはより多く消費)。同じ自動化でも、数え方の基準がまるで違うわけです。この違いを踏まえずに計測ロジックを載せると、プラットフォームの課金と自前計測がずれます。
プラットフォーム別の課金モデルと計測実装の自由度
| プラットフォーム | 基本的な利用量単位 | カウントの注意点 | 向いている利用者 |
|---|---|---|---|
| Zapier | 通常アクション1成功=1タスク(基本ルール) | トリガー・Filter・Pathsや実行されなかったステップは対象外。AI by Zapier(モデル階層に応じて消費が変わる)・Code・MCP等は別ルール(例: MCPの成功ツール呼び出しは2タスク) | 非技術チーム・小規模な単純自動化 |
| Make | credit(旧operation)。多くの非AI機能は1実行=1credit | アクションは入力bundleごとに実行される(複数bundleなら複数消費)。トリガー・検索の数え方はモジュール種別で異なる。フィルターやルーターといったフロー制御自体はcreditを消費しない。一部AI機能はトークン量などに応じて動的に消費 | 複雑なワークフローを組む中小規模 |
| n8n | 本番ワークフロー実行(production execution)。ステップ数不問 | 手動テスト実行は通常この枠に含まない。ノード内の「Retry On Fail」は同一execution内の再試行(外部API利用は増え得る)、失敗executionの再実行は別カウント。セルフホストは実装自由度が高い | 技術者・独自課金ロジックを載せたい運用 |
具体的な月額料金や無料枠、ネイティブ連携数は変動が速く、二次情報も混じるため、ここでは金額の断定は避けます。設計の観点で言えば、自前の計測・課金レイヤーを深く作り込みたいなら、セルフホストで内部に手を入れられるn8nのような基盤が自由度で有利です。逆に、非技術チームが単純な自動化を回すだけなら、提供機能の範囲で完結するツールのほうが運用は軽くなります。いずれにせよ、料金体系と「何を1カウントとするか」は改定されることがあるため、実装前に各公式の料金ページで最新の定義を確認しておきます。
単発実装で終わらせない(テスト・保守性)
計測基盤は、作って終わりではありません。本番で通用するワークフローは、テストとデバッグ、そして他の担当者が引き継げるドキュメント化まで含めて初めて完成します。冪等キーの衝突、期間キーの境界、リトライ時の挙動、二重書き込みの部分失敗。こうした「壊れ方」を再現するテストを用意しておかないと、課金の誤りは本番で初めて表面化します。計測は請求に直結する以上、運用し続けられる形に保つこと自体が要件の一部です。
まとめ
利用量・コスト計測は、性質の違う2つの役割に分けるのが本番の定石でした。整理すると3点。まず、プラン上限を弾く即時カウント(Redis等の高速カウンタ)と、請求の根拠になる課金記録(DBのイベント履歴)を、論理的に(規模に応じて保存先も)分離すること。次に、テナント別に複数メトリクスを数え、「ワークフロー実行数だけ」の集計を避けること。そして、リトライによる二重計上を、取り込み時点の冪等キーで防ぐこと。この3つが揃って初めて、上限判定の即応性と請求の正確性が両立します。
進め方としては、まず計測イベントの最小構造(テナント・メトリクス・数量・時刻に加え、event_id・source・取り込み時刻)を固め、即時カウント層をRedisで、課金記録層を append-only なDBで組む。そのうえで、冪等キーは (source, event_id) の複合かつ実行内の呼び出しまで一意になる粒度で設計し、イベント配送の不整合はアウトボックス・厳密な上限制御はリザベーションで(必要なら併用して)押さえ、Redis側の加算・上限判定・失効はLua、またはRedis 8.8以降なら INCREX で原子的にまとめる。AIリクエストはトークン量まで残し、使用量の集計(metering)と料金評価(rating)・請求確定(invoicing)は分けておく。この順で足場を作ると、あとから料金体系やプラットフォームを変えても、集計をやり直すだけで対応できます。
よくある質問
Q. なぜRedisのカウンタを請求の唯一の正本にしてはいけないのですか?
Redisはメモリ保持が基本で、クラッシュ時に損失窓が残り得るためです。RDBスナップショットでは最後のスナップショット以降を失う可能性があり、AOFでも失う量は設定で変わります(everysecは一般に約1秒の損失窓、alwaysは損失可能性を大きく下げるが絶対的なゼロ保証ではない、noはOSの書き出しタイミング任せ)。多少の超過を許せるソフトクォータなら巻き戻りを許容できる場合もありますが、ハードなコスト上限やセキュリティ上の制限、そして1件の欠損が金額誤りになる請求には、単純なRedisカウンターだけは不向きです。確定値はDBのイベント履歴に残します(PostgreSQL並みの安全性が要るならRDBとAOFの併用も選べます)。
Q. リトライで二重計上が起きるのを防ぐ一番の方法は何ですか?
取り込み時点で冪等キーによる重複排除を行うことです。ただしキーの粒度に注意します。「実行ID+メトリクス種別」だけだと同一実行内の複数AI呼び出しが1件に潰れるため、実行ID・ノードID・呼び出しID・メトリクスまで含めるか、外部APIのリクエストIDを使います。DB側は主キー制約と衝突時スキップで同一キーの再記録を弾きます。
Q. AIリクエストの計測は、回数だけ数えれば十分ですか?
不十分です。AIリクエストは1回あたりの原価が中身で変動するためです。LLMの多くはトークン量ですが、画像・音声などでは画像枚数・生成時間・音声時間で課金されることもあります。少なくとも回数と、その使用量(トークン量など)の両方を保持します。回数はプラン上限の判定に、使用量は原価計算に使えます。別イベントに分けるか、1回の呼び出しイベントの属性としてまとめて持つかは、要件に応じて選びます。LangfuseやHeliconeのような可観測性ツールは、リクエスト単位で使用量とコストを残せます。

