In Laravel 13's config/queue.php, which queue drivers ship, and when would you move off the default database driver?
answer
- connection versus queue name
- QUEUE_CONNECTION=database, jobs table
- SKIP LOCKED on modern databases
- redis block_for, sqs visibility timeout
- deferred, background, failover, null
basics
~20 sThe skeleton defines sync, database, beanstalkd, sqs, redis, deferred, background and failover connections, defaulting to database. Move to Redis or SQS when polling the jobs table loads the main database, throughput grows, or you want Horizon.
solid answer
~40 s`config/queue.php` holds **connections** — a driver plus its settings — and each connection has a default **queue** name; jobs can go to other queue names on the same connection. The Laravel 13 skeleton sets `'default' => env('QUEUE_CONNECTION', 'database')` and defines `sync`, `database`, `beanstalkd`, `sqs`, `redis`, `deferred`, `background` and `failover`; `null` is also a supported driver. The `database` driver needs only the `jobs` table the skeleton already migrates, and on MySQL 8.0.1+, MariaDB 10.6+ and PostgreSQL 9.5+ it pops with `FOR UPDATE SKIP LOCKED`. It still polls your main database every few seconds per worker and shares its load and backups. Redis (`block_for`, Horizon) or SQS (visibility timeout, 15-minute max delay) fit higher throughput or isolation. `sync`, `deferred` and `background` run jobs inside the current process, so they need no worker.
code
ini · 3 linesQUEUE_CONNECTION=redis
REDIS_QUEUE=default
REDIS_QUEUE_RETRY_AFTER=90go deeper
Recall that config/queue.php lists connections, that QUEUE_CONNECTION picks the default, and that a new app uses the database driver.
Explain connections versus queues, how the database driver reserves rows, and which connections run in-process without a worker.
Judge when the database driver's polling and write load justify Redis or SQS, and plan the switch without stranding queued jobs.
Choose the queue backend for the organisation, weighing an extra service to operate against isolation, throughput and managed options.
## Connections and queues Two words are easy to confuse: - a **connection** is an entry in the `connections` array of `config/queue.php`: a driver (`database`, `redis`, `sqs`…) plus its settings; - a **queue** is a named list of jobs on one connection. Each connection's `queue` key names its default queue, and `->onQueue('emails')` sends a job to another one on the same connection. `QUEUE_CONNECTION` picks the default connection. A worker is started for one connection (`php artisan queue:work redis`) and one or more of its queues. ## What the Laravel 13 skeleton ships | Connection | Driver behaviour | Needs a worker? | |---|---|---| | `sync` | runs the job immediately, in-process | no | | `database` | rows in the `jobs` table; `retry_after` 90 | yes | | `beanstalkd` | Beanstalkd tubes; `retry_after` 90 | yes | | `sqs` | Amazon SQS; retries follow the SQS visibility timeout | yes | | `redis` | Redis lists and sorted sets; `retry_after` 90, `block_for` null | yes | | `deferred` | runs the job in the current process after the response | no | | `background` | runs the job in a spawned PHP process after the response | no | | `failover` | pushes to `database`, falling back to `deferred` if that fails | per listed connection | The `null` driver, which discards jobs, is also supported. Every connection that has one ships `'after_commit' => false`. The skeleton's `.env.example` sets `QUEUE_CONNECTION=database`, and its `0001_01_01_000002_create_jobs_table` migration creates `jobs`, `job_batches` and `failed_jobs`. If that migration is missing, `php artisan make:queue-table` generates it. ## How the database driver works - **Push** inserts a row with the payload, `queue`, `attempts`, `available_at` and `created_at`. - **Pop** selects the oldest row that is either unreserved and available, or reserved longer ago than `retry_after` seconds, locks it, and sets `reserved_at`. - On MySQL 8.0.1+, MariaDB 10.6+, PostgreSQL 9.5+ and Vitess 19+, the lock is `FOR UPDATE SKIP LOCKED`, so concurrent workers skip each other's rows instead of queueing on the lock. - A finished job's row is deleted. This is a solid default: no extra service, transactional pushes when the queue shares the app's connection, and good-enough throughput for many apps. ## When to move off it 1. **Load on the primary database.** Every idle worker polls every `--sleep` seconds, and every job is at least an insert, an update and a delete on the primary. Many workers or high job volume add real write load and lock traffic. 2. **Throughput and latency.** Redis pops are in-memory; with `block_for` a worker waits on the connection instead of sleeping between polls, so a new job starts almost immediately. 3. **Tooling.** Horizon, its dashboard and auto-balancing work only with Redis queues. 4. **Operational isolation.** A separate queue backend keeps a queue backlog from bloating the main database and its backups; `DB_QUEUE_CONNECTION` can at least point the database driver at another database connection. 5. **Managed scale.** SQS removes the queue server entirely, at the cost of its own rules: a visibility timeout instead of `retry_after`, a 15-minute maximum delay and a payload size limit (Laravel can store oversized payloads in a cache store through the `overflow` option). ## Comparing the common backends | | database | redis | sqs | |---|---|---|---| | Extra infrastructure | none | a Redis server | an AWS account and queue | | Waiting for work | poll every `--sleep` seconds | poll, or block for `block_for` seconds | poll the SQS API | | Lease on a running job | `retry_after` via `reserved_at` | `retry_after` via a reserved set | the queue's visibility timeout | | Transactional push with app data | yes, on the app's own connection | no | no | | Horizon | no | yes | no | The database driver's transactional push is a real advantage for small apps: a job inserted inside a transaction disappears with a rollback. Redis trades that for speed and Horizon; SQS trades it for running no queue server at all. Beanstalkd is also supported and needs the `pda/pheanstalk` package. ## Driver-specific gotchas - Redis needs phpredis or `predis/predis`, and the queue driver does not support the Redis `serializer` and `compression` options. - On Redis Cluster, queue names must use a hash tag such as `{default}` so all of a queue's keys land in one slot. - `block_for` of `0` blocks indefinitely and delays handling of `SIGTERM` until a job arrives. - `failover` needs a running worker for each listed connection that is not processed in-process. Moving off `database` is a configuration change — a new `QUEUE_CONNECTION` and workers started for the new connection — and jobs already sitting in `jobs` need a worker on the old connection until they drain.
- Why does the database queue driver use FOR UPDATE SKIP LOCKED when it can?Several workers select the next job at once. With a plain row lock they queue behind whoever locked the oldest row; with `SKIP LOCKED` each worker skips rows another has locked and takes the next one. Laravel uses it on MySQL 8.0.1+, MariaDB 10.6+, PostgreSQL 9.5+ and Vitess 19+, and falls back to a plain lock elsewhere.
- Do sync, deferred and background connections need a queue worker in Laravel?No. All three process the job inside the PHP process that dispatched it: `sync` immediately, `deferred` after the response, and `background` in a separately spawned PHP process after the response. The docs state that no worker is needed for them, which also means none of them retries through a worker.
saying these in an interview costs you the question
- A new Laravel 13 app queues to Redis unless you configure otherwise
- A connection and a queue are two names for the same thing
- The database driver needs a separate database server to work
- SQS connections use retry_after exactly like the database driver
- Horizon can manage workers for the database queue driver