Back to Course |
Practical Laravel Queues on Live Server

Laravel Horizon and Redis

In this chapter we will cover how to set up Redis in-memory data store and Laravel Horizon to provide a beautiful dashboard and code-driven configuration for Redis queues.

Laravel Horizon

Redis is often preferred over a database for implementing queues because of its speed. In-memory key-value store makes it very fast compared to MySQL. In a queue scenario, where data needs to be accessed and processed quickly, Redis can handle requests much faster than MySQL.

Horizon allows us to easily monitor key metrics of the queue system such as job throughput, runtime, and job failures.

The advantage of using Horizon is that all queue worker configuration is stored in a single, simple configuration file and that allows us easily scale or modify queue workers when deploying the application. For example, in the previous chapter, we set the number of workers in the supervisor configuration.

Requirements

To use Redis with Laravel we need the PHP driver for redis.

One option is to install the predis/predis ~1.0 package via composer:

composer require predis/predis:~1.0

Or install a system-wide PHP extension.

In this course, we chose to install the Redis PHP Extension.

Ubuntu

sudo apt-get install redis php8.1-redis
sudo systemctl restart php8.1-fpm.service

Fedora

sudo dnf install redis php-pecl-redis5.x86_64
sudo systemctl restart php-fpm.service

Installation

Then we can install Horizon using the composer:

composer require laravel/horizon

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

php artisan horizon:install

And update queue connection in your environment file:

.env

QUEUE_CONNECTION=redis

Running Horizon

Horizon queue worker can be launched using this command:

php artisan horizon

Now we can try again by registering a new user and the verification email should be delivered.

Horizon and Supervisor

We can set up Supervisor for Horizon in the same fashion as we did for queue workers.

For supervisor configuration locations please refer to Chapter 2.

Config content should look as follows:

[program:laravel-horizon]
directory=/home/web/laravel-queues
command=php artisan horizon
 
process_name=%(program_name)s_%(process_num)02d
autostart=true
autorestart=true
user=web
numprocs=1
redirect_stderr=true
stdout_logfile=/home/web/.supervisor/laravel-horizon.log
stopwaitsecs=3600

Important to note that the numprocs value is set to 1. Worker processes are managed by Horizon and the configuration file is located in config/horizon.php in the Laravel project directory.

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.

Horizon & Deployment

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

php artisan horizon:terminate

This command should be included in your deployment script.

Horizon configuration

The default configuration of Horizon right after installation looks like this:

config/horizon.php

use Illuminate\Support\Str;
 
return [
 
// ...
 
'domain' => env('HORIZON_DOMAIN'),
 
// ...
 
'path' => env('HORIZON_PATH', 'horizon'),
 
// ...
 
'use' => 'default',
 
// ...
 
'prefix' => env(
'HORIZON_PREFIX',
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
),
 
// ...
 
'middleware' => ['web'],
 
// ...
 
'waits' => [
'redis:default' => 60,
],
 
// ...
 
'trim' => [
'recent' => 60,
'pending' => 60,
'completed' => 60,
'recent_failed' => 10080,
'failed' => 10080,
'monitored' => 10080,
],
 
// ...
 
'silenced' => [
// App\Jobs\ExampleJob::class,
],
 
// ...
 
'metrics' => [
'trim_snapshots' => [
'job' => 24,
'queue' => 24,
],
],
 
// ...
 
'fast_termination' => false,
 
// ...
 
'memory_limit' => 64,
 
// ...
 
'defaults' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'maxProcesses' => 1,
'maxTime' => 0,
'maxJobs' => 0,
'memory' => 128,
'tries' => 1,
'timeout' => 60,
'nice' => 0,
],
],
 
'environments' => [
'production' => [
'supervisor-1' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
 
'local' => [
'supervisor-1' => [
'maxProcesses' => 3,
],
],
],
];

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.

Typically, the environment is determined by the value of the APP_ENV environment variable.

Dashboard

Horizon exposes a dashboard at the /horizon URL.

Let's quickly run through the dashboard, we will come back to it later.

Here you will be presented with an overview and statistics about your queue workers like how many jobs were run in the past hour, how busy are queues, or how long jobs are waiting in queue to start being processed.

Dashboard

The pending Jobs tab will show all jobs in the queue waiting for their turn.

Pending Jobs

The completed Jobs tab has insights into how long it took for it to complete.

Completed Jobs

Dashboard access

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.

We can update the gate() method to include user emails which could be able to access the dashboard in production or use any other logic.

app/Providers/HorizonServiceProvider.php

protected function gate(): void
{
Gate::define('viewHorizon', function ($user) {
return in_array($user->email, [
'admin-email@example.org'
]);
});
}