Очереди и Horizon
Зачем очереди
Что даёт:
- Ответ сразу.
- Тяжёлое — в фон.
- Retry при сбое.
- Масштаб воркеров.
- Приоритеты.
Что вынести:
- Email.
- Webhooks.
- Отчёты.
- Ресайз.
- Индексация.
Правило: что > 200 мс и не нужно в ответе — в очередь.
Драйверы очередей
Доступные:
- sync — сразу, для тестов.
- database — таблица.
- redis — стандарт.
- sqs — AWS.
- beanstalkd.
Правило: в проде — Redis или SQS.
Установка
.env:
QUEUE_CONNECTION=redis
Миграция (для database):
php artisan queue:table
php artisan migrate
Первая job
php artisan make:job SendWelcomeEmail
class SendWelcomeEmail implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public User $user) {}
public function handle(): void
{
Mail::to($this->user)->send(new WelcomeMail($this->user));
}
}
Dispatch:
SendWelcomeEmail::dispatch($user);
Правило: ShouldQueue — обязательно для async.
Запуск воркера
php artisan queue:work
php artisan queue:work --queue=high,default
php artisan queue:work --tries=3 --timeout=60
Что важно:
--tries— попыток.--timeout— макс. сек.--memory— лимит памяти воркера.
Правило: воркер в проде — через Supervisor / Horizon.
Supervisor
/etc/supervisor/conf.d/laravel-worker.conf:
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/skills/api/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
numprocs=4
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/laravel-worker.log
stopwaitsecs=3600
Что даёт: авто-рестарт, логи.
Правило: Supervisor — базовый вариант.
Horizon
Что это: дашборд и менеджер очередей Redis.
Что даёт:
- UI для очередей.
- Auto-balance воркеров.
- Метрики — throughput, latency.
- Failed jobs в UI.
- Тэги для группировки.
Правило: Horizon — стандарт для Redis.
Horizon: установка
composer require laravel/horizon
php artisan horizon:install
php artisan migrate
Что создаёт:
config/horizon.php.app/Providers/HorizonServiceProvider.php.- Дашборд на
/horizon.
Horizon: доступ
HorizonServiceProvider:
protected function gate(): void
{
Gate::define('viewHorizon', function (User $user) {
return $user->is_admin;
});
}
Правило: Horizon — только для админов.
Horizon: конфиг
config/horizon.php:
'environments' => [
'production' => [
'supervisor-default' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 10,
'maxTime' => 3600,
'maxJobs' => 1000,
'memory' => 256,
'tries' => 3,
'timeout' => 60,
'nice' => 0,
],
'supervisor-high' => [
'connection' => 'redis',
'queue' => ['high'],
'balance' => 'simple',
'processes' => 4,
],
],
],
Что важно: разные supervisor для разных очередей.
Balance strategies
simple— фиксированное число процессов.auto— по нагрузке.false— один процесс на очередь.
Правило: auto — для смешанных очередей.
Приоритеты очередей
Dispatch:
SendEmail::dispatch($user)->onQueue('high');
GenerateReport::dispatch($report)->onQueue('low');
Воркер:
php artisan queue:work --queue=high,default,low
Что даёт: слева — раньше.
Правило: 3 очереди максимум.
Задержка
SendEmail::dispatch($user)->delay(now()->addMinutes(5));
SendEmail::dispatch($user)->delay(300);
Что важно: для database — свой планировщик.
Правило: Redis — delay из коробки.
Retry
В job:
public int $tries = 5;
public array $backoff = [10, 30, 60, 120, 300];
public int $maxExceptions = 3;
Что даёт: умный retry с backoff.
Правило: $backoff — растущий.
Failed jobs
Таблица failed_jobs:
php artisan queue:failed
php artisan queue:retry all
php artisan queue:forget 5
php artisan queue:flush
В Horizon: вкладка Failed.
Правило: failed — не забывать, иначе растёт.
unique jobs
class ProcessOrder implements ShouldQueue, ShouldBeUnique
{
public int $uniqueFor = 3600;
public function uniqueId(): string
{
return 'order:' . $this->order->id;
}
}
Что даёт: job не запустится дважды.
Правило: идемпотентность — обязательна.
Middleware jobs
Rate limiting:
public function middleware(): array
{
return [new RateLimited('api')];
}
WithoutOverlapping:
public function middleware(): array
{
return [(new WithoutOverlapping($this->user->id))->expireAfter(300)];
}
Правило: для пользовательских операций — без overlap.
Batches
Bus::batch([
new ProcessFile('a.csv'),
new ProcessFile('b.csv'),
])
->then(fn (Batch $batch) => Log::info('Done'))
->catch(fn (Batch $batch, $e) => Log::error('Failed'))
->finally(fn (Batch $batch) => Log::info('Finished'))
->dispatch();
Что даёт: группа задач как одна.
Правило: batch — когда важно знать итог группы.
Chains
Bus::chain([
new ChargeOrder($order),
new SendReceipt($order),
new UpdateAnalytics($order),
])->dispatch();
Что даёт: последовательное выполнение.
Правило: chain — порядок важен.
Scheduler
routes/console.php:
Schedule::job(new CleanupOldData)->daily();
Schedule::command('cache:prune-stale-tags')->hourly();
Воркер:
php artisan schedule:work
Cron:
* * * * * cd /var/www/skills/api && php artisan schedule:run >> /dev/null 2>&1
Правило: schedule:work — в dev, cron — в проде.
Дебаг job
public function handle(): void
{
Log::info('Job started', ['id' => $this->order->id]);
try {
// работа
} catch (\Throwable $e) {
Log::error('Job failed', ['error' => $e->getMessage()]);
throw $e;
}
}
Правило: логировать начало и ошибки.
Job в Horizon UI
Что видно:
- Pending jobs.
- Completed.
- Failed.
- Silenced.
- Runtime.
Правило: Horizon → Jobs — первый экран при инциденте.
Метрики Horizon
Что смотреть:
- Throughput — jobs/min.
- Wait time — latency.
- Runtime — длительность.
- Failed rate.
Правило: wait time растёт → воркеров мало.
Очереди в Laravel 11+
Что нового:
Queue::route()— централизованно.Connectionв каждом классе.
AppServiceProvider:
Queue::route(SendEmail::class, 'emails');
Queue::route(GenerateReport::class, 'reports');
Правило: маршрутизация — в одном месте.
Практика
1. Установите Horizon.
2. Настройте 3 очереди: high, default, low.
3. Настройте Gate для Horizon.
4. Вынесите email в очередь.
5. Вынесите отчёт в очередь.
6. Настройте retry с backoff.
7. Сделайте batch для импорта.
8. Настройте schedule:work в dev.
9. Проверьте Supervisor в проде.
10. Смотрите throughput в Horizon.
Частые ошибки
1. Job без ShouldQueue:
Синхронно
Решение: implements.
2. Публичный Horizon:
Доступ всем
Решение: Gate.
3. Одна очередь для всего:
High ждёт low
Решение: разделить.
4. Без retry:
Потеря job
Решение: $tries + $backoff.
5. Без unique:
Дубли
Решение: ShouldBeUnique.
6. Воркер без memory:
OOM
Решение: --memory=256.
7. Failed не смотрят:
Растёт таблица
Решение: алерт.
8. Schedule без cron:
Не работает в проде
Решение: cron.
Итог
- Очереди — async обработка.
- Redis — драйвер в проде.
- Horizon — UI + метрики.
- Приоритеты — high/default/low.
- Retry + backoff — обязательно.
- Unique jobs — идемпотентность.
- Batches — группы.
- Chains — последовательности.
- Scheduler — cron + schedule:run.
- Gate — доступ к Horizon.
Дальше — Laravel Octane: Swoole и RoadRunner.