handle() can now run twice and leave order 1042 paid once. Horizon is the dashboard and the process supervisor for a Redis queue. It shows how long jobs wait, which ones failed, and how many workers are busy. It is not a second queue worker you run beside queue:work.
Queues in production · Lesson 3
Horizon in production
Video
01 One supervisor, and terminate it on deploy
Configure the production supervisor with the queues it may touch, a timeout, and a cap on processes. auto balance adds workers when a queue is deep and removes them when it is quiet.
return [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['payments', 'default', 'mail'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 10,
'tries' => 3,
'timeout' => 60,
],
],
];payments is the queue onQueue('payments') chose in the job class lesson. A supervisor that omits it never runs the order.
Run php artisan horizon. Do not also run queue:work on payments, default or mail, or two systems will take the same jobs.
On deploy, run php artisan horizon:terminate. Horizon stops its workers after the current job, and the operating system’s process manager starts Horizon again so the workers load the new code. A worker that keeps the old code will keep the old bug.
Restrict the /horizon path to people who should see payloads. The dashboard shows job data, and the job class lesson already kept card numbers out of that data.
02 Close the dashboard to everyone else
Horizon registers a viewHorizon gate. An empty gate, in local, lets every visitor in. In production the gate should name who may open /horizon. A signed-in admin is enough. A guest receives 403.
class Example
{
protected function gate(): void
{
Gate::define('viewHorizon', function (User $user): bool {
return $user->is_admin === true;
});
}
}The gate does not hide the Redis queue. It hides the UI that prints payloads, wait times and failed exceptions. Keep the supervisor on redis, and keep queue:work off the same queues, or the dashboard’s process list will not match the workers that are actually running. After a deploy, horizon:terminate is what makes those workers pick up this gate and the rest of the new code.
minProcesses of 1 means a quiet queue still has a worker ready. maxProcesses of 10 is the cap when payments and mail are both deep. Raise the cap only when the Redis server and the database can take the extra connections. A worker holds a database connection for the whole job, and ten long payment jobs are ten connections.
Recap
You can run Horizon as the only supervisor, terminate it on deploy, and close the dashboard.
- Do not also run
queue:workon the same queues. horizon:terminatefinishes the current job, then the process manager starts the new code.viewHorizonis a gate. An empty gate in local is not the production rule.
Check yourself · 3 questions
Question 1 of 3You deploy new code and do not terminate Horizon. What do the workers run?
Where the series leaves you
Order 1042 has a path you can point at.
- The request returns before the provider answers.
- The job payload is the order id and the attempt.
- Retries wait, then stop.
- The idempotent handle() leaves the order paid once.
- The Horizon supervisor lists the payments queue.
Faking the queue in Test-driven Laravel is how you prove the job was pushed.