1. なぜ ChatGPT App にメトリクスと SLO が必要なのか
2 つのチームの状態を想像してください。
1 つ目は「たぶん動いている」という原則で生きています。ユーザーがサポートに苦情を入れたり、怒りのツイートを書いたりしない限りは OK。 時々だれかがログを開き、ひたすら並ぶ行をパラパラ見て、うなずいてタブを閉じる——そんな感じです。
2 つ目のチームには、いくつかのシンプルなダッシュボードがあります:
- p95(主要な MCP ツール)のレイテンシ。
- MCP と checkout の error‑rate。
- ウェブフックの可用性。
- ファネルにおける支払いへのコンバージョン。
さらに 3〜5 個の SLO があります: 「ツール recommend_gifts の呼び出しの 95% が 2 秒以内」、 「MCP‑tools のエラー比率 < 1%」、 「checkout の可用性 ≥ 99.5%」、 「ウィジェットから成功課金までのコンバージョン ≥ 10%」。
2 つ目の状態だと、あなたはこうなります:
- サポートに苦情が来る前でも、アプリに異常があることをすばやく把握できる;
- あらゆる変更の効果(新しいレコメンドアルゴリズム、SDK の移行、プロバイダの新料金)を測定できる;
- そして Store と審査のモジュールに進む頃には「品質基準があり、それにどの程度適合しているかを把握している」と胸を張って言える。
ここではとても当たり前のことを扱います。p95 とは何か、平均よりなぜ優れているのか、error‑rate と availability の数え方、ウェブフックを含む commerce 部分ではどんなメトリクスが重要か、そしてそれらからシンプルだが有用な SLO をどう組み立てるか。 これらはすべて、MCP のレコメンドツール・checkout・決済ウェブフックを備えた学習用アプリ GiftGenius を題材に進めます。
2. ChatGPT App の基本メトリクス: 何を測るべきか
レイテンシ: 「全体平均」ではなく p50/p95/p99
レイテンシとは、処理の開始から論理的な終了までの時間のことです。本スタックでは次のような操作があります:
- MCP ツール recommend_gifts の呼び出し(ギフトの選定);
- ACP で checkout intent を作るツールの呼び出し;
- 成功課金のウェブフックの処理。
ChatGPT でユーザーが体感する総遅延は、こちらが全面的にコントロールできるわけではありません。 プラットフォーム側の寄与(モデルの推論や OpenAI のネットワーク遅延)もあれば、こちら側の寄与(ツール、バックエンド、DB、決済)もあります。 レイテンシの SLI(Service Level Indicator — サービス品質の測定指標)としては、たいてい自分たちの部分、つまり App/MCP に入ってから自分たちのサーバが応答を返すまでを測ります。
ここで平均応答時間は欺瞞的です。もし 90% のリクエストが 100 ms で返り、10% が 5 秒かかると、平均は約 0.5 秒でグラフは「緑」に見えるかもしれません。 しかし 10 人に 1 人は 5 秒のフリーズを目にし、UX は確実に悪化します。
そこでパーセンタイルを使うのが一般的です。p50(中央値)、p95、p99。 p95 は「全体の 95% のリクエストがこの値より短い」という境界です。 SRE のガイドが強調するように、パーセンタイルは「外れ値を平均に隠さない」ため、常に分布の末尾に入ってしまう不運な 5〜10% のユーザーの現実を可視化します。
考え方のコツはこうです。p50 は「ふつうの」ユーザーの体験を示し、p95/p99 は「常にどこかが重い」忍耐強い人たちがどれくらい辛いかを示します。
Error‑rate: 失敗リクエストの割合
Error‑rate は、ある期間における失敗リクエスト数を総リクエスト数で割ったものです。 主なデータソースは、構造化ログか、ラベル status="success" | "error" を持つメトリクスです。
本スタックには、自然な単位での error‑rate がいくつかあります:
- MCP‑tools のレベル: recommend_gifts 呼び出しのうち、例外・外部 API の HTTP 5xx・タイムアウトなどで失敗した割合;
- ACP/checkout のレベル: 注文確定の試行が失敗した割合(API エラー、決済の不達など);
- webhook ハンドラのレベル: ウェブフックの処理が失敗で終わった割合。
やっかいなのは、ChatGPT App には「静かな」エラーが起きることがある点です。 たとえば MCP ツールがエラーを返したとき、モデルが謝って対話を続け、技術的なエラーをヘッダに出さない場合があります。 形式的には ChatGPT はユーザーに自然な返答を見せますが、信頼性の観点ではあなたのスタックは失敗しました。 したがって error‑rate は「最終的な UX としての GPT の返答」ではなく、エンジニアリング的なステータスで数えることが重要です。
Availability: サービスの可用性
Availability は、ある期間に成功裏に処理されたリクエストの割合(%)です。 概念的には error‑rate を裏返したもの、「失敗の割合」ではなく「成功の割合」を見ます。 古典的な例: availability = successful_requests / total_requests * 100%。
本スタックに当てはめると:
- MCP サーバの可用性: 直近 1 時間で、ChatGPT からの JSON‑RPC 呼び出しが正しく応答して終わった割合;
- checkout‑API の可用性: /api/checkout への呼び出しのうち HTTP 2xx で終わった割合;
- webhook エンドポイントの可用性: 決済からの受信ウェブフックに対し、正しい応答(通常は HTTP 200)を返せた割合。
もし決済プロバイダのドキュメントで可用性 99.9% を謳っているのに、あなたの数値が 97% なら、原因はたぶんあなた側です。
コマース系メトリクス: コンバージョンとファネル
GiftGenius は commerce のシナリオです。ここでは技術(高速・低エラー)だけでなく、ビジネス成果——つまりレコメンドがどれだけ支払い済み注文に変換されるか——も重要です。
コンバージョンファネルを見ます:
- ウィジェットが表示されたユーザー(view);
- ギフトを選んだユーザー(selection);
- 「注文手続き」ボタンを押したユーザー(checkout started);
- 注文ステータスが「支払い済み」になったユーザー(paid)。
このファネルから「ウィジェット表示 → 支払い完了」のコンバージョンを求められ、これも SLI(測定指標)になります。 例: 「1 日の『ウィジェットを見たユーザー数』に対する『成功課金数』の比 = 7%」。 もし突然レイテンシが増えたり、checkout がたまにタイムアウトで落ちたりすると、ファネルは思わぬ箇所で細くなります。
Instant Checkout を使っている場合
ここまでの commerce メトリクスは、Agentic Commerce Protocol ベースの Instant Checkout にもそのまま適用できます。 この場合は「自前の」/api/checkout の代わりに標準化された Agentic Checkout API があり、 ChatGPT はあなたの REST エンドポイント POST /checkout_sessions, POST /checkout_sessions/{id}, POST /checkout_sessions/{id}/complete (任意で cancel と GET)を呼び出し、 その都度、あなたから「真実の」カートとチェックアウト状態を受け取ります。
メトリクスの考え方は変わりません。p95 のレイテンシ、error-rate、availability を、自前の /api/checkout ではなく、これら標準エンドポイントで数えるだけです。 「p95 checkout レイテンシ < 3 秒、error-rate < 2%」といった SLO は、自作 API ではなく checkout_sessions 呼び出しに対して適用します。
別世界: ウェブフックのメトリクス
ウェブフックは非同期イベント(例: 決済からの payment_succeeded)で、注文のライフサイクルを進めます。 ウェブフックの処理が悪いと、アプリは素晴らしいレコメンドを見せても、注文は「支払い待ち」や不定状態で「止まる」ことがあります。
Stripe/決済と直接統合するのではなく Instant Checkout 経由で統合する場合は、決済の「ウェブフック」の役割を Agentic Checkout の order events が担います。 あなたのバックエンドが OpenAI に order.created や order.updated のようなイベントを専用の webhook URL に送ります。 これは同じクラスの実体で、最終的な注文ステータスがこれらの非同期イベントに依存する点は同じですが、相手先が自分のフロントエンドではなく ChatGPT になるだけです。
したがって、同じメトリクス——success rate、latency、error-rate——を Stripe のウェブフックではなく、OpenAI 側に送る自分の order‑event ウェブフックで数えます。 SLO には「99% 以上の order.* イベントが 7 日以内に配信・成功処理され、処理遅延の p95 は 500 ms 未満」のように、Instant Checkout に対して正しい SLI/SLO をそのまま書けます。
ウェブフックでは通常、次を見ます:
- webhook success rate — リトライを含め、ビジネスロジックが成功で終わったウェブフックの割合;
- webhook latency — ウェブフック受信から処理完了までの時間;
- webhook error rate — バリデーション失敗・DB 不可・タイムアウト等で処理が失敗した割合。
例えば次のように SLO を設定できます: 「7 日間で 99% 以上のウェブフックが成功処理される」 および「ウェブフック処理遅延の p95 は 500 ms 未満」。
3. GiftGenius でメトリクスを技術的に計測する方法
理論からコードに移りましょう。どこに計測を仕込めば、のちに p95 や error‑rate などの集計に使えるかを決めます。
特定の Prometheus や Datadog の話を持ち込まないため、ここではシンプルな logMetric 関数か JSON イベントのログ出力がある前提にします。あとは任意の監視ツールが上で必要なグラフを作ってくれます。
MCP ツールのレイテンシを計測する
TypeScript 製の GiftGenius MCP サーバに、ツール recommend_gifts があるとします。 ビジネスロジックをタイマーで囲みます:
// mcp/tools/recommendGifts.ts
import { logMetric } from "../observability/metrics"; // 仮のヘルパー
export async function recommendGiftsTool(input: RecommendInput) {
const startedAt = performance.now(); // 計測開始時刻
try {
const result = await recommendGifts(input); // ビジネスロジック
const duration = performance.now() - startedAt;
logMetric("tool_latency_ms", duration, {
tool: "recommend_gifts",
status: "success",
});
return result;
} catch (error) {
const duration = performance.now() - startedAt;
logMetric("tool_latency_ms", duration, {
tool: "recommend_gifts",
status: "error",
error_type: "exception",
});
throw error;
}
}
ここで logMetric は stdout に JSON ログを書くだけでも構いません:
// observability/metrics.ts
export function logMetric(
name: string,
value: number,
labels: Record<string, string | number>
) {
// 実際には Prometheus/DataDog のクライアントなどになる
console.log(
JSON.stringify({
type: "metric",
name,
value,
labels,
timestamp: new Date().toISOString(),
})
);
}
このアプローチなら tool_latency_ms のイベントストリームが得られ、ラベル tool と status を使って、成功呼び出しだけの p95 を出したり、逆にエラーで終わるリクエストの生存時間を見たりできます。
error‑rate を算出する
同様に、エラー用の専用メトリクスをログできます:
// 同じツールハンドラの中で
logMetric("tool_error_total", 1, {
tool: "recommend_gifts",
error_type: "external_api_timeout",
});
成功リクエストの場合:
logMetric("tool_success_total", 1, {
tool: "recommend_gifts",
});
そのうえでメトリクスシステム側で error_rate = tool_error_total / (tool_error_total + tool_success_total) を期間で集計します。 アプリ側は丁寧にイベントを吐くことだけが重要です。
もっとミニマルにしたいなら、error_total を別で出さずログだけでも構いません。 その場合の error‑rate は status フィールドから算出します。
checkout/ACP のメトリクス
Next.js の checkout エンドポイントでも考え方は同じです。ハンドラをタイマーで囲み、ステータスを数えます。
// app/api/checkout/route.ts
import { NextRequest, NextResponse } from "next/server";
import { logMetric } from "@/observability/metrics";
export async function POST(req: NextRequest) {
const startedAt = performance.now();
try {
const body = await req.json();
const result = await createCheckoutSession(body); // ACP/Stripe 呼び出し
const duration = performance.now() - startedAt;
logMetric("checkout_latency_ms", duration, { status: "success" });
logMetric("checkout_total", 1, { status: "success" });
return NextResponse.json(result, { status: 200 });
} catch (error) {
const duration = performance.now() - startedAt;
logMetric("checkout_latency_ms", duration, { status: "error" });
logMetric("checkout_total", 1, { status: "error" });
return NextResponse.json(
{ error: "Checkout failed" },
{ status: 500 }
);
}
}
これで checkout_latency_ms の p95 と、checkout_total に基づく error‑rate を確認できます。 これらの SLI から「p95 < 3 秒、error‑rate < 2%」のような SLO を簡単に設定できます。
ウェブフックのメトリクス
もちろんウェブフックもです。重要なメトリクス(セクション 2 参照)は既に述べましたが、ここでは実装の具体を見ます。 ここでは時間だけでなく「成功処理されたかどうか」も特に重要です。そうでないと、ユーザーが支払っても注文が「paid」になりません。
// app/api/webhooks/payment/route.ts
import { NextRequest, NextResponse } from "next/server";
import { logMetric } from "@/observability/metrics";
export async function POST(req: NextRequest) {
const startedAt = performance.now();
try {
const payload = await req.text(); // 生のデータ
const sig = req.headers.get("stripe-signature") || "";
const event = verifyStripeSignature(payload, sig); // 検証
await handlePaymentEvent(event); // 注文を更新
const duration = performance.now() - startedAt;
logMetric("webhook_latency_ms", duration, {
type: event.type,
status: "success",
});
logMetric("webhook_total", 1, {
type: event.type,
status: "success",
});
return new NextResponse("ok", { status: 200 });
} catch (error) {
const duration = performance.now() - startedAt;
logMetric("webhook_latency_ms", duration, {
type: "unknown",
status: "error",
});
logMetric("webhook_total", 1, {
type: "unknown",
status: "error",
});
return new NextResponse("error", { status: 500 });
}
}
これらのイベントに基づいて次のような SLI を定義できます:
- webhook success rate = success / (success + error);
- payment_succeeded に対する webhook_latency_ms の p95。
そしてそれらに対して SLO を設定します。例: 「7 日間で 99% のウェブフックが成功処理され、p95 は 500 ms 未満」。
4. パーセンタイル統計をもう少しだけ深掘り
これまでに p50/p95/p99 を何度か使いました。ここで少しだけ形式的に定義を見ておきます。 実際のパーセンタイル計算はメトリクスシステムがやってくれますが、裏で何が起きているかを知っておくのは有益です。
パーセンタイル pX は、「測定値の X% がこの値より小さい」という境界値です。 レイテンシの配列を昇順にソートすると、p95 は「尻尾」、つまり最大値に近い方に位置します。 コード(例えば Node で、ローカルで値集合の p95 を計算したくなった場合)は、概ね次のように書けます:
// パーセンタイルを計算する簡単な関数
export function percentile(values: number[], p: number): number {
if (values.length === 0) return 0;
const sorted = [...values].sort((a, b) => a - b);
const index = Math.ceil((p / 100) * sorted.length) - 1;
return sorted[Math.max(0, Math.min(index, sorted.length - 1))];
}
この関数はテストや簡単なスクリプトで使って、p95 が平均とどれくらい違うかを体感する用途に向いています。 本番では Prometheus、Datadog、New Relic といった大御所が面倒を見てくれます。
5. SLI、SLO、SLA をやさしく説明
難しそうに見える 3 文字ですが、中身はとてもシンプルです。
SLI — Service Level Indicator
SLI は具体的な測定指標、すなわち式です。例えば:
- 直近 24 時間のツール recommend_gifts の p95(latency);
- 直近 7 日間の MCP ツールの error‑rate;
- ウィジェット → 成功課金のコンバージョン(週単位)。
SLI は目標でも約束でもなく、単なる「体温計」です。
SLO — Service Level Objective
SLO は SLI に対する目標で、サービスの「健康条件」を定めます。例えば:
- 「p95(latency recommend_gifts) < 2 秒(7 日間のウィンドウ)」;
- 「すべての MCP‑tools の error‑rate < 1%(30 日間のウィンドウ)」;
- 「checkout API の可用性 ≥ 99.5%(四半期)」;
- 「ウィジェットから成功課金までのコンバージョン ≥ 15%(月間)」。
良いプラクティスは、まず現状を測定し、すでに維持できているレベルより少し厳しい SLO を置くことです。そうしないと「不可能ミッション」か「どうせ今のままで OK」に陥ります。
SLA — Service Level Agreement
SLA は内部目標ではなく、ユーザーやパートナーへの外部契約です。 SLA には義務(例: 「99.9% uptime」)と、違反時の結果(補償、ペナルティ、サブスク延長等)が書かれます。 多くの場合、SLO は SLA より厳しく設定され、「エラーバジェット」の余地を確保します。
学習用 GiftGenius の範囲では SLA は不要かもしれませんが、階段は理解しておきましょう:
SLI → SLO → SLA
メトリクス → 目標 → 外部への約束
Error budget(エラーバジェット)
Error budget は単純化すれば「許容される不完全さの量」です。 もし SLO が「30 日で可用性 99.9%」なら、0.1% が以下のために「使える」バジェットです:
- 計画メンテによるダウンタイム;
- 実験;
- 予期せぬインシデント。
バジェットを「使い切った」場合(例えば月間の実測可用性が 99.5% だった)、新機能を減速し、安定性の修復を優先すべきタイミングです。
6. GiftGenius に特有のメトリクス: commerce + webhooks
すべてを統合し、GiftGenius を 1 つのプロダクトとして眺めてみましょう。
ファネルとコンバージョン
ユーザーのシンプルな導線を仮定します:
flowchart TD A[GiftGenius のウィジェット付き App を開く] --> B[レコメンデーションを受け取る] B --> C[ギフトを選ぶ] C --> D[「支払いに進む」を押す] D --> E[支払い成功]
各ステップでイベントをログできます:
logMetric("funnel_step_total", 1, {
step: "widget_view",
});
logMetric("funnel_step_total", 1, {
step: "gift_selected",
});
logMetric("funnel_step_total", 1, {
step: "checkout_started",
});
logMetric("funnel_step_total", 1, {
step: "checkout_paid",
});
その後、widget_view と checkout_paid の件数からコンバージョンを算出します: paid / view * 100%。 そして SLO を「コンバージョン ≥ 10%」のように設定します。 もし突然コンバージョンが 3% に落ちたのに、技術的な SLI(latency/error‑rate)は正常に見えるなら、原因は UX、プロンプト/モデルへの指示、商品フィード品質などであり、インフラではない可能性があります。
コンバージョンの一部としてのウェブフックメトリクス
ウェブフックの技術メトリクスと実装については既に述べました。ここで重要なのはそれをコンバージョンに結びつけることです。commerce のシナリオではウェブフックが致命的に重要です。 ユーザーには「支払い完了」と見えても、最終的な注文ステータスはバックエンドが payment_succeeded をどれだけ速く・確実に受けて処理できるかに依存します。
SLI(ウェブフック):
- webhook success rate;
- webhook latency の p95;
- 大まかな可用性 webhook_endpoint_availability。
ウェブフックの success rate が落ちると、文字通り注文を失います。
7. SLO ベースのシンプルなアラート
本格的なアラートシステムは運用寄りのテーマですが、今の段階でも基礎は作れます。
アイデアは、各エラーごとにアラートを出すのではなく、SLO の逸脱に対して出すことです。 これは symptom‑based alerts(症状ベースのアラート)とも呼ばれます。あなたが知りたいのは「エラーが起きた」ではなく「サービスが約束より体系的に悪化し始めたか」です。
典型例:
- 直近 5 分間の MCP‑tools の error‑rate が 2% を超えた(SLO は 1% 未満);
- 直近 10 分間の p95(latency recommend_gifts) が 3 秒を超えた(SLO は 2 秒);
- 直近 15 分間、成功した checkout_paid が 1 件もない——統合の障害の疑い;
- 直近 10 分間の webhook success rate が 95% を下回った。
アラート本文は「Alert: metric 123 > 456」のような機械文ではなく、人間がすぐ理解できるものにしましょう。GiftGenius の「Slack 通知」例:
[ALERT][GiftGenius] High error rate on MCP tools:
error_rate = 3.2% (>1% SLO) for last 5 minutes.
Impact: part of users cannot receive gift recommendations.
Actions: check MCP logs and external gift API health.
このような通知は SLO と結び付いており、当番の開発者が影響範囲をすばやく把握できます。
8. ミニ演習: GiftGenius の SLO を考える
アプリ向けに SLO を作ってみましょう。メトリクスシステムを本格展開しなくても、紙に書くレベルで構いません。
「最初の一歩」の例:
- 主要な MCP ツール recommend_gifts について:
- SLI: 直近 7 日間の成功呼び出しの p95(latency)。
- SLO: p95(latency) < 2 秒。
- MCP ツールのエラーについて:
- SLI: 30 日間の error‑rate = errors / (errors + successes)。
- SLO: error‑rate < 1%。
- checkout API について:
- SLI: availability = 全リクエストに対する HTTP 2xx の割合。
- SLO: 月間の availability ≥ 99.5%。
- 決済ウェブフックについて:
- SLI: webhook success rate。
- SLO: 7 日間で 99% 以上の webhooks を成功処理し、処理遅延の p95 は 500 ms 未満。
- ビジネス成果について:
- SLI: 月間の「ウィジェット → 支払い済み注文」のコンバージョン。
- SLO: コンバージョン ≥ 10〜15%(業種により)。
この程度の小さなセットでも、意思決定の「背骨」になります。 たとえば新しいモデルをリリースしたり、プロンプトやレコメンドのアルゴリズムを変更しても、p95 と error‑rate が SLO の範囲内で、コンバージョンが上がっていれば、実験は成功と言えます。
自前の checkout エンドポイントの代わりに Instant Checkout を使う場合、「checkout API」は実質的に Agentic Checkout(/checkout_sessions の create/update/complete)呼び出しであり、「決済ウェブフック」は OpenAI 側に送るあなたの order events(order.created, order.updated)です。メトリクスや SLO の書き方は同じで、具体的なプロトコルだけが変わります。
9. メトリクスと SLO 運用の典型的な落とし穴
落とし穴 1: 平均応答時間だけを測る。
平均は便利ですが危険です。遅いリクエストの尻尾を簡単に隠してしまいます。 ChatGPT App では特に致命的です。大半のユーザーが速くても、少数の人に対する 5〜10 秒のラグが常態化すると、「いつも重いアプリ」という印象になります。 レイテンシでは必ず p50 と p95(可能なら p99)を使い、mean(平均)だけに頼らないでください。
落とし穴 2: SLI、SLO、SLA を混同する。
開発者が「当社の SLA は p95 < 2 秒」などと書くことがありますが、外部ユーザー向けに何の取り決めもなく、違反時の扱いも定まっていないケースがほとんどです。 それは実際には SLO です。 SLA は顧客との契約であり、違反時の結果があります。 混ぜてしまうと、実際に誰に何を約束しているのかが不明確になります。
落とし穴 3: アラートと SLO の紐付けがない。
ありがちな罠は、「すべてのエラー」「すべてのタイムアウト」「メトリクスの 0.01 の変動」にアラートを付けることです。 当番エンジニアは通知地獄となり、やがて誰も見なくなります。 より有用なのは SLO の逸脱でアラートすることです。error‑rate が目標レベルを大幅に超えたり、p95 が境界を外れたときに人を起こすべきです。
落とし穴 4: ウェブフックや非同期処理のメトリクスを無視する。
すでに触れた問題ですが、ウェブフックや非同期部分のメトリクスを見ないケースが多いです。 多くのチームは主要 API の HTTP 応答だけを監視し、ウェブフックやバッチ処理は見ていません。 commerce のシナリオでは、そこに最悪のバグが潜みます。支払いは通ってもウェブフックが処理されず、注文が「詰まる」。 ウェブフックの success rate と latency を見ないと、「動いているつもり」で、帳簿と DB の数字が大きく乖離し始めるまで気づけません。
落とし穴 5: 非現実的な SLO、あるいは SLO がない。
「100% uptime」「エラーは一切なし」といった SLO を掲げることがありますが、現実的ではなくチームを疲弊させます。 逆に、SLO をまったく定めず「たぶん大丈夫」で過ごすのも危険です。 まず現状の SLI を測り、わずかに厳しいが達成可能な目標を置き、システムの成熟とともに徐々に厳しくするのが中庸です。
落とし穴 6: プロダクトに結び付かない「メトリクスのためのメトリクス」。
ダッシュボードが大量にあり、p95 や p99 のグラフが何十枚もあっても、次の単純な問いに答えられないことがあります: 「この線が跳ねたら何が痛むのか? ユーザー? お金? それとも評判?」 特に LLM プロダクトでは、技術メトリクス(latency、error‑rate)とビジネスメトリクス(コンバージョン、成功課金)を結び付けることが重要です。 それを怠ると、オブザーバビリティは美しいだけのテレビになってしまいます。
GO TO FULL VERSION