Laravel Horizon
Что такое Horizon
Horizon — панель управления Redis-очередями в Laravel.
Что даёт:
- UI — веб-интерфейс для мониторинга.
- Метрики — throughput, wait time, jobs processed.
- Failed jobs — retry из UI.
- Auto-balance — распределение воркеров.
- Supervisor — вместо
supervisorиз ОС. - Теги — группировка.
- Алерты — при сбоях.
Установка
composer require laravel/horizon
php artisan horizon:install
Что создаёт:
config/horizon.php— конфиг.app/Providers/HorizonServiceProvider.php— доступ.- Миграции (если нужны).
Запуск
php artisan horizon
Что происходит: запускаются воркеры согласно конфигу.
UI: /horizon.
Доступ
HorizonServiceProvider::gate:
protected function gate(): void
{
Gate::define('viewHorizon', function (User $user) {
return $user->is_admin;
});
}
Что даёт: только админы видят UI.
Конфиг — environments
config/horizon.php:
'environments' => [
'production' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'maxProcesses' => 10,
'maxTime' => 0,
'maxJobs' => 0,
'memory' => 128,
'tries' => 1,
'timeout' => 60,
'nice' => 0,
],
],
'local' => [
'supervisor-1' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'simple',
'maxProcesses' => 3,
'tries' => 3,
],
],
],
Supervisor — логическая группа
Можно несколько:
'production' => [
// Срочные задачи
'supervisor-critical' => [
'connection' => 'redis',
'queue' => ['critical'],
'balance' => 'false',
'processes' => 5,
'tries' => 3,
'timeout' => 30,
],
// Обычные
'supervisor-default' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'maxProcesses' => 10,
'tries' => 3,
'timeout' => 60,
],
// Долгие (email, PDF)
'supervisor-long' => [
'connection' => 'redis',
'queue' => ['long-running'],
'balance' => 'auto',
'maxProcesses' => 3,
'timeout' => 900,
'tries' => 1,
],
],
Что даёт: разные настройки для разных типов задач.
Balance
Три режима:
false— фиксированное число процессов.simple— делит поровну.auto— распределяет по нагрузке.
false — предсказуемо.
simple — ок для равных очередей.
auto — оптимально.
Параметры
connection— Redis.queue— какие очереди слушать.balance— стратегия.processes— количество (дляfalse).minProcesses/maxProcesses— дляauto.maxTime— сколько секунд работает процесс.maxJobs— сколько задач обработать и перезапуститься.memory— лимит MB.tries— попытки.timeout— таймаут.nice— приоритет.
Запуск в проде
Через supervisor:
/etc/supervisor/conf.d/horizon.conf:
[program:horizon]
process_name=%(program_name)s
command=php /var/www/app/artisan horizon
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/www/app/storage/logs/horizon.log
stopwaitsecs=3600
Или systemd:
/etc/systemd/system/horizon.service:
[Unit]
Description=Horizon
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/app
ExecStart=/usr/bin/php /var/www/app/artisan horizon
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Рестарт после деплоя
php artisan horizon:terminate
Что делает: все воркеры завершают текущие задачи и перезапускаются.
Через supervisor:
sudo supervisorctl restart horizon
Правило: после деплоя — horizon:terminate.
Метрики
Что видно в UI:
- Throughput — сколько задач в час.
- Wait time — сколько ждёт задача.
- Runtime — сколько выполняется.
- Failed rate — % ошибок.
Что смотреть:
- Wait time растёт → воркеров мало.
- Failed rate растёт → ошибка в коде.
- Throughput падает → внешний сервис тормозит.
Failed jobs
UI → Failed Jobs:
- Retry — перезапустить.
- Retry All — все.
- Delete — удалить.
- View — посмотреть exception.
Теги
В Job:
public function tags(): array
{
return ['user:' . $this->user->id, 'email'];
}
В UI: фильтр по тегам.
Notifications
Алерты при сбое:
// AppServiceProvider::boot()
use Laravel\Horizon\Events\LongWaitDetected;
use Illuminate\Support\Facades\Notification;
Event::listen(LongWaitDetected::class, function ($event) {
Notification::route('mail', 'ops@example.com')
->notify(new LongWaitNotification($event));
});
Что даёт: узнаешь, что очередь встала.
События
use Laravel\Horizon\Events\JobFailed;
use Laravel\Horizon\Events\JobProcessed;
Event::listen(JobFailed::class, function ($event) {
Log::error('Horizon job failed', [
'job' => $event->job->payload(),
'exception' => $event->exception,
]);
});
Tags — продвинуто
Динамические:
class ProcessOrder implements ShouldQueue
{
public function tags(): array
{
return [
'order:' . $this->order->id,
'user:' . $this->order->user_id,
'status:' . $this->order->status,
];
}
}
В UI: фильтр по каждому.
Auto-scaling
Что делает:
- Смотрит длину очереди.
- Если растёт — добавляет воркеров (до
maxProcesses). - Если падает — уменьшает (до
minProcesses).
Результат:
- Ночью — 2 воркера.
- Днём — 10.
- В пик — 20.
Metrics и Prometheus
Horizon Metrics:
'fast_termination' => false,
'metrics' => [
'trim' => [
'recent' => 60, // минуты
'completed' => 10080, // неделя
'failed' => 10080,
],
],
Trim: сколько хранить метрики.
Waits
Пороги:
'waits' => [
'redis:default' => 60,
'redis:critical' => 30,
],
Что даёт: алерт при превышении.
Практика: полная настройка
1. Установка:
composer require laravel/horizon
php artisan horizon:install
2. Конфиг — production:
'environments' => [
'production' => [
'supervisor-critical' => [
'connection' => 'redis',
'queue' => ['critical'],
'balance' => 'false',
'processes' => 5,
'tries' => 3,
'timeout' => 30,
],
'supervisor-default' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'minProcesses' => 2,
'maxProcesses' => 10,
'tries' => 3,
'timeout' => 60,
'nice' => 0,
],
'supervisor-long' => [
'connection' => 'redis',
'queue' => ['long-running'],
'balance' => 'auto',
'maxProcesses' => 3,
'tries' => 1,
'timeout' => 900,
],
],
],
3. Запуск:
php artisan horizon
# UI: /horizon
4. Job с тегами:
class SendEmail implements ShouldQueue
{
public int $tries = 3;
public int $timeout = 30;
public function __construct(public User $user) { }
public function handle(): void
{
Mail::to($this->user)->send(new Welcome($this->user));
}
public function tags(): array
{
return ['email', 'user:' . $this->user->id];
}
}
5. Dispatch:
SendEmail::dispatch($user)->onQueue('default');
Частые ошибки
1. Horizon без Redis:
Horizon requires Redis
Решение: QUEUE_CONNECTION=redis.
2. Забыли horizon:terminate:
Воркеры работают со старым кодом
3. Все процессы умерли:
supervisor` не настроен
Решение: supervisor или systemd.
4. Только один supervisor:
Долгая задача блокирует остальные
Решение: разные очереди.
5. Забыли Gate:
/horizon открыт для всех
Итог
- Horizon — UI и метрики для Redis.
horizon:install— установка.horizon— запуск.- environments — конфиг по среде.
- supervisor — логическая группа.
- balance — auto / simple / false.
- Auto-scaling — встроен.
- Failed jobs — retry из UI.
- Tags — группировка.
horizon:terminate— рестарт после деплоя.
Дальше — Schedule (расписание).