Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Queues  ›  Lesson

Queues — Hand Off Now, Process Later

Message Processing 15 minA producer sends messages to a queue and a consumer receives them in batches, acking or retrying each, with at-least-once delivery that calls for idempotent processing.

Queues — Hand Off Now, Process Later

Every Hoplink redirect creates a click record: code, time, country. Writing each one to D1 inside the redirect adds a database round trip to every visit, and a burst of traffic from one viral post becomes a burst of writes. Cloudflare Queues let the redirect drop a small message in a queue and move on; a consumer later takes messages off in batches and writes them efficiently.

The rule

Producers send, consumers receive in batches, every message gets acked or retried.

Producer. Any Worker with a queue binding can send a message — any value that can be serialised, such as a JSON-like object:

// in the redirect handler
await env.CLICKS.send({ code, country: request.cf?.country, at: Date.now() });
return Response.redirect(target, 302);

Consumer. A Worker exports a queue handler. The platform delivers messages in batches:

export default {
  async queue(batch, env, ctx) {
    for (const msg of batch.messages) {
      try {
        await saveClick(env, msg.body);
        msg.ack();              // done, never deliver again
      } catch {
        msg.retry();            // try this one again later
      }
    }
  },
};

The config ties the two together:

{
  "queues": {
    "producers": [{ "binding": "CLICKS", "queue": "click-events" }],
    "consumers": [{ "queue": "click-events", "max_batch_size": 50, "max_batch_timeout": 5 }]
  }
}

A batch is delivered when it reaches max_batch_size messages or when max_batch_timeout seconds pass, whichever comes first.

The gain is decoupling. The redirect no longer waits on D1, and if D1 is slow or briefly down, clicks pile up safely in the queue instead of failing. Producer and consumer can even be separate Workers, deployed and scaled on their own.

Key rules of delivery:

  • Explicit acks. msg.ack() and msg.retry() settle one message; batch.ackAll() and batch.retryAll() settle them all.
  • Throwing retries the batch. If the handler throws, every message not already acked is delivered again.
  • At least once. A message can occasionally arrive more than once. Consumers should be idempotent: processing the same message twice must not double-count. Each message has a unique msg.id you can record to skip repeats.

The shortcut

Read every consumer with one question per message: "Where is this message's ack or retry?" Every path through the loop must end in exactly one of them — or deliberately let an error retry the batch.

Following one click through the queueStep 1 of 4
  1. Send

    The redirect handler calls env.CLICKS.send({...}) and returns the redirect straight away.

Trap: acking at the top of the loop "to be safe". If the save then fails, the message is already gone.

Drills

Answer fast — trust the rule.

quick drill

Which method does a Worker call to put a message on the CLICKS queue?

quick drill

With max_batch_size: 50 and max_batch_timeout: 5, only 12 messages arrive in 5 seconds. What happens?

quick drill

Why must a click consumer be idempotent?

quick drill

The consumer acks message A, then throws while handling message B. What is redelivered?

Remember

  • Producers send, consumers receive in batches, every message gets acked or retried.
  • Shortcut: find each message's ack or retry on every path.
  • Trap: acking before the work succeeds, and forgetting duplicates can arrive.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainThe click pipeline

Explain how Hoplink records clicks through a queue instead of writing to D1 during each redirect, covering the producer, batching, acks and retries, and how duplicates are prevented.