Laravel Horizon

Introduction

lightbulb

Before digging into Laravel Horizon, you should familiarize yourself with Laravel's base queue services. Horizon augments Laravel's queue with additional features that may be confusing if you are not already familiar with the basic queue features offered by Laravel.

Laravel Horizon provides a beautiful dashboard and code-driven configuration for your Laravel powered Redis queues. Horizon allows you to easily monitor key metrics of your queue system such as job throughput, runtime, and job failures.

When using Horizon, all of your queue worker configuration is stored in a single, simple configuration file. By defining your application's worker configuration in a version controlled file, you may easily scale or modify your application's queue workers when deploying your application.

Installation

lightbulb

Laravel Horizon requires that you use Redis to power your queue. Therefore, you should ensure that your queue connection is set to redis in your application's config/queue.php configuration file.

You may install Horizon into your project using the Composer package manager:

1composer require laravel/horizon
1composer require laravel/horizon

After installing Horizon, publish its assets using the horizon:install Artisan command:

1php artisan horizon:install
1php artisan horizon:install

Configuration

After publishing Horizon's assets, its primary configuration file will be located at config/horizon.php. This configuration file allows you to configure the queue worker options for your application. Each configuration option includes a description of its purpose, so be sure to thoroughly explore this file.

lightbulb

Horizon uses a Redis connection named horizon internally. This Redis connection name is reserved and should not be assigned to another Redis connection in the database.php configuration file or as the value of the use option in the horizon.php configuration file.

Environments

After installation, the primary Horizon configuration option that you should familiarize yourself with is the environments configuration option. This configuration option is an array of environments that your application runs on and defines the worker process options for each environment. By default, this entry contains a production and local environment. However, you are free to add more environments as needed:

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 'maxProcesses' => 10,
5 'balanceMaxShift' => 1,
6 'balanceCooldown' => 3,
7 ],
8 ],
9 
10 'local' => [
11 'supervisor-1' => [
12 'maxProcesses' => 3,
13 ],
14 ],
15],
1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 'maxProcesses' => 10,
5 'balanceMaxShift' => 1,
6 'balanceCooldown' => 3,
7 ],
8 ],
9 
10 'local' => [
11 'supervisor-1' => [
12 'maxProcesses' => 3,
13 ],
14 ],
15],

You may also define a wildcard environment (*) which will be used when no other matching environment is found:

1'environments' => [
2 // ...
3 
4 '*' => [
5 'supervisor-1' => [
6 'maxProcesses' => 3,
7 ],
8 ],
9],
1'environments' => [
2 // ...
3 
4 '*' => [
5 'supervisor-1' => [
6 'maxProcesses' => 3,
7 ],
8 ],
9],

When you start Horizon, it will use the worker process configuration options for the environment that your application is running on. Typically, the environment is determined by the value of the APP_ENV environment variable. For example, the default local Horizon environment is configured to start three worker processes and automatically balance the number of worker processes assigned to each queue. The default production environment is configured to start a maximum of 10 worker processes and automatically balance the number of worker processes assigned to each queue.

lightbulb

You should ensure that the environments portion of your horizon configuration file contains an entry for each environment on which you plan to run Horizon.

Supervisors

As you can see in Horizon's default configuration file, each environment can contain one or more "supervisors". By default, the configuration file defines this supervisor as supervisor-1; however, you are free to name your supervisors whatever you want. Each supervisor is essentially responsible for "supervising" a group of worker processes and takes care of balancing worker processes across queues.

You may add additional supervisors to a given environment if you would like to define a new group of worker processes that should run in that environment. You may choose to do this if you would like to define a different balancing strategy or worker process count for a given queue used by your application.

Maintenance Mode

While your application is in maintenance mode, queued jobs will not be processed by Horizon unless the supervisor's force option is defined as true within the Horizon configuration file:

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'force' => true,
6 ],
7 ],
8],
1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 // ...
5 'force' => true,
6 ],
7 ],
8],

Default Values

Within Horizon's default configuration file, you will notice a defaults configuration option. This configuration option specifies the default values for your application's supervisors. The supervisor's default configuration values will be merged into the supervisor's configuration for each environment, allowing you to avoid unnecessary repetition when defining your supervisors.

Balancing Strategies

Unlike Laravel's default queue system, Horizon allows you to choose from three worker balancing strategies: simple, auto, and false. The simple strategy splits incoming jobs evenly between worker processes:

1'balance' => 'simple',
1'balance' => 'simple',

The auto strategy, which is the configuration file's default, adjusts the number of worker processes per queue based on the current workload of the queue. For example, if your notifications queue has 1,000 pending jobs while your render queue is empty, Horizon will allocate more workers to your notifications queue until the queue is empty.

