«server reached pm.max_children setting (5)» и 504 на сайте: как посчитать число процессов PHP-FPM для маленькой виртуальной машины

Что было. Виртуальная машина начального уровня (один vCPU, менее 2 ГБ памяти), nginx + PHP-FPM, плюс фоновые Python-службы, которые ходят во внешние API. Под нагрузкой сайт отдаёт 504, в журнале PHP-FPM: WARNING: [pool www] server reached pm.max_children setting (5), consider raising it. Администратор поднимал pm.max_children до 32, но значение «откатывалось», а сайт оставался недоступным.

Что означает предупреждение. Пул исчерпал разрешённое число рабочих процессов, новые запросы стоят в очереди, nginx не дожидается ответа и отдаёт 504. Значение 5 по умолчанию мало для чего угодно, кроме тестов. Но просто поставить 32 на машине с 1,75 ГБ памяти нельзя: каждый процесс PHP занимает 30–80 МБ (а с тяжёлыми CMS до 150), и 32 процесса упрутся в OOM-killer, который снесёт и PHP, и базу. «Откат» значения, о котором писал автор, скорее всего был правкой не того файла: у PHP несколько пулов и несколько мест, где лежит www.conf (в примере /usr/local/etc/php-fpm.d/, у пакетных установок /etc/php/8.x/fpm/pool.d/).

Как посчитать.

  1. Средняя память процесса: ps -C php-fpm8.3 --no-headers -o rss | awk '{s+=$1} END {print s/NR/1024 " MB"}' (имя процесса под вашу версию).
  2. Сколько памяти можно отдать PHP: всё, что осталось после ОС, nginx, базы и фоновых служб. На машине из примера это в лучшем случае 600–800 МБ.
  3. pm.max_children = доступная память / память на процесс. При 50 МБ на процесс и 700 МБ доступных получается 14, не 32.
  4. Остальное: pm = dynamic, pm.start_servers примерно четверть от максимума, pm.min_spare_servers/pm.max_spare_servers вокруг него, pm.max_requests = 500, чтобы процессы перезапускались и не текли. На совсем маленьких машинах pm = ondemand с pm.process_idle_timeout = 10s экономит память в простое.
  5. systemctl restart php8.3-fpm и проверить systemctl status, что подхватился именно правленый файл (php-fpm8.3 -tt покажет итоговую конфигурацию).

Если предупреждение не уходит. Значит запросы медленные, а не многочисленные: смотрите request_slowlog_timeout и slowlog в пуле, включите OPcache, проверьте, не ждёт ли PHP внешний API синхронно. Фоновые задачи, которые ходят во внешние сервисы, выносить из веб-запроса в очередь. И честно оценить: одна машина с одним ядром для сайта, базы и фоновых воркеров это компромисс, который под нагрузкой ломается первым.

Параметры пула описаны в документации PHP.