Laravel под нагрузкой: где приложение упирается первым
Тот же код, который держал 50 запросов в секунду, на 500 начинает отдавать 502 и держать соединения открытыми. Разбираем, что упирается раньше: пул БД, сессии в файлах, cold boot фреймворка и N+1.

Типичный сценарий: приложение на Laravel 11.x, PHP 8.3, работает под PHP-FPM за Nginx. На стенде и в первые месяцы всё ровно. Потом трафик растёт, и на 300–500 одновременных запросах сервис начинает отдавать 502 Bad Gateway, время ответа скачет с 80 мс до нескольких секунд, а в логах Nginx появляется upstream timed out. При этом CPU на сервере загружен не полностью. Значит, упирается не в вычисления, а в конкуренцию за ресурсы: воркеры FPM, соединения к БД, блокировки сессий.
Разберём узкие места по порядку, в котором они обычно проявляются под ростом нагрузки, и что мерить, чтобы понять, какое из них у вас первое. Инвалидация кеша, шардирование БД и переход на очереди для тяжёлых задач — отдельные темы; здесь только то, что бьёт по времени ответа синхронных запросов.
Первое, что кончается: воркеры PHP-FPM
Laravel обрабатывает запрос синхронно: пока идёт обращение к базе или внешнему API, воркер FPM занят и не берёт следующий запрос. Число одновременных запросов, которые вы физически можете обслужить, равно pm.max_children в конфиге пула FPM. Всё, что сверх — встаёт в бэклог сокета, а когда бэклог переполнен, Nginx получает отказ и отдаёт 502.
Формула простая: если средний запрос держит воркер 200 мс, один воркер даёт 5 запросов в секунду. Чтобы держать 500 rps, нужно либо 100 воркеров, либо ускорить сам запрос до 20 мс. Наращивать pm.max_children бесконечно нельзя: каждый воркер держит свой объём памяти.
Медленный запрос под нагрузкой опаснее, чем кажется: он не просто отвечает дольше — он держит воркер занятым и отнимает пропускную способность у всех остальных.
Оценить нужное число воркеров:
- посмотрите пиковую память одного процесса FPM (
psили черезpm.status_path); - разделите доступную под FPM память на этот объём — получите потолок
pm.max_children; - если потолок ниже, чем требует ваш rps при текущем времени ответа, узкое место не в железе, а в скорости запроса.
N+1 запросы: тихий убийца времени ответа
Самая частая причина того, что запрос держит воркер 200 мс вместо 20. Eloquent по умолчанию ленив: обращение к связи внутри цикла порождает отдельный SQL-запрос на каждую итерацию. На стенде с десятью записями это незаметно, на проде с сотней — сто лишних round-trip к базе.
// N+1: на каждый заказ — отдельный запрос за пользователем
$orders = Order::all();
foreach ($orders as $order) {
echo $order->user->name; // запрос в БД на каждой итерации
}
// Eager loading: два запроса вместо N+1
$orders = Order::with('user')->get();
foreach ($orders as $order) {
echo $order->user->name; // связь уже загружена
}Как ловить в бою: включите Model::preventLazyLoading() в AppServiceProvider для окружения local и staging — Laravel начнёт бросать исключение при ленивой загрузке, и вы поймаете N+1 до того, как он уедет в прод. Для наблюдения на самом проде подключите Laravel Telescope или логируйте медленные запросы на стороне БД.
Соединения к базе: пул кончается раньше воркеров
Каждый обрабатывающий запрос воркер FPM открывает своё соединение к PostgreSQL или MySQL. 100 воркеров — до 100 одновременных соединений. У PostgreSQL по умолчанию max_connections около 100, и часть из них зарезервирована под суперпользователя и обслуживание. Упрётесь в лимит — получите too many connections, и запросы начнут падать ещё до того, как кончатся воркеры FPM.
Точное значение max_connections в вашей инсталляции смотрите через SHOW max_connections; — оно зависит от конфига и хостинга. Наращивать его в лоб дорого: каждое соединение PostgreSQL — это отдельный процесс со своей памятью.
Рабочее решение — пулер соединений между приложением и базой. Для PostgreSQL это PgBouncer в режиме transaction pooling: приложение открывает много «дешёвых» соединений к пулеру, а он мультиплексирует их в небольшое число реальных соединений к базе.
| Симптом | Что упёрлось | Куда смотреть |
|---|---|---|
| 502, бэклог сокета растёт, CPU не полный | Воркеры FPM | pm.max_children, время ответа |
| too many connections в логах БД | Пул соединений | max_connections, PgBouncer |
| Время ответа растёт линейно с числом записей | N+1 запросы | Telescope, лог медленных запросов |
| Запросы к одному пользователю сериализуются | Блокировка сессии | драйвер сессий, session lock |
Блокировка сессий: параллельные запросы встают в очередь
По умолчанию Laravel хранит сессию в файле, и на время обработки запроса файл сессии блокируется. Если браузер шлёт несколько параллельных запросов от одного пользователя (частая история с SPA и предзагрузкой), они не выполняются параллельно — второй ждёт, пока первый освободит файл. Пользователь видит, что интерфейс «залипает», хотя сервер не загружен.
Лечится сменой драйвера сессий. Redis снимает файловую блокировку и заодно решает проблему шаринга сессий между несколькими серверами приложения за балансировщиком. Настройка — в config/session.php и переменной SESSION_DRIVER в .env; точный набор поддерживаемых драйверов сверяйте с документацией вашей версии Laravel.
Cold boot фреймворка на каждом запросе
PHP-FPM по своей модели заново инициализирует приложение на каждый запрос: загрузка сервис-провайдеров, чтение конфигов, компиляция роутов. Под нагрузкой это заметный фиксированный оверхед. Обязательный минимум для прода:
- Кеш конфигов:
php artisan config:cache— собирает все конфиги в один файл, .env перестаёт читаться на каждом запросе. - Кеш роутов:
php artisan route:cache— не работает, если в роутах есть замыкания; их придётся вынести в контроллеры. - Кеш событий:
php artisan event:cacheдля явно зарегистрированных слушателей. - OPcache в PHP: без него интерпретатор перекомпилирует PHP-файлы каждый раз. Проверьте, что
opcache.enable=1в конфиге PHP.
Радикальный шаг — Laravel Octane: приложение загружается один раз и держится в памяти воркеров Swoole или RoadRunner, обрабатывая запросы без повторного бутстрапа. Это убирает cold boot почти полностью, но меняет модель жизни объектов: синглтоны и статические свойства теперь живут между запросами, и утечка состояния между пользователями становится реальным риском. Переходить на Octane имеет смысл, когда вы уже закрыли N+1 и пул соединений, а не вместо них.
Что мерить и в каком порядке
Не оптимизируйте вслепую. Порядок диагностики:
- снимите время ответа под нагрузкой (
ab,wrkилиk6) при разной параллельности — 50, 200, 500 одновременных; - если CPU не в потолке, а 502 идут — смотрите бэклог сокета и статус FPM через
pm.status_path; - если в логах БД
too many connections— ставьте PgBouncer, а не наращивайте воркеры; - если время ответа растёт с объёмом данных — ищите N+1 через Telescope;
- только после этого думайте про Octane и горизонтальное масштабирование.
Что делать дальше
Начните с самого дешёвого: включите config:cache, route:cache и OPcache — это выключенные по умолчанию вещи, которые дают эффект без переписывания кода. Затем прогоните нагрузочный тест и посмотрите, что упирается первым по таблице выше. Тяжёлые операции (отправка писем, генерация отчётов, обращения к внешним API) выносите в очереди через php artisan queue:work, чтобы они не держали синхронный воркер — это следующая большая тема после того, как вы закрыли узкие места на пути обычного запроса.
IT-архитектор / SQL-оптимизатор: Поиск N+1, оптимизация запросов, code review, поиск багов.
Спросить за 20 ₽Источники: Laravel Octane — официальная документация, Laravel Configuration & Optimization
Частые вопросы
У меня Laravel 10, а не 11 — советы применимы?
Включил config:cache, и приложение перестало видеть изменения в .env
route:cache падает с ошибкой про замыкания
Наращивать pm.max_children или ставить больше серверов?
Стоит ли сразу переходить на Octane для скорости?
Как понять, что проблема именно в блокировке сессий, а не в БД?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.