Очереди — это «нервная система» большинства production-приложений на Laravel: отправка писем, генерация отчётов, обработка вебхуков, ресайз изображений. Стандартный воркер php artisan queue:work отлично справляется, пока задач немного и они однородны. Но как только очередей становится несколько, у них появляется разный приоритет, а количество задач скачет в течение суток — начинается ручная боль: сколько воркеров запустить, на какую очередь, как понять, что что-то «висит». Здесь на сцену выходит Laravel Horizon — официальный пакет, который превращает управление Redis-очередями в наглядный дашборд и конфигурацию, хранящуюся в коде.
В этой статье разберём установку Horizon, ключевые параметры конфигурации, три стратегии балансировки воркеров, запуск в production через Supervisor, а также теги, уведомления и метрики. Все примеры и команды сверены с официальной документацией Laravel 13.x.
Что такое Horizon и когда он нужен
Horizon — это надстройка над стандартными очередями Laravel, которая работает только поверх Redis. Он не заменяет систему очередей, а дополняет её: даёт красивый дашборд по адресу /horizon, показывает пропускную способность (throughput), время выполнения задач, упавшие джобы и время ожидания в очереди. Главная идея — «code-driven configuration»: вся настройка воркеров лежит в одном файле config/horizon.php под контролем версий, а не разбросана по флагам команд запуска.
Важное ограничение: на текущий момент Horizon не совместим с Redis Cluster. Если вы используете кластерный Redis, это нужно учитывать заранее.
Установка
Сначала убедитесь, что соединение очереди настроено на Redis в файле config/queue.php ('default' => env('QUEUE_CONNECTION', 'redis')). После этого установка занимает две команды:
composer require laravel/horizon
php artisan horizon:install
Команда horizon:install публикует ассеты дашборда и создаёт основной конфигурационный файл config/horizon.php. Внутри Horizon использует зарезервированное Redis-соединение с именем horizon — это имя нельзя занимать под другие соединения в config/database.php.
Конфигурация: окружения и супервайзеры
Главный блок конфигурации — environments. Это массив окружений (определяются по APP_ENV), в каждом из которых описаны группы воркеров. По умолчанию есть production и local:
'environments' => [
'production' => [
'supervisor-1' => [
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
'local' => [
'supervisor-1' => [
'maxProcesses' => 3,
],
],
],
Каждое окружение содержит один или несколько супервайзеров (по умолчанию supervisor-1). Супервайзер «надзирает» за группой воркеров и отвечает за их балансировку между очередями. Удобно завести отдельный супервайзер для тяжёлых задач, чтобы ограничить им количество процессов.
Можно задать и wildcard-окружение '*', которое сработает, если ни одно другое окружение не совпало. А блок defaults позволяет вынести общие значения, чтобы не дублировать их в каждом супервайзере.
Полезные параметры супервайзера
Помимо количества процессов, на уровне супервайзера настраиваются параметры повторов и таймаутов:
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default', 'notifications'],
'tries' => 3,
'timeout' => 60,
'backoff' => [1, 5, 10],
],
],
Несколько важных нюансов из документации:
tries— максимальное число попыток. Если не задать его, Horizon по умолчанию делает одну попытку, если только в классе джобы не определено свойство$tries(оно приоритетнее конфига). Значение0означает бесконечные попытки.timeout— сколько секунд воркер может выполнять задачу до принудительного завершения. Это значение должно быть на несколько секунд меньше, чемretry_afterвconfig/queue.php, иначе задача может выполниться дважды.backoff— пауза перед повтором упавшей задачи. Массив[1, 5, 10]задаёт экспоненциальную задержку: 1 секунда, затем 5, затем 10 и далее по 10.
Три стратегии балансировки
В отличие от стандартных очередей, Horizon умеет распределять воркеры между очередями автоматически. Параметр balance принимает три значения: auto, simple и false.
auto (по умолчанию) динамически подбирает число воркеров под нагрузку. Если в очереди notifications скопилась тысяча задач, а default пуста — Horizon перебросит больше воркеров на notifications:
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default', 'notifications'],
'balance' => 'auto',
'autoScalingStrategy' => 'time',
'minProcesses' => 1,
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
Здесь autoScalingStrategy определяет логику масштабирования: time — по оценочному времени разбора очереди, size — по числу задач. Параметры balanceMaxShift и balanceCooldown задают скорость масштабирования: в примере выше создаётся или убирается максимум один процесс каждые три секунды. Важно помнить: при стратегии auto порядок очередей в массиве не задаёт приоритет — для жёсткого приоритета используйте отдельные супервайзеры.
simple делит фиксированное число процессов поровну между очередями без авто-масштабирования. В примере ниже по 5 процессов на каждую очередь:
'supervisor-1' => [
'queue' => ['default', 'notifications'],
'balance' => 'simple',
'processes' => 10,
],
false отключает балансировку и обрабатывает очереди строго в порядке списка (как стандартный воркер Laravel), но всё ещё умеет масштабировать число процессов между minProcesses и maxProcesses при накоплении задач.
Запуск и деплой в production
Локально Horizon запускается одной командой, которая поднимает все настроенные воркеры для текущего окружения:
php artisan horizon
Полезный набор управляющих команд: horizon:pause и horizon:continue — приостановить и возобновить обработку, horizon:status — проверить состояние, horizon:terminate — корректно завершить процесс (текущие задачи доедут до конца).
В production процесс php artisan horizon должен быть под присмотром монитора процессов. Классический выбор — Supervisor. Установка на Ubuntu:
sudo apt-get install supervisor
Конфиг обычно кладут в /etc/supervisor/conf.d/horizon.conf:
[program:horizon]
process_name=%(program_name)s
command=php /home/forge/example.com/artisan horizon
autostart=true
autorestart=true
user=forge
redirect_stderr=true
stdout_logfile=/home/forge/example.com/horizon.log
stopwaitsecs=3600
Значение stopwaitsecs должно быть больше длительности самой долгой задачи, иначе Supervisor убьёт воркер до завершения работы. После создания конфига применяем его:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start horizon
Ключевой момент деплоя: на каждом релизе вызывайте php artisan horizon:terminate. Horizon завершит текущие задачи и остановится, а Supervisor поднимет процесс заново — уже с новым кодом. Запускать тяжёлый воркер со старым кодом без перезапуска нельзя, иначе изменения не подхватятся.
Теги, уведомления и метрики
Horizon автоматически проставляет задачам теги по Eloquent-моделям в их свойствах. Например, джоба с моделью App\Models\Video и id = 1 получит тег App\Models\Video:1 — по нему удобно искать связанные задачи в дашборде. Теги можно задать и вручную через метод tags():
public function tags(): array
{
return ['render', 'video:'.$this->video->id];
}
Для уведомлений о долгом ожидании в очереди есть методы маршрутизации, которые вызывают в boot() провайдера HorizonServiceProvider:
Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
Horizon::routeMailNotificationsTo('admin@example.com');
Порог «долгого ожидания» настраивается в секциях waits конфига (по умолчанию 60 секунд на пару соединение/очередь). Чтобы заработал дашборд метрик, добавьте сбор снапшотов в расписание routes/console.php:
use Illuminate\Support\Facades\Schedule;
Schedule::command('horizon:snapshot')->everyFiveMinutes();
Итоги
Laravel Horizon снимает с команды рутину ручного управления воркерами Redis-очередей и заменяет её декларативной конфигурацией в коде плюс наглядным дашбордом. Для большинства проектов достаточно поставить пакет, настроить блок environments со стратегией auto, повесить процесс под Supervisor и не забыть про horizon:terminate в деплое. А теги, уведомления и метрики дают именно ту наблюдаемость, которой так не хватает «голому» queue:work. Если у вас уже работает Redis и больше одной очереди — Horizon окупается практически сразу.