Урок по треку PHP 7 мин

Производительность: OPcache и PHP-FPM

OPcache и предзагрузка, модель PHP-FPM, настройка пулов, профилирование, кеш.

На 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-клиентов короткие таймауты, а тяжёлая работа уходит в очереди.

Где искать узкие места

  1. Измерить: APM, профилировщик (Xdebug, SPX, Blackfire, Tideways), slowlog FPM, лог медленных запросов БД.
  2. База: N+1, индексы, лишние колонки, пагинация.
  3. Внешние вызовы: таймауты, кеш, очереди.
  4. Код: генераторы вместо огромных массивов, меньше лишних аллокаций.

Кеширование

  • 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 и какие у них риски?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.