When using the auto strategy, you may define the minProcesses and maxProcesses configuration options to control the minimum number of processes per queue and the maximum number of worker processes in total Horizon should scale up and down to:

1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 'connection' => 'redis',
5 'queue' => ['default'],
6 'balance' => 'auto',
7 'autoScalingStrategy' => 'time',
8 'minProcesses' => 1,
9 'maxProcesses' => 10,
10 'balanceMaxShift' => 1,
11 'balanceCooldown' => 3,
12 'tries' => 3,
13 ],
14 ],
15],
1'environments' => [
2 'production' => [
3 'supervisor-1' => [
4 'connection' => 'redis',
5 'queue' => ['default'],
6 'balance' => 'auto',
7 'autoScalingStrategy' => 'time',
8 'minProcesses' => 1,
9 'maxProcesses' => 10,
10 'balanceMaxShift' => 1,
11 'balanceCooldown' => 3,
12 'tries' => 3,
13 ],
14 ],
15],

The autoScalingStrategy configuration value determines if Horizon will assign more worker processes to queues based on the total amount of time it will take to clear the queue (time strategy) or by the total number of jobs on the queue (size strategy).

The balanceMaxShift and balanceCooldown configuration values determine how quickly Horizon will scale to meet worker demand. In the example above, a maximum of one new process will be created or destroyed every three seconds. You are free to tweak these values as necessary based on your application's needs.

When the balance option is set to false, the default Laravel behavior will be used, wherein queues are processed in the order they are listed in your configuration.

Dashboard Authorization

The Horizon dashboard may be accessed via the /horizon route. By default, you will only be able to access this dashboard in the local environment. However, within your app/Providers/HorizonServiceProvider.php file, there is an authorization gate definition. This authorization gate controls access to Horizon in non-local environments. You are free to modify this gate as needed to restrict access to your Horizon installation:

1/**
2 * Register the Horizon gate.
3 *
4 * This gate determines who can access Horizon in non-local environments.
5 */
6protected function gate(): void
7{
8 Gate::define('viewHorizon', function (User $user) {
9 return in_array($user->email, [
11 ]);
12 });
13}
1/**
2 * Register the Horizon gate.
3 *
4 * This gate determines who can access Horizon in non-local environments.
5 */
6protected function gate(): void
7{
8 Gate::define('viewHorizon', function (User $user) {
9 return in_array($user->email, [
11 ]);
12 });
13}

Alternative Authentication Strategies

Remember that Laravel automatically injects the authenticated user into the gate closure. If your application is providing Horizon security via another method, such as IP restrictions, then your Horizon users may not need to "login". Therefore, you will need to change function (User $user) closure signature above to function (User $user = null) in order to force Laravel to not require authentication.

Silenced Jobs

Sometimes, you may not be interested in viewing certain jobs dispatched by your application or third-party packages. Instead of these jobs taking up space in your "Completed Jobs" list, you can silence them. To get started, add the job's class name to the silenced configuration option in your application's horizon configuration file:

1'silenced' => [
2 App\Jobs\ProcessPodcast::class,
3],
1'silenced' => [
2 App\Jobs\ProcessPodcast::class,
3],

Alternatively, the job you wish to silence can implement the Laravel\Horizon\Contracts\Silenced interface. If a job implements this interface, it will automatically be silenced, even if it is not present in the silenced configuration array:

1use Laravel\Horizon\Contracts\Silenced;
2 
3class ProcessPodcast implements ShouldQueue, Silenced
4{
5 use Queueable;
6 
7 // ...
8}
1use Laravel\Horizon\Contracts\Silenced;
2 
3class ProcessPodcast implements ShouldQueue, Silenced
4{
5 use Queueable;
6 
7 // ...
8}

Upgrading Horizon

When upgrading to a new major version of Horizon, it's important that you carefully review the upgrade guide.

Running Horizon

Once you have configured your supervisors and workers in your application's config/horizon.php configuration file, you may start Horizon using the horizon Artisan command. This single command will start all of the configured worker processes for the current environment:

1php artisan horizon
1php artisan horizon

You may pause the Horizon process and instruct it to continue processing jobs using the horizon:pause and horizon:continue Artisan commands:

1php artisan horizon:pause
2 
3php artisan horizon:continue
1php artisan horizon:pause
2 
3php artisan horizon:continue

You may also pause and continue specific Horizon supervisors using the horizon:pause-supervisor and horizon:continue-supervisor Artisan commands:

