Cloudflare Workers 無料プランのCPU時間超過エラー回避方法

Cloudflare Workers 無料プランのCPU時間超過エラー回避方法

※本記事にはプロモーション(広告・アフィリエイトリンク)を含みます。

Cloudflare Workers の無料プランで開発していると、ある時点で突然 Error: Worker exceeded CPU time limit が返ってきて処理が止まることがある。CPU 時間の上限は仕様として明確に決まっており、回避策も体系化されている。この記事では、上限の仕組みから wrangler を使ったボトルネック特定、コードレベルの対策、有料プランへの移行判断まで、再現できる手順でまとめる。

CPU時間とウォールクロック時間は別物

ここを誤解したままだと対策が的外れになる。

Cloudflare Workers が制限しているのは CPU 時間(実際に CPU を使った時間)であり、リクエストから応答までの ウォールクロック時間(経過時間)ではない。

外部 API への fetch() を含む処理では、ネットワーク待機中は CPU を使っていないため CPU 時間にカウントされない。一方、大きな JSON のパース・複雑な正規表現マッチ・暗号化処理は、実行中ずっと CPU を占有するため一気に消費する。

公式ドキュメント(Cloudflare Workers Limits)によると、各プランの CPU 時間上限は次のとおりだ(執筆時点)。

プラン CPU 時間上限 備考
無料プラン(Free) 10ms / リクエスト
Workers Paid($5/月) 50ms / リクエスト 月1,000万リクエストまで含む
Workers Unbound 最大 30秒 CPU 時間あたりの従量課金

エラーが出る典型的な原因

Error: Worker exceeded CPU time limit が発生しやすい処理を整理する。

大きな JSON のパース

数 MB に及ぶ JSON 文字列を JSON.parse() するとそれだけで数 ms 消費することがある。レスポンス全件を取得してからパースするのではなく、必要なフィールドだけを絞り込んで取得する設計が有効だ。

ループ内で繰り返しコンパイルされる正規表現

// NG: ループ内で毎回 RegExp オブジェクトを生成している
for (const item of items) {
  if (new RegExp(pattern).test(item.id)) {
    // ...
  }
}

// OK: モジュールスコープで一度だけコンパイルする
const RE_ID = new RegExp(pattern);
for (const item of items) {
  if (RE_ID.test(item.id)) {
    // ...
  }
}

正規表現リテラル(/pattern/)も同様に、ループの外で定義しておく。

暗号ハッシュ処理

SubtleCrypto で SHA-256 などを計算する場合、データ量が大きいと一気に CPU 時間を消費する。結果を KV にキャッシュして再計算を避けるか、外部サービスに委ねることを検討する。

大量レコードのメモリ内処理

KV や D1 から数百〜数千件のレコードを取得してソート・変換する処理は、件数が増えるほど CPU 時間が積み上がる。集計・ソートは D1 のクエリ側で行い、Workers 内の処理を最小限にする。

Wranglerでボトルネックを特定する

どの処理が CPU 時間を消費しているかを特定するには、performance.now() でラップして計測する。これはウォールクロック時間の計測だが、I/O 待機がなく CPU 処理が支配的な箇所では近い値になる。

export default {
  async fetch(request: Request): Promise<Response> {
    const t0 = performance.now();
    const result = await heavyProcess(request);
    console.log(`heavyProcess: ${(performance.now() - t0).toFixed(2)}ms`);
    return Response.json(result);
  },
};

ローカルでの動作確認には wrangler dev を使う。

npx wrangler dev src/index.ts

本番環境のログは wrangler tail でリアルタイムに確認できる。

npx wrangler tail <worker-name>

CPU 時間超過が発生すると、ログに exceeded CPU time limit というメッセージが記録される。エラーが出た時刻・URL・メソッドを照合することで、どのリクエストパターンがトリガーになっているかを絞り込める。ログにこのメッセージが見当たらない場合は、メモリ超過やタイムアウトなど別の原因の可能性があり、対策が変わる点に注意する。

無料プランのままできる回避策

1. 重い処理を外部バックエンドに委ねる

CPU を食う処理を Workers の外に出すのが最も効果的だ。自宅の Linux サーバーで Node アプリを systemd 常駐させて Cloudflare Tunnel でインターネット側に公開するような構成であれば、Workers はルーティングや軽量な前処理に徹し、重い計算はバックエンドサーバーに委ねる切り分けが有効だと考えられる。fetch() のネットワーク待機は CPU 時間に含まれないため、この設計変更だけで超過を回避できる可能性がある。

2. 結果を Workers KV にキャッシュする

同じ計算を繰り返しているなら、結果を KV に保存して次回以降のリクエストから返す。

