Queues and background jobs

Set up a Laravel Redis queue: dispatch background jobs, run queue:work, and retry failed jobs with queue:retry. Why Redis lists fit a queue.

Some work does not need to happen while the user waits. Sending an email, resizing an image, or calling a slow API can run in the background. Laravel calls this a queue, and a Laravel Redis queue is one of the fastest ways to store that pending work until a worker gets to it.

Point Laravel's queue at Redis

You already have Redis wired into Laravel from Chapter 5. Switching the queue over is one line in your .env:

QUEUE_CONNECTION=redis

Now every job you dispatch is stored in Redis instead of the default sync or database driver. Nothing else in your code has to change.

Dispatch a job

A job is a small class that does one piece of work. Generate one with php artisan make:job SendWelcomeEmail, then dispatch it from anywhere:

use App\Jobs\SendWelcomeEmail;

SendWelcomeEmail::dispatch($user);

That call returns almost instantly. Laravel serialises the job, pushes it into Redis, and your controller responds right away. The actual email is sent later, by a separate process.

Run a worker with queue:work

Something has to pull jobs back out and run them. That is the worker:

php artisan queue:work

Leave it running. It watches the Redis queue, picks up each job in order, runs its handle() method, and removes it when done. In production you run this under a process manager like Supervisor so it restarts if it ever stops.

Why Redis lists fit a queue

A queue is first in, first out: the oldest job runs next. That is exactly what a Redis list gives you. Laravel pushes new jobs onto one end and the worker pops them off the other. Both operations are fast and atomic, so two workers never grab the same job. You already saw lists in Chapter 3; the queue is that data type doing a real job.

One detail worth knowing: a delayed job (SendWelcomeEmail::dispatch($user)->delay(60)) does not sit in the list. Laravel parks it in a sorted set scored by the time it should run, then moves it into the list once that time passes. So the Redis queue driver is really a list plus a sorted set working together, not a single structure.

Failed jobs and retries

Jobs fail. An API times out, a record is missing, the network blips. When a job throws an exception, Laravel can retry it a set number of times:

php artisan queue:work --tries=3

After the last attempt fails, the job is moved to a failed_jobs table so it is not lost. You can inspect failures and push them back onto the queue once you have fixed the cause:

php artisan queue:failed
php artisan queue:retry all

queue:retry all re-queues every failed job; pass an id to retry just one.

Common mistake

Changing a job's code and expecting the running worker to notice. A worker loads your code once and keeps it in memory, so old jobs keep running the old logic. After any deploy or code change, restart the worker:

php artisan queue:restart

This tells workers to finish their current job and shut down cleanly, ready to be started again with fresh code.

FAQ

Do I need a worker running all the time?

Yes, for jobs to actually run. If no worker is running, jobs pile up in Redis and wait. They are not lost, they just sit there until a worker picks them up.

What is the difference between a queue and rate limiting?

They both use Redis but solve different problems. A queue defers work to run later; rate limiting counts requests to block abuse. Different patterns, same store.

Where did the job go after I dispatched it?

Into a Redis list. You can peek at it with the redis-cli console before a worker runs, though Laravel manages the exact key names for you.