Deep Dive into Laravel Horizon — what goes on behind the scenes
Laravel Horizon is a simple way to get started with Laravel Queues by reducing the amount of configuration to spawn and manage queue workers. However, understanding how the queue worker processes are created and in what order jobs get processed can be a little unclear when starting out, especially if you are new to Queues altogether.
In this article I will dive into and explain:
- How many worker processes are being spawned by Laravel Horizon?
- If I have more than one queue, such as a
high
anddefault
queue
— how many worker processes are created by Laravel Horizon?
— what order are jobs executed?
Horizon has an option called balance
which accepts false
, simple
, or auto
. The balance
option directly affects worker creation and job execution.
Let’s start with the easiest option first, balance=false
balance=false
When the
balance
option is set tofalse
, the default Laravel behavior will be used, which processes queues in the order they are listed in your configuration.
In the case of balance=false
here is how worker processes are created:
- Create worker processes equal to the number you have set
processes
to inconfig/horizon.php
Job execution order:
- The workers will always process jobs in the order of how you define the
queues
inconfig/horizon.php
.
Examples? Ya, that might help
// config/horizon.php
...
'local' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['high', 'default'],
'balance' => 'false',
'processes' => 1,
'tries' => 0
,
],
],
...
With the above config, notice we have two queues
specified (high, default)
We also have balance=false
and processes=1
To get a better understanding of what processes are being created we can login to our Homestead server
- Login to Homestead with
homestead ssh
. This only works if you are using homestead AND you setup this function. - Once you are in the server, you can run
top
to view a list of all processes.
In the image below, each line is a process on the server. I cut out a bunch of useless information like the PID. Here you just see the command that Horizon is running. If you are curious how I got this information, Check out my other post.
artisan horizon:superviso
is the process responsible for managing the worker processes.
artisan horizon:work
is our actual worker process created by supervisor
.
Notice the supervisor
has a param of --max-processes=1
and --min-processes=1
The min-processes will become more important with the other strategies we will talk about later.
And notice the horizon:work
process has a param of --queue=high,default
This means it will process ALL high jobs first, then default ones. This will also change in the other strategies.
What happens when I have a few jobs in both queues? What order are they processed?
I have a small script that creates some simple test jobs for both queues just to demonstrate the order jobs are being processed. Let’s take a look!
- I added some jobs to the queue — 5 default
z:1, z:2, z:3, z:4, z:5
— 1 highhigh1
- The queue starts by processing a high priority job (
high1
) then a default job (z:1
) - When the worker starts started processing
z:2
, I manually created 5 more default jobs and 1 more high job. - The worker finished processing
z:2
then started on thehigh2
job.
Keep in mind, I only have one worker process in this example. If I have 2 or more workers, you would see multiple jobs starting at the same time.
balance = simple
The
simple
strategy […] splits incoming jobs evenly between processes.
In the case of balance=simple
here is how worker processes are created:
- Always create 1 worker process for each queue, even if you specify fewer
processes
than queues in your config. - If there are any left over processes, create 1 new process for each queue in the order of how the queues are specified in the config.
For example:
'queue' => ['high', 'default'],
'balance' => 'simple',
'processes' => 1,
will result in 2
processes, NOT 1
process. 1 process is created for each queue in the queue
config.
And this:
'queue' => ['high', 'default', 'emails'],
'balance' => 'simple',
'processes' => 5,
will result in 5
total.
- 2 for
high
- 2 for
default
- 1 for
emails
Why does this matter? Imagine you want your works to process everything in the high
priority queue first before moving onto the default
queue. If that is the case, stick to balance=false
balance = auto
The
auto
strategy adjusts the number of worker processes per queue based on the current workload of the queue. For example, if yournotifications
queue has 1,000 waiting jobs while yourrender
queue is empty, Horizon will allocate more workers to yournotifications
queue until it is empty.
In the case of balance=auto
here is how worker processes are created:
- Always create 1 worker process for each queue, even if you specify fewer
processes
than queues in your config. - If there are any left over processes, create 1 new process for each queue in the order of how the queues are specified in the config.
- The worker processes are then reassigned to different queues based on which one has jobs in the queue. There will still always be at least 1 process per queue.
For example:
'queue' => ['high', 'default'],
'balance' => 'auto',
'processes' => 1,
will result in 2
processes, NOT 1
process. 1 process is created for each queue in the queue
config.
And this:
'queue' => ['high', 'default', 'emails'],
'balance' => 'auto',
'processes' => 5,
will result in 5
total.
- 2 for
high
- 2 for
default
- 1 for
emails
However, 2 of the workers will be reassigned to whichever queue has the most jobs. For example, if the emails
queue has 1,000 jobs, the high
queue has 0 jobs, and the default
queue has 10 jobs, Horizon you will probably reassign the workers like so:
- 1 for
high
- 1 for
default
- 3 for
emails
Notice the high
priority queue still has 1 worker even though there are 0 jobs for that queue.
Here is another example: Let’s have 2 queues and 6 processes. We add 1,000 jobs to the default
queue, then after a minute, add 1,000 high
priority jobs and see how Horizon reassigns workers to queues with more jobs.
'queue' => ['high', 'default'],
'balance' => 'auto',
'processes' => 6,
After adding 1,000 default
jobs:
After adding 1,000 high
jobs while there are still 980 default
jobs:
Note: The balance=auto
feature won’t do any automatic switching unless you have excess processes
. If you have 10 queues and processes set to 11, 1 worker process will be able to move around freely between queues.
In both simple
and auto
: If you setup 10 queues and set your processes to 1, you will actually have 10 processes running. Even if you set your processes to 9, you will still have 10 running, 1 for each queue.
If you are curious how I came to these conclusions, check out my other post which is random notes about me testing each case.
Can I change min-processes?
If you are like me, you might be thinking, min-processes
is just a config option on the horizon command. Can’t I change that and alter how Horizon spawns my workers. In your config/horizon.php
, you can add this to the config:
'min-processes' => 0
Or this:
'min-processes' => '5',
The horizon command does get updated to use this config, however, it seems to have zero effect on how processes are spawned.