Queues and Workers
Connections, queues and drivers in config/queue.php, starting a worker, and why a deploy needs queue:restart.
php artisan queue:work [connection] --queue=high,default --tries=3 --timeout=60 --max-jobs=1000A queue driver is the storage your jobs sit in between dispatch and execution. Laravel calls one configured driver a connection, and each connection holds named queues, which are separate stacks of jobs inside it. config/queue.php defines the connections. QUEUE_CONNECTION in .env selects the default one.
The drivers you will meet
sync runs the job immediately, in the dispatching process, before dispatch() returns. Nothing is stored and no worker exists, so an exception in the job reaches the caller. database stores jobs as rows; php artisan make:queue-table then php artisan migrate creates the table. redis stores them in Redis and is what to use under real load. sqs and null are also available; null discards everything.
Workers
A worker is a long-running PHP process that pops jobs and calls handle(). Start one with php artisan queue:work. With no argument it uses the default connection, so that command is shorthand for naming the connection yourself: php artisan queue:work redis. Both are the same process. --queue=high,default drains high completely before it looks at default.
Why a deploy breaks workers
queue:listen boots the framework again for every job. It is slower but picks up edits at once, which suits local work. queue:work loads your classes once and holds them in memory for its whole life. A deploy therefore changes nothing for a running worker. php artisan queue:restart signals workers to exit after their current job, and your process monitor starts them again on the new code.
Example
<?php
// config/queue.php (excerpt)
return [
// Which connection dispatch() uses when none is named.
'default' => env('QUEUE_CONNECTION'),
'connections' => [
// Runs the job inline. No storage, no worker.
'sync' => [
'driver' => 'sync',
],
// One row per waiting job in the 'jobs' table.
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', '{default}'),
'retry_after' => env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => 5,
'after_commit' => false,
],
],
];When to use it
- Switch a local project from sync to database so you can watch a job actually queue.
- Give invoicing its own high queue so billing is never stuck behind report generation.
- Add queue:restart to the deploy script after noticing workers running week-old code.
- Drain the queue in CI with queue:work --stop-when-empty before asserting on results.
More examples
Getting the database driver working
The database driver needs no extra service, which makes it the right first choice. Its cost is a row insert and a locking select per job, so it stops scaling somewhere in the low hundreds of jobs a minute. Reach for Redis before that point, not after.
# Create the jobs table migration, then run it.
php artisan make:queue-table
php artisan migrate
# .env
# QUEUE_CONNECTION=database
# Start a worker. No connection argument, so it reads
# 'default' from config/queue.php, which reads QUEUE_CONNECTION.
php artisan queue:work
# Exactly the same process, with the connection named.
php artisan queue:work database
# Dev loop: reboots the framework per job, so edits apply at once.
php artisan queue:listen
# After a deploy: workers exit when their current job finishes.
php artisan queue:restartPriority queues
Queue names are strings you invent; nothing creates them in advance. The order in --queue is strict: the worker only looks at default when high is empty. That means one flooded low queue cannot delay a payment, but it also means a permanently busy high queue starves the others, so give low work its own worker process when that is a risk.
<?php
use App\Jobs\ChargeInvoice;
use App\Jobs\RebuildSearchIndex;
// Per dispatch
ChargeInvoice::dispatch($invoice)->onQueue('high');
RebuildSearchIndex::dispatch()->onQueue('low');
// Or set a default for the class in a service provider's boot method
// (Laravel 13):
use Illuminate\Support\Facades\Queue;
Queue::route(RebuildSearchIndex::class, queue: 'low');
// Run a worker that always clears 'high' first:
// php artisan queue:work redis --queue=high,default,lowA worker is a loop you can bound
Long-lived PHP processes accumulate memory. Rather than hunting the leak, bound the process and let the monitor restart it; --memory=128 exits when the process passes 128 MB. --stop-when-empty is what you want in a deploy script or a test pipeline that must finish.
# Stop after 1000 jobs or one hour, whichever comes first.
# A process monitor restarts it, which releases leaked memory.
php artisan queue:work redis --max-jobs=1000 --max-time=3600 --memory=128
# Process one job and exit. Handy in a cron-only environment.
php artisan queue:work --once
# Drain the queue and exit. Useful in CI.
php artisan queue:work --stop-when-empty
# Wait 3 seconds between polls when the queue is empty.
php artisan queue:work --sleep=3
Discussion