CloudflareのWorkers・KV・Pagesを無料プランのまま組み合わせて、小さなWebサービス(日本語SEOのキーワード分析ツール)を運用しています。維持費はほぼ0円で、この構成自体にはかなり満足しています。
ただ、公式ドキュメントには書いてあるのに設計前の自分が読み飛ばしていて、あとから踏んだ制限がいくつかありました。この記事では実際に踏んだ3つと、それぞれどう回避したかをまとめます。
なお、サービスを作った経緯そのものはZennに書いたので、この記事は技術的な落とし穴だけに絞ります。
① サブリクエストは1リクエストあたり50回まで
Workerの中から外部にfetchできる回数には上限があります。無料プランは1リクエストあたり50回です。
これを踏んだのは「キーワードの後ろに あ〜ん・a〜z・0〜9 を付けてサジェストを総当たり収集する」機能を作ったときでした。変形語は83個。Workerの中で83回fetchして1レスポンスにまとめる素直な実装は、50回の壁で動きません。
回避策はファンアウトをフロント側に移すことでした。
// フロント側: 変形語ごとに自前のWorkerを1回ずつ呼ぶ(同時4本に制限)
const queries = [seed, ...variants.map((c) => seed + " " + c)];
let idx = 0;
async function runner() {
while (idx < queries.length) {
const q = queries[idx++];
const res = await fetch("/api/suggest?keyword=" + encodeURIComponent(q));
// ...結果をマージ
await sleep(100); // 外部エンドポイントへの行儀
}
}
await Promise.all(Array.from({ length: 4 }, runner));
Worker側は「1リクエスト = 外部fetch1回 + KV読み書き」に収まるので上限に触れません。
結果的にこの設計のほうが良かった点もあって、キャッシュが変形語単位になるので、誰かが一度調べたキーワードは2回目から外部アクセスなしで返せます。実測で初回25秒、キャッシュ後2.5秒でした。
② KVの書き込み上限は読み取りの1/100
Workers KVの無料枠は次のとおりです。
| 操作 | 無料枠/日 |
|---|---|
| 読み取り | 100,000 |
| 書き込み | 1,000 |
| 削除 | 1,000 |
| リスト | 1,000 |
読み取り10万を見て「余裕がある」と思っていたら、書き込みは2桁少ない1,000でした。
上の総当たり収集は、新規キーワードなら1回で最大83件のキャッシュ書き込みになります。つまり1日12回実行されたら書き込み枠が尽きる計算です。
さらに厄介なのは、枠を超えたときの挙動です。put は例外を投げるので、「キャッシュを保存しようとしただけ」のつもりがAPI全体を500にします。
// キャッシュ保存はtry/catchで包む。書けなくても応答は返す
full = await source.fetchSuggest(query);
try {
await cache.set(key, full, TTL_SECONDS);
} catch {
// KV書き込み枠の超過など。キャッシュだけ諦める
}
これで落ちなくなりますが、延命でしかないので、最終的には書き込みを発生させる機能そのものにサーバー側で回数制限を付けました(後述のツールでは1日5回まで、と画面に明記しています)。
書き込み枠は「ヘビーユーザー1人で溶ける」量なので、KVに書く機能を無料公開するなら回数制限とセットで考えたほうがいいです。
③ workers.dev のサブドメインを変えると、旧URLは即NXDOMAINになる
これは制限というより運用の罠です。
workers.devのURLは <worker名>.<アカウントのサブドメイン>.workers.dev という形式で、アカウントのサブドメインはダッシュボードから変更できます。別のプロジェクトの都合で変更したところ、旧サブドメインのURLはリダイレクトされるわけではなく、DNSごと消えました(NXDOMAIN)。
フロントのビルドにAPIのURLを焼き込んでいたので、そのままだと全機能が沈黙するところでした。
助かったのは、デプロイスクリプトがWorkerのデプロイ出力からURLを毎回抽出してフロントのビルドに渡す作りにしていたことです。
DEPLOY_OUT="$(npx wrangler deploy 2>&1)"
WORKER_URL="$(echo "$DEPLOY_OUT" | grep -oE 'https://[a-zA-Z0-9._-]+\.workers\.dev' | head -1)"
VITE_API_BASE="$WORKER_URL/api" npm run build -w web
URLをハードコードせず、デプロイのたびに実際の値を取り直す。地味ですが、サブドメイン変更後も再デプロイ1回で追従できました。ドキュメントやREADMEに書いた旧URLだけが残骸になるので、そちらは手で直す必要があります。
まとめ
- サブリクエストは50回/req。ファンアウトはフロントに移すと、キャッシュ単位も細かくなって一石二鳥
- KVは読み取り10万/日に対して書き込み1,000/日。
putのtry/catchは必須、書き込みを生む機能には回数制限を - workers.devのサブドメイン変更は旧URLが即死する。URLはビルド時に動的に注入する
宣伝になってしまうので手短にしますが、この構成で運用しているのは「さちると」という無料のSEOキーワード分析ツールです(登録不要)。サジェストの総当たり収集や、記事がAI検索に引用されやすい構造かのチェックなどができます。踏んだ穴の実物を見たい方はどうぞ。
経緯や「なぜ無料ツールに制限を入れることにしたか」の話はZennに書きました。