1php artisan horizon:pause-supervisor supervisor-1
2 
3php artisan horizon:continue-supervisor supervisor-1
1php artisan horizon:pause-supervisor supervisor-1
2 
3php artisan horizon:continue-supervisor supervisor-1

You may check the current status of the Horizon process using the horizon:status Artisan command:

1php artisan horizon:status
1php artisan horizon:status

You may check the current status of a specific Horizon supervisor using the horizon:supervisor-status Artisan command:

1php artisan horizon:supervisor-status supervisor-1
1php artisan horizon:supervisor-status supervisor-1

You may gracefully terminate the Horizon process using the horizon:terminate Artisan command. Any jobs that are currently being processed will be completed and then Horizon will stop executing:

1php artisan horizon:terminate
1php artisan horizon:terminate

Deploying Horizon

When you're ready to deploy Horizon to your application's actual server, you should configure a process monitor to monitor the php artisan horizon command and restart it if it exits unexpectedly. Don't worry, we'll discuss how to install a process monitor below.

During your application's deployment process, you should instruct the Horizon process to terminate so that it will be restarted by your process monitor and receive your code changes:

1php artisan horizon:terminate
1php artisan horizon:terminate

Installing Supervisor

Supervisor is a process monitor for the Linux operating system and will automatically restart your horizon process if it stops executing. To install Supervisor on Ubuntu, you may use the following command. If you are not using Ubuntu, you can likely install Supervisor using your operating system's package manager:

1sudo apt-get install supervisor
1sudo apt-get install supervisor
lightbulb

If configuring Supervisor yourself sounds overwhelming, consider using Laravel Forge, which will automatically install and configure Supervisor for your Laravel projects.

Supervisor Configuration

Supervisor configuration files are typically stored within your server's /etc/supervisor/conf.d directory. Within this directory, you may create any number of configuration files that instruct supervisor how your processes should be monitored. For example, let's create a horizon.conf file that starts and monitors a horizon process:

1[program:horizon]
2process_name=%(program_name)s
3command=php /home/forge/example.com/artisan horizon
4autostart=true
5autorestart=true
6user=forge
7redirect_stderr=true
8stdout_logfile=/home/forge/example.com/horizon.log
9stopwaitsecs=3600
1[program:horizon]
2process_name=%(program_name)s
3command=php /home/forge/example.com/artisan horizon
4autostart=true
5autorestart=true
6user=forge
7redirect_stderr=true
8stdout_logfile=/home/forge/example.com/horizon.log
9stopwaitsecs=3600

When defining your Supervisor configuration, you should ensure that the value of stopwaitsecs is greater than the number of seconds consumed by your longest running job. Otherwise, Supervisor may kill the job before it is finished processing.

lightbulb

While the examples above are valid for Ubuntu based servers, the location and file extension expected of Supervisor configuration files may vary between other server operating systems. Please consult your server's documentation for more information.

Starting Supervisor

Once the configuration file has been created, you may update the Supervisor configuration and start the monitored processes using the following commands:

1sudo supervisorctl reread
2 
3sudo supervisorctl update
4 
5sudo supervisorctl start horizon
1sudo supervisorctl reread
2 
3sudo supervisorctl update
4 
5sudo supervisorctl start horizon
lightbulb

For more information on running Supervisor, consult the Supervisor documentation.

Tags

Horizon allows you to assign “tags” to jobs, including mailables, broadcast events, notifications, and queued event listeners. In fact, Horizon will intelligently and automatically tag most jobs depending on the Eloquent models that are attached to the job. For example, take a look at the following job:

1<?php
2 
3namespace App\Jobs;
4 
5use App\Models\Video;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Foundation\Queue\Queueable;
8 
9class RenderVideo implements ShouldQueue
10{
11 use Queueable;
12 
13 /**
14 * Create a new job instance.
15 */
16 public function __construct(
17 public Video $video,
18 ) {}
19 
20 /**
21 * Execute the job.
22 */
23 public function handle(): void
24 {
25 // ...
26 }
27}
1<?php
2 
3namespace App\Jobs;
4 
5use App\Models\Video;
6use Illuminate\Contracts\Queue\ShouldQueue;
7use Illuminate\Foundation\Queue\Queueable;
8 
9class RenderVideo implements ShouldQueue
10{
11 use Queueable;
12 
13 /**
14 * Create a new job instance.
15 */
16 public function __construct(
17 public Video $video,
18 ) {}
19 
20 /**
21 * Execute the job.
22 */
23 public function handle(): void
24 {
25 // ...
26 }
27}

