Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Queues  ›  Lesson

Background Jobs — Slow Work Off the Request Path

Background Jobs 12 minBackground jobs keep requests fast by saving only the essentials before replying and sending must-happen extras as small queue messages that are processed and retried later.

Background Jobs — Slow Work Off the Request Path

When someone creates a Hoplink, the product team wants three extras: fetch the target page's title for a preview, render a QR code image into R2, and email the owner a confirmation. Together they take several seconds, and any of them can fail. Making the creator stare at a spinner for all that is poor design. The link itself is ready the moment it is saved; the extras can follow.

The rule

Save the essentials, enqueue the extras, return at once.

A background job is a message that describes work to do, plus a consumer that does it:

app.post("/links", async (c) => {
  const { code, url } = await c.req.json();
  await c.env.DB.prepare("INSERT INTO links (code, url, owner) VALUES (?, ?, ?)")
    .bind(code, url, "priya").run();                         // essential: now

  await c.env.JOBS.sendBatch([                               // extras: later
    { body: { type: "fetch-title", code } },
    { body: { type: "render-qr", code } },
    { body: { type: "email-owner", code }, delaySeconds: 60 },
  ]);

  return c.json({ created: code }, 201);
});

The consumer switches on type and does each job, acking or retrying as in the previous lesson. delaySeconds holds a message back before its first delivery — here, giving the owner a minute to edit the link before the email goes out.

Keep job messages small: send the code, not the whole page or image. The consumer loads what it needs from D1 or R2 when it runs. Queue messages have a size limit (128 KB), and small messages stay valid even if data changes in between.

Queues or waitUntil? Both run work after the response, but they promise different things:

  • ctx.waitUntil — runs in the same invocation, with no retries. If it fails or times out, the work is simply lost. Fine for "nice to have" things like a log line.
  • A queue — the message is stored durably, retried on failure, and processed in a separate invocation with its own time budget. Right for work that must eventually happen.

The shortcut

Sort each step of a request into three bins: needed for the reply → do it now; must happen but not now → queue; nice if it happens → waitUntil.

Sorting the link-creation stepsStep 1 of 4
  1. Save the link

    The reply says "created", so it must be true first. Do it now.

Trap: putting the essential step in the queue. If the reply says "created" but the insert is still waiting in a queue, the creator may visit their new link and get a 404.

Drills

Answer fast — trust the rule.

quick drill

Which step of creating a link must finish before replying "201 Created"?

quick drill

What does delaySeconds: 60 on a message do?

quick drill

Why send { type: "render-qr", code } instead of the finished QR image?

quick drill

Rendering the QR code sometimes fails and must be retried. Why is a queue better than ctx.waitUntil here?

Remember

  • Save the essentials, enqueue the extras, return at once.
  • Shortcut: needed now → do it; must happen later → queue; nice to have → waitUntil.
  • Trap: queuing the essential step, or waitUntil for work that must not be lost.

Practice — level up

0/1 solved

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

ExplainNow, queue or waitUntil?

When a Hoplink is created, the app saves the link, fetches the page title, renders a QR code into R2, emails the owner after a minute, and writes a debug log line. Sort each step and justify it.