Some work is too slow to make a user wait on: sending an email, processing an uploaded file, calling a slow model. Do it inside the request and the page hangs on a spinner, or the request times out before the work finishes. This chapter gets that slow work off the request, so your app stays snappy no matter how heavy the job.
10.7.1Not all work fits in one request
A web request has one job: take the input and return a response fast, ideally in well under a second. Slow work does not belong inside it. While the request waits on the file or the model, the user sits watching a frozen page, and if it waits too long the request times out and fails.
The fix is a background job: work your app does after it has already responded to the user. The request hands the slow part off and returns at once, then the job runs on its own time.
10.7.2Respond now, work later
So split the request in two. Accept the input, hand the slow part to the background, and respond at once. The user hears "we are on it" in the same instant, while the heavy work runs after the response is already sent.
That is why a well-built app feels fast even when the work behind it is not. The user gets an immediate, honest status instead of a spinner that might time out, and nothing on their screen is blocked on the slow job.
10.7.3A queue holds the work for a worker
Between your app and the slow work sits a queue: a line of jobs waiting to be run. Your app adds a job and moves on. A separate worker pulls jobs off the queue one at a time and runs each to completion, in its own process.
This is the same queue-and-worker setup that carries the load when you add more machines, aimed here at slow work instead of traffic. It buys retries almost for free: if a job throws, the queue hands it back to be run again, so a blip does not lose the work.
Which queue depends on what you already run. pg-boss keeps the jobs in the database you already have. BullMQ and Celery trade a second service to run for more throughput. Amazon SQS and Inngest run the queue for you.
Watch out: the queue can deliver a job more than once, so make each job safe to run twice. Sending the same email or charging a card twice does real damage; this is the same safe-to-retry rule your risky writes already need.
10.7.4Some jobs run on a clock, not a request
Half of a real product's background work has no user behind it at all: the nightly cleanup, the weekly digest, the trial that expires, the subscription that renews. That is a scheduled job, work that runs on a clock. Run it from your queue's own scheduler rather than a bare cron line on the host, so it inherits the same retries and the same record as every other job.
One trap catches everyone here. A scheduled run can fire twice, or be skipped entirely while a machine restarts, so write each one to be safe to run twice and safe to miss once.
10.7.5Tell the user when it is done
The work now finishes out of sight, so close the loop and tell the user when the job is done, or they are left wondering if it worked. Two honest ways:
- The worker writes a status the user's page checks, processing then done, and the page updates itself.
- The worker pushes a notification when it finishes: an email, or an in-app alert the user sees next time.
Either way the rule holds: never start background work without a way to report the result. A silent job that never reports back looks, to the user, exactly like one that failed.
This prompt moves your slow operations to the background:
Do this now: paste the prompt with your slowest feature, the one that makes users wait. Let your agent move it to a background job that reports back when it is done.