Жизненный цикл запроса в Laravel под нагрузкой
Приложение на Laravel начинает отдавать 502 и таймауты при трёх сотнях одновременных запросов, хотя код не менялся. Разбираем, что происходит внутри на каждый запрос и где именно упирается производительность.

Проект на Laravel 11 держал 40 запросов в секунду и держал бы больше, но при пике трафика PHP-FPM выстроил очередь, а Nginx начал отдавать 502 Bad Gateway. CPU при этом загружен на 60%, база отвечает за 5 мс, а latency 95-го перцентиля подскочил с 80 мс до 3 секунд. Симптом знакомый: приложение не столько медленное, сколько дорогое на каждый запрос. Чтобы понять, где теряется время, нужно пройти по жизненному циклу запроса и посмотреть, что из него выполняется на каждом обращении.
Разбираем поведение именно под нагрузкой: что бутстрапится заново на каждый запрос в классической схеме PHP-FPM, почему это дорого и что меняет Octane. Тюнинг Nginx, настройка базы и профилирование конкретных контроллеров — отдельные темы, здесь только цикл запроса и его цена.
Что происходит на каждый запрос в PHP-FPM
В классической схеме (Nginx + PHP-FPM) PHP-процесс обрабатывает один запрос и обнуляет память. Это значит, что весь фреймворк собирается заново каждый раз. Порядок такой:
- Автозагрузка Composer — подключается
vendor/autoload.php, регистрируются PSR-4 маппинги классов. - Создание контейнера —
Applicationинстанцируется вpublic/index.php, регистрируются базовые биндинги. - Загрузка сервис-провайдеров — фреймворк проходит по всем провайдерам из
config/app.phpи композер-пакетов: сначалаregister(), потомboot(). Это самый недооценённый по стоимости этап. - Маршрутизация — HTTP-ядро прогоняет запрос через глобальные middleware, сопоставляет URI с маршрутом, применяет middleware группы и маршрута.
- Разрешение контроллера — контейнер собирает контроллер и его зависимости через рефлексию, вызывает метод, отдаёт
Response. - Завершение — отрабатывают terminable middleware, ответ отправляется, память процесса очищается.
Пункты 1–3 — это чистые накладные расходы фреймворка. Они выполняются до того, как ваш код вообще начнёт работать. И повторяются на каждый запрос.
Почему провайдеры — узкое место
Каждый сервис-провайдер с методом boot(), который выполняется всегда (а не лениво), добавляет фиксированную стоимость к каждому запросу. Пакет, который на старте читает конфиг, парсит YAML, компилирует какой-нибудь шаблонизатор или регистрирует наблюдателей Eloquent, платит эту цену независимо от того, нужен он в текущем запросе или нет.
Под нагрузкой дорогая не логика контроллера, а то, что фреймворк делает до него. Двадцать eager-провайдеров по 2 мс каждый — это 40 мс фиксированного налога на любой, даже самый простой ответ.
Проверить состав загруженных провайдеров можно через php artisan about (доступна с Laravel 9). Она покажет окружение, версии и включённое кеширование.
Как замерить, а не гадать
Прежде чем что-то оптимизировать, нужны цифры. Не выдумывайте узкое место — замерьте его на своём железе и своих данных.
- Нагрузочный тест — прогоните эндпоинт через
k6илиwrkс реальным профилем параллельности. Фиксируйте не среднее, а p95 и p99 latency и число ошибок. - Профилировщик на запрос — Laravel Telescope в dev-окружении покажет, сколько времени ушло на запросы к базе, сколько их было и какие дублируются. В проде Telescope включать не стоит: он сам добавляет накладные расходы и пишет много данных.
- Профилирование PHP — XHProf или Blackfire покажут разбивку по функциям и точно ответят, сколько времени съедает бутстрап фреймворка против вашего кода.
- OPcache — убедитесь, что он включён и не сбрасывается. Без OPcache PHP перекомпилирует все файлы фреймворка на каждый запрос, и это добьёт latency сильнее всего остального.
Порядок действий: сначала OPcache и кеш конфигов, потом замер, и только потом решения посерьёзнее.
Три уровня снижения стоимости запроса
Уровень 1 — кеширование бутстрапа
Артизан-команды собирают часть работы в один файл, чтобы не повторять её на каждый запрос. Выполняются в деплое, не в рантайме:
# Laravel 11.x, выполнять в пайплайне деплоя
# Сборка кеша конфигов в один файл
php artisan config:cache
# Кеш маршрутов (только если нет замыканий в routes)
php artisan route:cache
# Кеш скомпилированных провайдеров и биндингов
php artisan optimize
# Прекомпиляция Blade-шаблонов
php artisan view:cacheПосле config:cache вызовы env() вне файлов конфигурации возвращают null — это частая ловушка. Читайте окружение только в config/*.php, а в коде обращайтесь через config().
Уровень 2 — отложенные провайдеры
Провайдер, реализующий DeferrableProvider с методом provides(), регистрируется, но не бутстрапится, пока не понадобится один из его сервисов. Если провайдер нужен не в каждом запросе — сделайте его отложенным. Это снимает часть фиксированного налога из пункта 3 жизненного цикла.
Уровень 3 — Laravel Octane
Octane меняет модель выполнения радикально: фреймворк бутстрапится один раз при старте воркера и остаётся в памяти между запросами. Пункты 1–3 жизненного цикла перестают повторяться. Запросы обслуживают воркеры Swoole или RoadRunner.
| Параметр | PHP-FPM | Octane (Swoole/RoadRunner) |
|---|---|---|
| Бутстрап фреймворка | на каждый запрос | один раз при старте воркера |
| Стоимость простого ответа | высокая (весь бутстрап) | низкая (только логика) |
| Изоляция состояния | полная, память чистая | состояние живёт между запросами |
| Риск утечек памяти | минимальный | реальный, нужен контроль |
| Порог входа | стандартный стек | расширение Swoole или бинарь RoadRunner |
Выигрыш от Octane тем заметнее, чем больше доля бутстрапа в общем времени ответа. Для тяжёлого эндпоинта с секундным запросом к внешнему API экономия 30 мс на бутстрапе не решает ничего. Для легковесного JSON-API, где сам ответ считается за 5 мс, а бутстрап занимает 40, Octane убирает основную часть latency.
Где Octane ломается
Долгоживущий воркер — это другая модель, и привычные допущения перестают работать.
- Синглтоны переживают запросы. Сервис, зарегистрированный как singleton и хранящий данные конкретного пользователя, отдаст их следующему запросу в этом воркере. Это утечка данных между пользователями, а не просто баг.
- Статические свойства накапливают состояние. Всё, что вы кладёте в
static, живёт до перезапуска воркера. - Утечки памяти. Если каждый запрос добавляет что-то в глобальный массив или коллекцию, воркер будет пухнуть, пока его не убьёт лимит. Настройте
max_requests— периодический перезапуск воркера как страховка. - Глобальные хелперы состояния. Библиотеки, полагающиеся на суперглобалы
$_SERVER/$_GETнапрямую в обход запроса Laravel, могут вести себя непредсказуемо.
Octane сбрасывает состояние приложения и сессии между запросами, но только для сервисов, которые он знает. Свой код с состоянием — ваша ответственность.
Что делать дальше
Сначала выжмите бесплатное: включите OPcache, прогоните config:cache, route:cache и view:cache в деплое. Замерьте p95 до и после — часто этого хватает, чтобы 502 ушли. Дальше профилируйте бутстрап через Blackfire или XHProf и посмотрите на eager-провайдеры: те, что не нужны на каждый запрос, переведите в отложенные. Octane берите, когда цифры показывают, что бутстрап — реальная доля времени ответа, и вы готовы аудировать код на утечки состояния. Что мерить в проде постоянно: p95/p99 latency, число активных PHP-FPM или Octane воркеров и потребление памяти на воркер.
IT-архитектор / SQL-оптимизатор: Поиск N+1, оптимизация запросов, code review, поиск багов.
Спросить за 20 ₽Источники: Laravel — Request Lifecycle, Laravel Octane — официальная документация
Частые вопросы
У меня Laravel 10, а не 11 — команды из статьи сработают?
Я сделал config:cache, и приложение перестало видеть переменные окружения. Что не так?
Octane точно ускорит моё приложение?
Включил все кеши, но 502 всё равно появляются под нагрузкой. Куда смотреть?
Можно ли использовать Telescope в проде для профилирования под нагрузкой?
Как понять, что синглтон в Octane течёт состоянием между запросами?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.