На middle и senior собесах по PHP спрашивают, как PHP исполняет запросы в продакшене, что делает OPcache и как настроить PHP-FPM под нагрузку.
Модель исполнения
nginx принимает HTTP и передаёт PHP-запросы в PHP-FPM по FastCGI. FPM держит пул воркеров, каждый обрабатывает один запрос целиком, затем всё состояние запроса уничтожается (share-nothing). Одновременно обрабатывается не больше pm.max_children запросов, остальные ждут в очереди сокета.
OPcache
Без OPcache каждый запрос читает, разбирает и компилирует все PHP-файлы. OPcache хранит байткод в общей памяти для всех воркеров.
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=50000
opcache.validate_timestamps=0 ; в продакшене: не проверять изменения файлов
С validate_timestamps=0 после деплоя нужен reload FPM: иначе останется старый код. Удобная схема: каталог релиза, атомарная смена symlink, systemctl reload php-fpm.
Дополнительно: opcache.preload (7.4) загружает классы фреймворка при старте пула, JIT (8.0) ускоряет вычисления, но почти не помогает приложению, которое ждёт базу.
Настройка PHP-FPM
pm = dynamic
pm.max_children = 100 ; память под PHP / размер воркера
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
pm.max_requests = 1000 ; перезапуск воркера против утечек
request_terminate_timeout = 30s
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm/slow.log
pm.status_path = /fpm-status
pm.max_childrenсчитают от памяти: 6 ГБ под PHP и 60 МБ на воркер дают около 100.staticдля стабильной высокой нагрузки,dynamicдля смешанной,ondemandдля редко используемых пулов.- Признаки нехватки: растёт
listen queue, в логеserver reached pm.max_children.
Если внешний API начал отвечать по 30 секунд, все воркеры застрянут в ожидании, и сайт ляжет. Поэтому у HTTP-клиентов короткие таймауты, а тяжёлая работа уходит в очереди.
Где искать узкие места
- Измерить: APM, профилировщик (Xdebug, SPX, Blackfire, Tideways), slowlog FPM, лог медленных запросов БД.
- База: N+1, индексы, лишние колонки, пагинация.
- Внешние вызовы: таймауты, кеш, очереди.
- Код: генераторы вместо огромных массивов, меньше лишних аллокаций.
Кеширование
- Redis или Memcached для дорогих вычислений и общих данных, с TTL и сбросом по событию.
- Защита от одновременного пересчёта (cache stampede): блокировка или stale-while-revalidate (
Cache::flexibleв Laravel 11+). - HTTP-кеш и CDN для публичных страниц.
- Кеш конфигурации и маршрутов фреймворка, оптимизированный автозагрузчик Composer.
Долгоживущие рантаймы
RoadRunner, Swoole, FrankenPHP и Laravel Octane загружают приложение один раз и обслуживают много запросов. Выигрыш на загрузке фреймворка, но появляются утечки состояния между запросами и памяти, а соединения с базой надо переподключать.
Частые ошибки
- OPcache выключен или кеш переполнен.
pm.max_childrenпо числу ядер, а не по памяти.- Нет таймаутов к внешним сервисам.
memory_limit = -1вместо потоковой обработки.- Оптимизация без профилирования.
Что спросят на собесе
- Что делает OPcache и что важно при деплое?
- Как работает PHP-FPM, как подобрать
pm.max_children? - Чем отличаются режимы
static,dynamic,ondemand? - Как найти, почему эндпоинт медленный?
- Что такое Octane и RoadRunner и какие у них риски?