Workers Analytics Engine 運用
業務イベント (検索 / 閲覧 / コンバージョン) を Cloudflare Workers Analytics Engine (CAE) に書き出して、SQL API で集計する。
なぜ CAE か
| 用途 | Workers Logs | OTel | Sentry | CAE |
|---|---|---|---|---|
| 検索クエリの集計 | △ | × | × | ◎ |
| LCP / レイテンシ階層 | × | ◎ | × | ○ |
| 5xx の即時通知 | △ | × | ◎ | × |
| 業務 KPI ダッシュボード | × | × | × | ◎ |
| 90 日無料保管 | × (3d) | 課金 | 課金 | ◎ |
CAE は「サンプル不要の業務指標」と「SQL で柔軟に集計したい」要件に最適。
binding
wrangler.toml の各 env / 各 split file で:
[[env.dev.analytics_engine_datasets]]
binding = "ANALYTICS"
dataset = "parky_business_events"
[[env.prod.analytics_engine_datasets]]
binding = "ANALYTICS"
dataset = "parky_business_events_prod"
dataset 名は dev / stg / prod で分離(混入防止)。binding 未設定でも helper は no-op で動くので開発用 wrangler dev で安全。
helper
parky/api/src/lib/analytics-engine.ts
主要 API
import {
recordSearchEvent,
recordViewEvent,
recordActionEvent,
writeBusinessEvent,
} from "../lib/analytics-engine";
// 検索イベント
recordSearchEvent(c.env, {
kind: "ai", // "ai" / "lot" / "media"
query: body.message,
resultCount: 1,
channel: "mobile",
userId,
latencyMs: 230,
});
// 閲覧イベント
recordViewEvent(c.env, {
resource: "lot",
resourceId: lot.code,
channel: "web",
userId,
});
// 行動イベント (お気に入り / 経路選択 / 通知 ON)
recordActionEvent(c.env, {
action: "favorite.add",
channel: "mobile",
userId,
metadata: { lot_code: "shibuya-001" },
});
// 任意イベント
writeBusinessEvent(c.env, {
event: "sponsor.click",
channel: "web",
tags: { campaign_id: "spring-2026" },
metrics: { count: 1, revenue_yen: 500 },
indexKey: userId,
});
スキーマ
CAE の各 datapoint は次の 3 配列で構成される。helper は内部で詰める:
| Slot | 用途 | 上限 |
|---|---|---|
blobs[0] |
event 名 (例 search.ai) |
必須 |
blobs[1] |
channel | — |
blobs[2] |
locale | — |
blobs[3..19] |
tags ("key=value" 形式) | 17 個 |
doubles[0] |
count (default 1) | — |
doubles[1..19] |
sub metric | 19 個 |
indexes[0] |
高 cardinality 索引 (user_id 等) | 1 個 / 96 文字 |
集計クエリ
Cloudflare Dashboard で SQL を直接実行できる。
例: 直近 24h の AI 検索 latency P95
SELECT
blob1 AS channel,
quantileTDigest(0.95)(double2) AS p95_latency_ms,
count() AS hits
FROM parky_business_events
WHERE timestamp > NOW() - INTERVAL '1' DAY
AND blob1 = 'mobile'
AND blob0 = 'search.ai'
GROUP BY blob1
ORDER BY hits DESC;
例: 駐車場 detail view ランキング
SELECT
index1 AS lot_code,
count() AS views,
uniqExact(index1) AS unique_users
FROM parky_business_events
WHERE timestamp > NOW() - INTERVAL '7' DAY
AND blob0 = 'view.lot'
GROUP BY index1
ORDER BY views DESC
LIMIT 50;
コスト
- 90 日まで無料保管
- write: 100 万 datapoint / 月まで無料、超過後 $0.25 / 100 万
- read (SQL API): query 1 回 = 100 万行スキャン まで無料、超過後 $1 / 100 万 行
Parky の現在の write rate (検索 + 閲覧) で月 50 万 datapoint 程度の見込み。
既存導入箇所
| 場所 | event |
|---|---|
| bff/web/search.ts | search.ai |
順次拡張: lot detail view / favorite / directions / sponsor click / push opt-in 等。
トラブルシュート
datapoint が出ない
- binding 未設定 → wrangler.toml を確認 (
grep ANALYTICS api/wrangler*.toml) - helper を呼んでない → grep
recordSearchEvent\|writeBusinessEvent - Cloudflare Account のプラン制限 → Workers Paid plan が必要 (Free でも binding は使えるが SQL API が無効)
SQL に出てこない
- 反映まで数秒のラグあり (CAE は eventually consistent)
- dataset 名間違い →
parky_business_events(dev) /parky_business_events_prod(prod)