Laravel 13.31 добавляет прерываемые задачи (interruptible jobs), позволяя воркерам очереди перехватывать сигнал SIGTERM, отправляемый во время деплоя, и завершать работу корректно, вместо того чтобы обрывать задачу на середине. Это дает преимущество командам, запускающим длительные задачи — обработку десятков тысяч строк, массовую рассылку писем или генерацию отчетов, — так как при развертывании новой версии данные остаются согласованными.
Почему деплои убивали задачи
Когда устанавливается новая версия, менеджеры процессов, такие как Supervisor, Docker и Kubernetes, просят существующие воркеры остановиться, отправляя SIGTERM — вежливый запрос на завершение. Большинство воркеров ждут завершения текущей задачи перед выходом. Проблема возникает, когда менеджер выделяет на выполнение запроса всего несколько секунд. Если задаче требуются минуты, менеджер переходит к SIGKILL, мгновенно убивая процесс. Задача так и не достигает согласованного состояния, оставляя частично обновленные записи, «осиротевшие» файлы или дублирующуюся работу.
Ответ Laravel: кооперативное прерывание
Laravel 13.31 вводит контракт под названием Interruptible, который класс задачи может реализовать, чтобы узнавать о запросе на завершение. Когда приходит SIGTERM, Laravel вызывает метод задачи interrupted(). Фреймворк не останавливает задачу автоматически; задача должна сама проверить флаг, который устанавливает Laravel, и завершиться. Эта кооперативная модель позволяет разработчикам завершить текущую итерацию цикла, откатить частичные изменения или создать контрольную точку перед выходом.
Как сделать задачу прерываемой
- Реализуйте контракт — добавьте
implements Interruptibleв определение класса задачи. - Проверяйте флаг внутри цикла обработки — вставляйте проверку на каждой итерации, чтобы понять, нужно ли остановить задачу.
- Выполните очистку — в методе
interrupted()сохраните любое состояние, которое позволит задаче возобновиться позже, или залогируйте прерывание для последующего анализа.
Главная ловушка: не используйте --once
Запуск воркера очереди с командой php artisan queue:work --once полностью отключает обработку сигналов. Воркер обрабатывает одну задачу и выходит, но он никогда не регистрирует обработчик SIGTERM. Следовательно, любая прерываемая задача будет игнорировать запрос на завершение и будет убита в процессе выполнения. Такая схема часто встречается в контейнерах, запускаемых через cron, и при разовых деплоях. Исправьте это, запуская режим демона (php artisan queue:work) всякий раз, когда вам нужны прерываемые задачи.
Отслеживание прерываний
Laravel генерирует два события, к которым можно подключиться:
- WorkerInterrupted — генерируется всякий раз, когда любой воркер получает SIGTERM. Логирование этого события дает общее представление о том, как часто деплои прерывают работу воркеров.
- JobInterrupted — генерируется только в том случае, если текущая задача реализует контракт Interruptible. Используйте это событие для запуска специфичной для задачи очистки, например, удаления временных файлов или обновления маркера «последней обработанной строки».
Прослушивание этих событий позволяет командам создавать дашборды, которые показывают влияние деплоев на работу и помогают выявлять задачи, которые часто прерываются.
Улучшение отчетности о размере очереди
В релизе добавлен метод totalSize() для менеджера очередей, который возвращает общее количество ожидающих задач во всех очередях. Это значение надежно для драйверов на базе базы данных и Redis, которые сообщают фактическое количество. Драйверы вроде SQS, Sync и Beanstalkd по-прежнему возвращают ноль, так как они не предоставляют счетчик через API Laravel. Командам, использующим SQS, следует по-прежнему полагаться на метрики CloudWatch для определения глубины очереди.
Что это значит для различных конфигураций
- Docker/Kubernetes — теперь можно эффективно использовать период плавного завершения работы (graceful shutdown). Увеличьте период ожидания завершения (termination grace period), чтобы у задач было несколько секунд на то, чтобы заметить флаг и выйти корректно.
- Supervisor — установите параметр
stopwaitsecsвыше времени, за которое, как вы ожидаете, задача завершится после получения флага прерывания. - Устаревшие задачи — просмотрите любые задачи, которые работают дольше, чем таймаут менеджера, и, где это возможно, сделайте их прерываемыми.
Контраргумент: дополнительная сложность
Эта функция не заменяет правильную настройку таймаутов. Если цикл задачи никогда не проверяет флаг прерывания, менеджер все равно отправит SIGKILL. Командам необходимо провести аудит длительных путей выполнения кода и вставить проверки в логических точках разрыва. Некоторым разработчикам может показаться, что дополнительный шаблонный код — реализация контракта и добавление проверок флага — излишен для коротких задач, которые и так успевают завершиться в окно завершения работы.
Чек-лист действий
- Просканируйте скрипты развертывания на наличие флага
--onceи замените его режимом демона там, где используются прерываемые задачи. - Выявите самые длительные задачи (те, которые обрабатывают десятки тысяч строк или выполняются несколько минут) и добавьте им контракт Interruptible.
- Вставьте проверку флага внутри каждой итерации цикла и перенесите необходимый код завершения в метод
interrupted(). - Подключите слушателей к событиям
WorkerInterruptedиJobInterrupted, чтобы собирать метрики о том, как часто развертывания влияют на процесс обработки. - Для очередей Redis или баз данных используйте
totalSize()для мониторинга очереди; для SQS оставьте существующие оповещения CloudWatch.
Относитесь к остановкам, вызванным развертыванием, как к скоординированному этапу, а не как к резкому завершению процессов. Laravel 13.31 обеспечивает согласованность данных и избавляет от операционных сложностей, связанных с незавершенными задачами. Компромиссом является небольшое увеличение ответственности кода, но команды, полагающиеся на интенсивную фоновую обработку, получают более надежную рабочую среду.