const CACHE_KEY = `result:${cacheId}`;
const cached = await env.KV.get(CACHE_KEY, 'json');
if (cached) return Response.json(cached);

const result = await expensiveCompute();
await env.KV.put(CACHE_KEY, JSON.stringify(result), { expirationTtl: 300 });
return Response.json(result);

expirationTtl は秒単位。この例では 300秒(5分)でキャッシュが失効する。キャッシュの有効期限は用途に合わせて調整すること。

3. 正規表現をモジュールスコープで定義する

前述のとおり、正規表現はモジュールのトップレベルで一度だけコンパイルしておく。Workers は V8 アイソレートで動くため、モジュールスコープの定数は起動時に一度評価される(ただし、アイソレートの再利用タイミングはランタイムが制御するため、環境によって動作が異なる場合がある。公式ドキュメントも併せて確認すること)。

4. D1 クエリに集計・フィルタを任せる

Workers 内でソートや集計をするのではなく、D1(SQLite)のクエリで WHERE・ORDER BY・GROUP BY を使って絞り込んでから取得する。Workers に届く時点でデータ量を最小化することで、処理コストを下げられる。HonoでCloudflare D1を使う|TypeScript Bindingsの型定義の書き方では、D1 を TypeScript で扱う際の型定義の書き方を解説している。クエリ設計の参考になる。

5. Queues で処理を非同期化する

1リクエストで全件処理するのではなく、Cloudflare Queues を使って処理をキューに積み、バックグラウンドで小分けに実行する設計にすると、1リクエストあたりの CPU 時間を大幅に削減できる。Queues は Workers Paid 以上が必要になるが、処理の性質上どうしても分割できない場合の選択肢になる。

有料プランへの移行を検討するタイミング

上記の対策を施してもなお CPU 時間超過が続く場合、または処理の性質上 10ms に収めることが構造的に難しい場合は、有料プランへの移行を検討する。

状況 推奨プラン 月額(執筆時点)
50ms あれば収まる Workers Paid(Bundled) $5
秒単位の処理が必要 Workers Unbound 使用量に応じた従量課金
バッチ処理をキューに積みたい Queues Workers Paid に含まれる

Workers Paid は月 $5 から使え、CPU 時間が 10ms から 50ms に増えるだけでなく、KV・D1 などの無料枠も拡大する。最新の料金は公式の料金ページで必ず確認すること。

「たぶん CPU 時間が原因」という思い込みで先に有料プランへ切り替えても、実際には別の原因だったというケースもある。まず wrangler tail でログを取って原因を確認してからでも遅くない。

まとめ

  • Cloudflare Workers 無料プランの CPU 時間上限は 10ms / リクエスト。ウォールクロック時間ではなく実際の CPU 使用時間のみカウントされる
  • fetch() のネットワーク待機は CPU 時間に含まれない。重い処理を外部バックエンドに委ねるだけで超過を回避できる可能性がある
  • wrangler tail でエラーを捕捉し、performance.now() でどの処理が原因かを絞り込む
  • 正規表現のモジュールスコープ定義・KV キャッシュ・D1 クエリへの集計移行は、無料プランのまま試せる主な対策
  • それでも足りなければ Workers Paid(50ms)または Unbound(最大 30秒)への移行を検討する

次に取る行動: まず npx wrangler tail <worker-name> を実行して、ログに本当に exceeded CPU time limit が出ているかを確認する。別のエラーであれば対策が変わるため、ログを見ることを最初の一手にする。

よくある質問

Cloudflare Workers 無料プランのCPU時間の上限は何ミリ秒ですか?

公式ドキュメントによると、無料プランは1リクエストあたり10msです。ウォールクロック時間ではなく実際にCPUを使った時間のみがカウントされます。fetchなどI/O待機中はカウントされません(執筆時点)。

fetch()で外部APIを叩く処理はCPU時間に含まれますか?

含まれません。fetchのネットワーク待機中はCPUを使っていないためCPU時間にはカウントされません。重い処理を外部サービスに委ねてfetchで結果を受け取る設計にすると、CPU時間消費を大幅に削減できる可能性があります。

wrangler tail でCPU時間超過エラーはどう確認すればいいですか?

npx wrangler tail <worker-name> を実行すると本番環境のログがリアルタイムに流れます。CPU時間超過が発生すると「exceeded CPU time limit」というメッセージが記録されます。エラーの時刻・URL・メソッドと照合してトリガーとなるリクエストを特定します。

有料プランにするとCPU時間の上限はどれくらい増えますか?

公式ドキュメントによると、Workers Paid(月$5)のBundledプランでは50msになります。さらに長時間の処理が必要な場合はWorkers Unboundを選択でき、最大30秒まで実行できます(CPU時間あたりの従量課金、執筆時点の情報)。