Очереди в Laravel: когда пора перестать всё делать в запросе
Иногда один HTTP-запрос тащит за собой отправку писем, генерацию PDF, импорт из Excel и ещё пол-проекта. Пользователь ждёт, сервер задыхается. В этот момент самое время вынести тяжёлую работу в очереди.
Что уносить в очередь
Хорошее правило: всё, что не нужно для немедленного ответа пользователю, можно отправить в фон.
Обычно это: • массовая отправка email/SMS/уведомлений • генерация отчётов, PDF, архивов • импорты/экспорты (Excel, CSV, API) • синхронизация с внешними сервисами • тяжёлые расчёты и агрегации
Контроллер должен только принять данные, запустить нужные Jobs и вернуть быстрый ответ.
Базовый пример Job
Создадим задачу для генерации отчёта: php artisan make:job GenerateReport
Класс Job: php
namespace AppJobs;
use AppServicesReportService; use IlluminateBusQueueable; use IlluminateContractsQueueShouldQueue; use IlluminateFoundationBusDispatchable; use IlluminateQueueInteractsWithQueue; use IlluminateQueueSerializesModels;
class GenerateReport implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct( public int $userId, ) {}
public function handle(ReportService $service): void { $service-generateForUser($this->userId); } }
Запуск из контроллера: GenerateReport::dispatch($user->id);
HTTP-ответ уходит сразу, а отчёт собирается в фоне.
Настройка воркера
В .env указываем драйвер очереди: QUEUE_CONNECTION=database
Создаём таблицу для задач и накатываем миграции: php artisan queue:table php artisan migrate
Запускаем воркер (дальше оборачиваем в systemd/supervisor): php artisan queue:work --tries=3
Этого уже достаточно, чтобы разгрузить большинство тяжёлых контроллеров.
Таймауты и повторные попытки
Важно не забыть про две вещи: • количество попыток выполнения задачи (--tries или свойство $tries в Job) • максимальное время работы задачи (--timeout или свойство $timeout)
Пример: class GenerateReport implements ShouldQueue { public int $timeout = 120; public int $tries = 3; }
Так задача не будет висеть вечно и не будет долбить внешний сервис бесконечно.
Чек-лист перед выносом в очередь • Логика действительно не нужна прямо сейчас пользователю • Вся нужная информация передаётся в Job через параметры, а не берётся из сессии • Логика идемпотентна: повторный запуск не создаёт дубли и кривые данные • Настроены таймауты и количество попыток • Есть логи и базовый мониторинг очередей
Частые ошибки • В Job передают целые модели вместо ID, потом ловят проблемы с сериализацией • Тяжёлый код остаётся в контроллере, а Job превращается в тонкую обёртку • Не настраивают мониторинг очередей — воркер умер, а все думают, что система работает • Не разделяют очереди по типам задач (high, default, low), из-за чего тяжёлый отчёт блокирует отправку критичных уведомлений
Итог
Очереди в Laravel — это базовый инструмент, а не «опция на потом». Как только один HTTP-запрос начинает делать больше одной-двух тяжёлых операций, выносите их в Job. Пользователю — быстрый ответ, серверу — спокойная жизнь, вам — предсказуемое поведение проекта.