Назад в блог

PHP-FPM pm.max_children: параметр, который кладёт Bitrix под нагрузкой

Bitrix выдаёт 504. Nginx логи чистые. MySQL загружен на 40%. OPcache прогрет. Composite cache включён. И всё равно сайт мёртв в час пик.

Я видел этот сценарий в нескольких проектах. Каждый раз диагностика занимала слишком долго, потому что мы начинали не с того конца. Начинали с кода, с базы, с сетью — а нужно было начинать с конфига PHP-FPM.

Конкретный случай: интернет-магазин, акция, трафик вырос в три раза. TTFB улетел на 8-12 секунд. ~600 ошибок 504 в час. ps aux | grep php-fpm показывал ровно 5 процессов. Все пять заняты. Запросы в очередь — и там умирают по таймауту.

Параметр в конфиге: pm.max_children = 5. Дефолт ISPmanager. Для продакшн-Bitrix с каталогом от 10 000 SKU это потолок, который сработает раньше, чем ожидаешь.

Как PHP-FPM принимает запросы: очередь и воркеры

PHP-FPM работает по модели пула процессов. Nginx передаёт PHP-запросы через FastCGI-сокет, FPM помещает их в очередь. Свободный воркер берёт запрос из очереди и обрабатывает. Когда свободных воркеров нет — новые запросы ждут.

pm.max_children — это максимальное количество параллельных PHP-процессов в пуле. Если все max_children заняты, следующий запрос встаёт в очередь. Длина очереди управляется listen.backlog (по умолчанию 511). Когда и очередь переполнена — FPM начинает отказывать.

На большинстве VPS через ISPmanager или VestaCP этот параметр стоит 5. Это значение из дефолтных шаблонов панели, которое никто не меняет при выкатке в продакшн.

502 vs 504 — в чём разница

Оба кода говорят, что Nginx не дождался ответа от PHP-FPM, но причины разные.

504 Gateway Timeout — запрос ушёл в PHP-FPM, воркер начал его обрабатывать, но не уложился в fastcgi_read_timeout. Это либо медленный запрос (тяжёлый SQL, внешний API), либо воркер завис.

502 Bad Gateway — Nginx вообще не смог подключиться к FPM или получил немедленный отказ. Это случается, когда очередь переполнена и FPM сбрасывает соединение, либо процесс FPM упал.

На практике: если под нагрузкой видишь 504 — смотришь на время обработки запросов и очередь. Если 502 — на состояние самого FPM-процесса.

В нашем случае были именно 504: воркеры работали, но пять запросов одновременно обрабатывались по 2-3 секунды каждый (Bitrix с каталогом без composite на некэшированных страницах), следующие ждали, таймаут наступал раньше, чем очередь рассасывалась.

Формула расчёта pm.max_children для Bitrix

Формула простая:

pm.max_children = (RAM_total - RAM_OS - RAM_MySQL) / RAM_per_php_process

Средний PHP-FPM процесс Bitrix с prolog/epilog занимает 50-120 MB RAM. На сайте с включёнными модулями (sale, catalog, iblock) без оптимизации — ближе к 80-100 MB. Проверяйте своё значение через ps aux --sort -rss | grep php-fpm.

Пример для VPS 4 GB RAM:

  • Система + Nginx: ~512 MB
  • MySQL: ~1024 MB (конфиг innodb_buffer_pool_size 768 MB + overhead)
  • Доступно PHP: 4096 - 512 - 1024 = 2560 MB
  • Процесс Bitrix PHP: ~68 MB (среднее по замеру)
  • pm.max_children = 2560 / 68 ≈ 37

Мы поставили 38. TTFB под нагрузкой упал с 8-12 секунд до 1.1 секунды. Ошибки 504 исчезли за 5 минут после применения конфига.

Важно оставить запас на случай, что процессы раздуются. Не берите 100% доступной памяти — оставляйте 10-15% на рост. При pm.max_children = 38 и 68 MB на процесс пиковое потребление PHP ≈ 2.6 GB, что укладывается с запасом.

pm.dynamic vs pm.static — что выбирать для Bitrix

Три режима управления пулом.

Первый — pm.static: фиксированное количество воркеров, все запущены всегда. Предсказуемо, минус — память занята постоянно, даже ночью.

Второй — pm.dynamic: воркеры создаются и убиваются по необходимости. Параметры: pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers. Экономит RAM в периоды низкой нагрузки.

Третий — pm.ondemand: процесс создаётся только под входящий запрос. Для продакшна не подходит — первый запрос после паузы получает overhead на создание процесса.

Для продакшн-Bitrix средней нагрузки я использую pm.dynamic со следующими значениями (пример для VPS 4 GB, max_children=38):

pm = dynamic
pm.max_children = 38
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 500

pm.max_requests = 500 ограничивает жизнь воркера количеством обработанных запросов — помогает с утечками памяти в старых версиях Bitrix.

Если RAM больше 8 GB и нагрузка стабильная — pm.static с max_children = 60-80 даёт более предсказуемый результат.

Мониторинг пула без внешних агентов

PHP-FPM умеет отдавать статус через встроенный endpoint. Включается в www.conf:

pm.status_path = /fpm-status

Затем в Nginx добавляется location:

location = /fpm-status {
    allow 127.0.0.1;
    deny all;
    fastcgi_pass unix:/var/run/php/php-fpm.sock;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
}

После этого curl http://localhost/fpm-status показывает:

pool:                 www
process manager:      dynamic
start time:           01/Sep/2024:12:00:00 +0000
accepted conn:        12847
listen queue:         0
max listen queue:     127
idle processes:       8
active processes:     3
total processes:      11
max active processes: 38

listen queue больше нуля — запросы ждут. active processes близко к pm.max_children — пул на пределе. На это нужно ставить алерт.

В нашем случае перед инцидентом listen queue доходил до 127 при listen.backlog по умолчанию. Это была очередь из 127 запросов, которые либо дождались воркера, либо получили 504.

Что не менять вместе с pm.max_children

fastcgi_read_timeout в Nginx — отдельный параметр. Иногда его увеличивают, пытаясь «вылечить» 504. Это маскирует проблему: запросы будут ждать дольше, очередь вырастет, память закончится раньше. Сначала правьте пул — потом смотрите, остались ли медленные запросы.

memory_limit в php.ini не влияет на размер FPM-процесса напрямую. Он ограничивает память на один запрос, но сам процесс занимает больше из-за загруженных модулей Bitrix. Замеряйте через ps aux, не через memory_limit.

Итог

Дефолт pm.max_children = 5 от ISPmanager — это не баг и не саботаж. Это настройка для shared-хостинга, где ресурсы между сайтами делятся. На выделенном VPS с Bitrix-каталогом она работает только в спокойное время.

Формула займёт 10 минут. Применение конфига — перезапуск FPM без даунтайма (systemctl reload php8.1-fpm). Мониторинг через /fpm-status займёт ещё 15 минут.

Следующие вещи, которые стоит проверить на том же сервере — в статьях Как PHP OPcache убивает production Bitrix и Файловые сессии PHP убили наш сервер в 23:00. Все три проблемы из одного слоя, и чинятся одним аудитом конфига за полдня.