How does Laravel's php artisan queue:work --queue=high,default prioritise jobs, and what can go wrong with that setup?
answer
- comma list, checked left to right
- every loop restarts at the first queue
- low queues can starve
- no --queue means the connection default
- separate workers per queue for isolation
basics
~20 sOn every loop the worker checks the listed queues in order and takes the first job found, so default runs only when high is empty. A busy high queue starves the rest, and unlisted queues are never polled.
solid answer
~40 s`--queue` takes a comma-separated list for one connection. On each iteration the worker walks the list left to right and pops from the first queue that has a job, then starts again from the left for the next job. That is **strict priority**, not weighting: while `high` always has work, `default` gets nothing. Without `--queue`, the worker drains only the connection's default queue (its `queue` key, `default` in the skeleton), so jobs sent with `->onQueue('transcode')` sit forever unless some worker lists `transcode`. Order in the list is the only knob; for isolation — slow transcoding must not block password-reset mails — run separate worker pools per queue, such as `--queue=mail` and `--queue=transcode`, each with its own process count and timeout.
code
bash · 5 lines# Mail first, then general work
php artisan queue:work redis --queue=mail,default --timeout=60
# Transcoding pool: long jobs, helps with default when idle
php artisan queue:work redis-long --queue=transcode,default --timeout=1700go deeper
Recall that --queue takes a comma list in priority order and that a worker only processes the queues it is given.
Explain the left-to-right walk on every iteration, why that starves lower queues, and the default-queue trap.
Design worker pools per class of work, with per-pool timeouts and a long-running connection, and monitor queue depth.
Decide how queue names map to capacity across the system, trading utilisation of shared pools against guaranteed isolation.
## What the flag means A Laravel **queue** is a named list of jobs on a connection. `php artisan queue:work redis --queue=high,default` starts one worker on the `redis` connection and gives it two queue names to consume. Jobs reach those queues through `->onQueue('high')`, a `#[Queue('high')]` class attribute, or the connection's default `queue` key. ## How the worker picks the next job Inside `Illuminate\Queue\Worker::getNextJob()` the list is split on commas and walked in order: 1. try to pop from `high`; 2. if `high` returned nothing, try `default`; 3. return the first job found, or nothing if every queue was empty. After the job finishes, the next iteration starts again at step 1. So the worker always prefers `high`, and `default` is only consulted when `high` is empty at that exact moment. Paused queues (`queue:pause`) are skipped in the walk. ## What goes wrong | Symptom | Cause | Fix | |---|---|---| | Jobs on `transcode` never run | no worker lists `transcode` | add it to a worker's `--queue` | | `default` backlog grows during bursts | `high` is never empty, so strict priority starves `default` | dedicated workers for `default` | | Password-reset mails wait behind 20-minute encodes | one pool shares short and long jobs | separate pools per queue | | Long jobs killed early | a pool's `--timeout` fits mail, not video | per-pool `--timeout` and a longer `retry_after` connection | | Priority order ignored | workers started with the order reversed | check the Supervisor `command` lines | The first row is the most common one in practice: a worker started as plain `php artisan queue:work` listens only to the connection's default queue, so a job dispatched to any other queue name is silently waiting. ## Designing worker pools Priority in one worker and isolation across workers solve different problems: - **Priority in one list** (`--queue=high,default`) is enough when all jobs are short and `high` is bursty but not constant. - **Separate pools** give each class of work guaranteed capacity: for example, four processes on `--queue=mail,default` and two on `--queue=transcode`, each with its own `--timeout`, `--memory` and `numprocs`. - **A shared fallback** such as `--queue=transcode,default` on the video pool lets idle transcoders help with general work without letting general work block transcoding. - **Long jobs on their own connection.** Because `retry_after` is set per connection, a transcoding queue that runs for many minutes usually lives on a connection whose `retry_after` exceeds its longest job. ## A worked layout for the transcoding app The video-transcoding app has three kinds of work: user-facing mail, general housekeeping, and encodes that run for up to 25 minutes. 1. Dispatch mail with `->onQueue('mail')`, housekeeping to the connection's `default` queue, and encodes with `#[Connection('redis-long')]` and `#[Queue('transcode')]` on the job class. 2. Run a Supervisor program of four workers with `queue:work redis --queue=mail,default --timeout=60`. 3. Run a second program of two workers with `queue:work redis-long --queue=transcode --timeout=1700`. 4. Give `redis-long` a `retry_after` of 1800 so encodes are never re-offered while still running. 5. Watch the size of `transcode`; when uploads peak, raise that program's `numprocs` rather than adding `transcode` to the mail workers. Mail now has four dedicated workers that no encode can occupy, and encodes have limits that fit them. ## Why there is no per-job priority Laravel has no priority number on a job. Within one queue, workers take jobs in the order the backend hands them out — oldest available first on the database driver, list order on Redis — and a delayed job simply becomes available later. Priority therefore exists only **between** queue names, expressed by the order of a worker's `--queue` list. That is why a design that needs "urgent" work splits it onto its own queue name at dispatch time, with `->onQueue('mail')` or a `#[Queue]` attribute, rather than tagging jobs inside one shared queue. ## Checking what a worker is doing - `php artisan queue:work -v` prints the connection and queue of every job it processes. - `php artisan queue:monitor redis:high,redis:default --max=100` reports queue sizes and fires a `QueueBusy` event when a queue exceeds the threshold. - The `jobs` table (database driver) or Horizon (Redis) shows which queues are growing. Priority lists are cheap and useful, but they are ordering, not capacity planning: when a queue must never wait behind another, give it its own workers.
- Does --queue=high,default give high a fixed share of the worker's time in Laravel?No. It is strict priority, not a ratio: every iteration starts with `high`, and `default` runs only when `high` is empty at that moment. If you need guaranteed capacity for each queue, run separate workers per queue, each with its own process count.
- Why do jobs dispatched with onQueue('reports') never run although workers are up?A worker without `--queue` consumes only its connection's default queue, and a worker with `--queue` consumes only the names listed. Unless some worker on that connection lists `reports`, those jobs wait indefinitely. `queue:work -v` output, or the queue's size, shows it.
saying these in an interview costs you the question
- Queues listed later in --queue get a proportional share of processing time
- A worker without --queue processes jobs from every queue on the connection
- The worker finishes the whole high queue before it ever re-checks it
- Queue priority is set per job with a priority property on the class
- One worker pool is enough as long as the priority order is right