If this job is queued with an App\Models\Video instance that has an id attribute of 1, it will automatically receive the tag App\Models\Video:1. This is because Horizon will search the job's properties for any Eloquent models. If Eloquent models are found, Horizon will intelligently tag the job using the model's class name and primary key:

1use App\Jobs\RenderVideo;
2use App\Models\Video;
3 
4$video = Video::find(1);
5 
6RenderVideo::dispatch($video);
1use App\Jobs\RenderVideo;
2use App\Models\Video;
3 
4$video = Video::find(1);
5 
6RenderVideo::dispatch($video);

Manually Tagging Jobs

If you would like to manually define the tags for one of your queueable objects, you may define a tags method on the class:

1class RenderVideo implements ShouldQueue
2{
3 /**
4 * Get the tags that should be assigned to the job.
5 *
6 * @return array<int, string>
7 */
8 public function tags(): array
9 {
10 return ['render', 'video:'.$this->video->id];
11 }
12}
1class RenderVideo implements ShouldQueue
2{
3 /**
4 * Get the tags that should be assigned to the job.
5 *
6 * @return array<int, string>
7 */
8 public function tags(): array
9 {
10 return ['render', 'video:'.$this->video->id];
11 }
12}

Manually Tagging Event Listeners

When retrieving the tags for a queued event listener, Horizon will automatically pass the event instance to the tags method, allowing you to add event data to the tags:

1class SendRenderNotifications implements ShouldQueue
2{
3 /**
4 * Get the tags that should be assigned to the listener.
5 *
6 * @return array<int, string>
7 */
8 public function tags(VideoRendered $event): array
9 {
10 return ['video:'.$event->video->id];
11 }
12}
1class SendRenderNotifications implements ShouldQueue
2{
3 /**
4 * Get the tags that should be assigned to the listener.
5 *
6 * @return array<int, string>
7 */
8 public function tags(VideoRendered $event): array
9 {
10 return ['video:'.$event->video->id];
11 }
12}

Notifications

lightbulb

When configuring Horizon to send Slack or SMS notifications, you should review the prerequisites for the relevant notification channel.

If you would like to be notified when one of your queues has a long wait time, you may use the Horizon::routeMailNotificationsTo, Horizon::routeSlackNotificationsTo, and Horizon::routeSmsNotificationsTo methods. You may call these methods from the boot method of your application's App\Providers\HorizonServiceProvider:

1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 parent::boot();
7 
8 Horizon::routeSmsNotificationsTo('15556667777');
9 Horizon::routeMailNotificationsTo('[email protected]');
10 Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
11}
1/**
2 * Bootstrap any application services.
3 */
4public function boot(): void
5{
6 parent::boot();
7 
8 Horizon::routeSmsNotificationsTo('15556667777');
9 Horizon::routeMailNotificationsTo('[email protected]');
10 Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
11}

Configuring Notification Wait Time Thresholds

You may configure how many seconds are considered a "long wait" within your application's config/horizon.php configuration file. The waits configuration option within this file allows you to control the long wait threshold for each connection / queue combination. Any undefined connection / queue combinations will default to a long wait threshold of 60 seconds:

1'waits' => [
2 'redis:critical' => 30,
3 'redis:default' => 60,
4 'redis:batch' => 120,
5],
1'waits' => [
2 'redis:critical' => 30,
3 'redis:default' => 60,
4 'redis:batch' => 120,
5],

Metrics

Horizon includes a metrics dashboard which provides information regarding your job and queue wait times and throughput. In order to populate this dashboard, you should configure Horizon's snapshot Artisan command to run every five minutes in your application's routes/console.php file:

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('horizon:snapshot')->everyFiveMinutes();
1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('horizon:snapshot')->everyFiveMinutes();

Deleting Failed Jobs

If you would like to delete a failed job, you may use the horizon:forget command. The horizon:forget command accepts the ID or UUID of the failed job as its only argument:

1php artisan horizon:forget 5
1php artisan horizon:forget 5

If you would like to delete all failed jobs, you may provide the --all option to the horizon:forget command:

1php artisan horizon:forget --all
1php artisan horizon:forget --all

Clearing Jobs From Queues

If you would like to delete all jobs from your application's default queue, you may do so using the horizon:clear Artisan command:

1php artisan horizon:clear
1php artisan horizon:clear

You may provide the queue option to delete jobs from a specific queue:

1php artisan horizon:clear --queue=emails
1php artisan horizon:clear --queue=emails

Comments

No Comments Yet

“Laravel” is a Trademark of Taylor Otwell.
The source documentation is released under MIT license. See laravel/docs on GitHub for details.
The translated documentations are released under MIT license. See cornch/laravel-docs-l10n on GitHub for details.