Обновление мажорной версии Laravel без простоя: что ломается
Мажорный апгрейд Laravel — это не «поменять цифру в composer.json». Разбираем, что происходит с автозагрузкой, кешем конфигурации и очередями в момент деплоя и почему app` down` не спасает.

Команда обновляет проект с Laravel 10 на 11, прогоняет тесты локально, катит на прод — и первые тридцать секунд после деплоя ловит серию Class not found и заваленную очередь. Причина не в коде: старые PHP-воркеры держат в памяти классы предыдущей версии, а web-запросы уже читают новый автозагрузчик. Мажорное обновление Laravel ломается не в тестах, а в момент, когда на одном сервере одновременно живут два состояния приложения.
Разберём, что именно меняется под капотом при мажорном апгрейде и почему стандартный php artisan down закрывает не ту дыру, которую вы думаете. Тема инвалидации схемы БД при миграциях большая — здесь смотрим только на слой приложения и деплой, миграции затрагиваем в части совместимости.
Что физически меняется при мажорном апгрейде
Мажорная версия (10 → 11, 11 → 12) по семантическому версионированию разрешает несовместимые изменения. На практике на прод влияют четыре слоя, и у каждого своя точка отказа.
1. Автозагрузчик Composer
Composer генерирует карту классов в vendor/composer/. При обновлении composer install переписывает эти файлы. PHP-FPM с включённым OPcache держит скомпилированный байткод старых файлов в памяти. Пока OPcache не сброшен, часть процессов резолвит классы по старой карте, часть — по новой. Если сигнатура метода фреймворка изменилась, вы получаете Fatal error ровно на тех запросах, что попали к «непрогретому» процессу.
2. Кеш конфигурации и маршрутов
Файлы bootstrap/cache/config.php и routes-*.php — это сериализованный слепок конфигурации на момент сборки. Между мажорными версиями меняются структура config/*.php и способ регистрации маршрутов. Laravel 11 радикально изменил структуру каталога: Kernel.php ушёл, конфигурация приложения переехала в bootstrap/app.php. Старый config.php в кеше может ссылаться на несуществующие ключи.
3. Долгоживущие процессы: очереди и Octane
Воркер queue:work загружает фреймворк один раз и держит его в памяти между задачами — в этом и смысл. После деплоя воркер продолжает исполнять старый код, пока его явно не перезапустить. То же с Laravel Octane (Swoole/RoadRunner): приложение бутстрапится один раз на старте воркера.
4. Сессии и сериализованные данные
Если в сессии или кеше лежат сериализованные объекты моделей, а между версиями изменился их набор свойств, unserialize вернёт частично битый объект. Это тихая ошибка: исключения нет, но поведение неверное.
Мажорный апгрейд без простоя — это не про скорость деплоя, а про то, чтобы в любой момент времени на сервере жило ровно одно согласованное состояние приложения. Всё остальное — следствие.
Почему artisan down не решает проблему
Команда php artisan down отдаёт 503 на входящих web-запросах. Но она:
- не останавливает уже запущенные воркеры очередей — они читают из брокера дальше;
- не сбрасывает OPcache — старый байткод остаётся в памяти FPM;
- не защищает от гонки между
composer installи первым запросом после снятия заглушки; - по умолчанию сама зависит от загруженной конфигурации, которую вы как раз собираетесь менять.
Классический сценарий поломки: деплой на месте (git pull в тот же каталог). Пока composer install переписывает vendor/, входящий запрос успевает подхватить половину старых, половину новых файлов. Отсюда — плавающие Class not found, которые не воспроизводятся на второй попытке.
Стратегия деплоя, которая убирает гонку
Единственный надёжный способ — атомарное переключение: собрать релиз в отдельном каталоге и переключить симлинк одной операцией. Так работают Deployer, Envoyer и Laravel Vapor. Симлинк меняется атомарно на уровне ФС, поэтому запрос видит либо целиком старый релиз, либо целиком новый.
Порядок для zero-downtime апгрейда
- Собрать новый релиз в каталоге
releases/новый:composer install --no-dev --optimize-autoloader, затем кеширование —php artisan config:cache,route:cache,view:cache,event:cache. Ошибка на этом шаге не должна касаться прода. - Прогнать совместимые миграции заранее — те, что не ломают старую версию кода (добавление nullable-колонок, новых таблиц). Несовместимые изменения схемы разбить на два деплоя (expand/contract).
- Переключить симлинк
current → releases/новыйодной командой. - Перезагрузить FPM:
systemctl reload php8.3-fpm— reload делает graceful-рестарт, текущие запросы дорабатывают на старом коде, новые уходят на новый. Это же сбрасывает OPcache для новых процессов. - Перезапустить воркеры очередей:
php artisan queue:restart. Команда не убивает процессы сразу, а ставит флаг в кеше — воркер завершает текущую задачу и мягко выходит, после чего его поднимает супервизор (Horizon/Supervisor). - Для Octane —
php artisan octane:reload: перезапуск воркеров без разрыва соединений.
Как понять, что нужен expand/contract
Если миграция удаляет или переименовывает колонку, старый код (который ещё крутится в graceful-окне и в незавершённых воркерах) обратится к несуществующему полю. Разбивайте на два релиза:
| Этап | Что делаем | Совместимость |
|---|---|---|
| Expand | Добавить новую колонку, писать в обе | Старый и новый код работают |
| Deploy кода | Код читает только новую колонку | Старая ещё на месте |
| Contract | Удалить старую колонку | Старого кода уже нет |
Порядок команд кеширования: где путаются
Частая ошибка — закешировать конфигурацию до composer install или на старой структуре каталогов. Правильная последовательность в сборочном скрипте нового релиза (Laravel 11.x, PHP 8.2+):
# сборка внутри каталога нового релиза, прод его ещё не видит
composer install --no-dev --optimize-autoloader --no-interaction
# очистить возможный кеш, унаследованный из репозитория
php artisan config:clear
# собрать кеши заново под новую версию
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# проверить, что приложение вообще бутстрапится на новой версии
php artisan aboutКоманда artisan about (доступна с Laravel 9) — дешёвая проверка: если фреймворк не смог собрать конфигурацию под новую версию, она упадёт здесь, в изолированном каталоге, а не на проде.
Если не получилось
Class "Illuminate\..." not foundсразу после деплоя. OPcache не сброшен или FPM не перезагружен. Выполнитеsystemctl reloadсервиса FPM и убедитесь, чтоopcache.validate_timestampsучитывает смену симлинка (при деплое через симлинк надёжнее reload, чем полагаться на таймстемпы).- Очередь падает с ошибкой сигнатуры метода после апгрейда. Воркеры не перезапущены.
php artisan queue:restartи проверьте, что супервизор действительно поднял процессы:supervisorctl status. Undefined array keyв конфигурации. Закеширован старыйconfig.php. Удалитеbootstrap/cache/config.php, пересоберите. В Laravel 11 сверьте, что миграция на новую структуруbootstrap/app.phpвыполнена по официальному гайду.- Тихо ломается логика после деплоя. Похоже на битую десериализацию сессий/кеша. При мажорном апгрейде безопаснее сбросить кеш приложения и, если сессии в БД/Redis, спланировать их инвалидацию заранее.
- Миграция уронила старый код в graceful-окне. Значит, изменение схемы было несовместимым — переделайте по схеме expand/contract из таблицы выше.
Что делать дальше
Перед мажорным апгрейдом прогоните официальный upgrade guide для целевой версии построчно — там перечислены именно breaking changes, а не общие советы. Настройте деплой через атомарный симлинк (Deployer или Envoyer), если ещё катите git pull на месте: без этого гонку автозагрузчика убрать нельзя. В проде после переключения смотрите на два показателя первые пять минут: долю 5xx на входящем трафике и глубину очереди — всплеск любого из них означает, что воркеры или FPM не перезапустились.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Laravel Upgrade Guide (официальная документация), Laravel Octane — документация
Частые вопросы
У меня Laravel 10, а не 11 — порядок тот же?
А если у меня нет отдельного сервера под staging для сборки релиза?
Я перезагрузил FPM, но всё равно ловлю старый код — почему?
Обязательно ли делать expand/contract или можно накатить миграцию сразу?
Тесты зелёные, стоит ли вообще бояться прода?
Что делать, если после переключения симлинка нужно срочно откатиться?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.