Cloudflare Queuesの仕組みと特徴——Cronと勘違いしやすい理由
Cloudflare Queuesは、名前や「バックグラウンドで何かを処理する」という説明だけを見ると、定期実行の仕組みだと誤解されがちです。しかし実態はCron Triggersとは発火の仕組みからして全く別物です。ここではQueues自体の仕組みと特徴を紹介しながら、なぜCronと混同しやすいのか、実際は何が違うのかを見ていきます。
Queuesの仕組み
Queuesはproducer(送信側)とconsumer(受信側)の2つのWorkerで構成されるメッセージキューイングシステムです。producer側でメッセージをenqueueすると、Cloudflareのキューにいったん積まれ、consumer側のWorkerがqueue()ハンドラでそれを受け取って処理します。
// wrangler.jsonc — producer側
{
"queues": {
"producers": [
{ "queue": "ingest-jobs", "binding": "INGEST_QUEUE" }
]
}
}// producer: メッセージをenqueueする
export const POST: RequestHandler = async ({ request, platform }) => {
const job = await request.json();
await platform!.env.INGEST_QUEUE.send(job);
// enqueueした時点で即座にレスポンスを返せる
return new Response(null, { status: 202 });
};// wrangler.jsonc — consumer側
{
"queues": {
"consumers": [
{ "queue": "ingest-jobs", "max_batch_size": 10, "max_retries": 3 }
]
}
}// consumer: queue()ハンドラでメッセージを処理する
export default {
async queue(batch: MessageBatch<IngestJob>, env: Env) {
for (const message of batch.messages) {
try {
await processJob(message.body, env);
message.ack();
} catch (err) {
message.retry();
}
}
},
};Queuesの特徴
- イベント駆動: enqueueという「実際に起きた出来事」に反応して動きます。何かが起きるまでは何もしません。
- バッチ処理:
max_batch_sizeやmax_batch_timeoutの設定に応じて、複数のメッセージをまとめてconsumerに渡せます。1件ずつ処理を起動するオーバーヘッドを避けられます。 - リクエストのライフサイクルから切り離される: producer側はenqueueした時点で処理を終えてレスポンスを返せるため、実際に重い処理はユーザーのリクエストを待たせません。
- リトライとDead Letter Queueが組み込み:
message.retry()で明示的に再試行を指示でき、max_retriesを超えたメッセージは設定していればDead Letter Queue(DLQ)に送られます。失敗時の扱いを自前で実装する必要がありません。
一見Cronに見えるけれど、実は全く違う
QueuesもCron Triggersも「リクエストの外側で非同期に何かをする」という点では似ているため、混同しやすいのですが、トリガーの性質が根本的に異なります。
- Queues: 「メッセージがenqueueされた」というイベントに反応する
- Cron: 「実世界の時計が指定時刻に一致した」という時刻の条件に反応する
この違いは、ローカル開発での挙動にそのまま表れます。
- Queues(ローカル): メッセージをenqueueすると、Miniflareが自動的にconsumer Workerの
queue()ハンドラを呼び出します。手動で何かを叩く必要はなく、実際のイベント駆動の動きがそのままローカルで再現されます。 - Cron(ローカル):
scheduled()ハンドラは時計を見て自動発火することはありません。/__scheduledエンドポイントを自分で叩かない限り、永遠に呼ばれません。
# ローカルでCronトリガーを手動発火する
curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"Queuesは「enqueueされた」という事実そのものに反応するだけなので、ローカルでもそのまま成立します。一方Cronは、wrangler devが時刻を監視する常駐スケジューラーを回しているわけではないため自動発火せず、そもそも本番側も最大15分の伝播遅延があるゆるい仕組みなので、ローカルで厳密に再現する意味自体が薄いという事情もあります。だからこそ「テスト用に手動で発火を模倣するエンドポイント」が用意されている、という設計になっています。
使い分けの基準
「定期的に何かをする」のがCron、「何かが起きたら非同期に処理する」のがQueues、と考えると整理しやすくなります。両者は排他的ではなく、Cronで定期的にジョブを洗い出してQueuesにenqueueする、という組み合わせもよく使われるパターンです。Alcogyでも、時刻起点でまとめて発生するジョブはCronで拾い上げ、それを実際に処理する部分はQueuesのconsumerに任せる、という形で役割を分けています。
Queueコンシューマーも通常のWorkerである以上、CPU時間制限(デフォルト30秒、limits.cpu_msで最大300秒まで引き上げ可能)は健在です。「Queuesに入れれば処理時間を気にしなくていい」わけではない点は注意が必要ですが、それはまた別の話として、Queues自体は「時刻ではなくイベントに反応する非同期処理の仕組み」だと理解しておくと、Cronとの混同を避けられます。