PHP-FPM (FastCGI Process Manager) керує пулом окремих процесів. Кожен обробляє один PHP-запит і звільняється для наступного. Замість того щоб веб-сервер сам запускав інтерпретатор PHP на кожен виклик, він передає запит уже готовому процесу через unix-сокет або TCP-порт, а FPM стежить, скільки таких процесів тримати живими. Nginx і Apache через mod_proxy_fcgi однаково добре працюють із цією моделлю. Тому FPM став стандартом для стеку, де PHP не вбудований у веб-сервер як модуль.
Дефолтний конфіг пакета з дистрибутива розрахований на маленький сервер із мінімальним трафіком. На продакшн-навантаженні він або марнує пам’ять, або вбиває сайт чергою запитів, що чекають на вільний процес. Різниця між сайтом, що витримує сплеск трафіку, і сайтом, що падає в 502, часто зводиться до п’яти рядків у файлі пулу.
Де лежать конфігураційні файли пулу
FPM розділяє налаштування на два рівні. Загальний файл php-fpm.conf відповідає за сам демон: шлях до pid-файлу, налаштування syslog, поведінку при аварійному перезапуску дочірніх процесів. Другий рівень, файли пулів у теці pool.d, описує окремий обробник для конкретного сайту чи застосунку. На системах Debian і Ubuntu типовий шлях виглядає як /etc/php/8.3/fpm/pool.d/www.conf. На CentOS і похідних дистрибутивах частіше зустрічається /etc/php-fpm.d/www.conf. Номер версії PHP у шляху змінюється залежно від встановленої версії, а сама назва пулу www є довільною і переозначається під кожен проєкт.
Для сайту з високим навантаженням окремий пул на застосунок дає перевагу, якої немає в спільному конфігу. Можна обмежити пам’ять і кількість процесів для одного клієнта, не зачепивши інші сайти на тому самому сервері. Це особливо помітно на хостингах, де кілька WordPress-інсталяцій живуть поруч, і один зламаний плагін здатний забрати весь пул процесів собі.
Три режими менеджера процесів
Директива pm визначає, як FPM керує кількістю живих процесів. Від цього вибору залежить решта налаштувань пулу. Режим static тримає постійно фіксовану кількість процесів, задану через pm.max_children, і жоден запит не чекає на запуск нового воркера. Платити за це доводиться пам’яттю: усі процеси зарезервовані навіть тоді, коли трафіку немає. Режим dynamic коливає кількість процесів між pm.min_spare_servers і pm.max_spare_servers, стартуючи з pm.start_servers, а межу зверху все одно ставить pm.max_children. Третій режим, ondemand, взагалі не тримає жодного воркера в очікуванні. Процес створюється на перший запит і завершується після pm.process_idle_timeout бездіяльності.
Static підходить для навантаженого продакшну зі стабільним трафіком, де економія на старті процесу важливіша за економію пам’яті в тихі години. Dynamic лишається розумним компромісом для більшості сайтів середнього масштабу. Ondemand варто розглядати там, де запитів мало і рідко: наприклад, на внутрішній адмін-панелі чи staging-середовищі, а не на публічному магазині з постійним потоком відвідувачів.
Як порахувати pm.max_children через пам’ять процесу
Найпоширеніша помилка в тюнінгу пулу, це підібрати pm.max_children навмання або скопіювати чуже значення з форуму. Правильний розрахунок починається з вимірювання реальної ваги одного php-fpm процесу командою ps -eo rss,cmd –sort -rss. Середнє значення в стовпці RSS ділять на обсяг оперативної пам’яті, доступний саме для PHP.
Візьмемо конкретний приклад. Сервер має 4 гігабайти оперативної пам’яті, з яких 1 гігабайт зарезервовано під операційну систему, базу даних і кеш. На FPM лишається 3 гігабайти. Якщо середній процес важить 60 мегабайт, ділення 3072 на 60 дає приблизно 51 процес як верхню межу. Це і є значення pm.max_children для такого сервера, а не довільна цифра з чужого гайду.
| Параметр | Формула | Приклад |
|---|---|---|
| pm.max_children | RAM для FPM / вага процесу | 3072 МБ / 60 МБ = 51 |
| pm.start_servers | 25% від max_children | 13 |
| pm.min_spare_servers | 25% від max_children | 13 |
| pm.max_spare_servers | 75% від max_children | 38 |
Похідні параметри резерву процесів
Коли максимум відомий, інші директиви режиму dynamic виводяться з нього як частка. pm.start_servers задає, скільки процесів піднімається одразу при запуску служби. Зазвичай береться чверть від max_children, щоб сервіс не чекав на розігрів під час першого сплеску трафіку після рестарту. pm.min_spare_servers тримає той самий поріг простою: якщо вільних процесів менше за це число, FPM додає нові. pm.max_spare_servers обмежує запас зверху, і коли простих процесів стає забагато, зайві завершуються, звільняючи пам’ять для інших сервісів на машині.
Занижене pm.min_spare_servers змушує FPM постійно наздоганяти попит, форкаючи нові процеси в момент сплеску. Саме ця затримка на форк відчувається користувачем як повільна перша сторінка після тиші. Завищене pm.max_spare_servers тримає пам’ять зарезервованою про запас без жодної користі в години низького трафіку.
Тайм-аути запиту і захист від зависань
Окремий скрипт, що завис через нескінченний цикл або мертвий зовнішній API, здатний утримувати процес назавжди. Директива max_execution_time у php.ini обмежує час виконання самого PHP-коду. Вона не завжди спрацьовує, якщо скрипт застряг у системному виклику поза інтерпретатором. Для такого випадку існує request_terminate_timeout на рівні пулу: FPM примусово вбиває воркер, якщо запит триває довше заданого часу, і повертає клієнту помилку 502 замість того щоб тримати ресурс заблокованим годинами.
Значення тайм-ауту вибирають виходячи з найповільнішої легітимної операції на сайті. Якщо звичайна сторінка рендериться за 200 мілісекунд, а генерація великого звіту в адмінці займає 40 секунд, тайм-аут ставлять із запасом над цими 40 секундами. Інакше легітимний запит обірветься так само, як завислий.
Slowlog: пошук повільних скриптів
Коли сайт гальмує, а причина незрозуміла, вбудований slowlog дає точку опори швидше за зовнішній профайлер. Директива request_slowlog_timeout визначає поріг, після якого FPM записує backtrace виконання скрипта у файл, вказаний у slowlog. Директива request_slowlog_trace_depth задає глибину цього стека викликів. Типовий поріг для продакшну лежить у діапазоні від трьох до шести секунд, залежно від того, яка затримка вже вважається проблемою на конкретному проєкті.
Backtrace у slowlog показує не просто факт гальмування, а конкретну функцію і рядок коду, де процес застряг у момент дампу. Наступний крок після ввімкнення логу зазвичай веде прямо в код застосунку. Далі вгадувати налаштування пулу вже не доводиться.
Сторінка статусу для моніторингу пулу
Директива pm.status_path вмикає ендпоінт, який віддає поточний стан пулу у форматі, придатному для парсингу. Він показує кількість активних і простих процесів, довжину черги очікування, максимальну кількість процесів, зафіксовану з моменту старту. Поле listen queue найкраще показує реальну проблему. Якщо воно стабільно більше нуля під звичайним навантаженням, наявних воркерів фізично не вистачає, і запити чекають своєї черги замість того щоб просто повільно оброблятися.
Поле max active processes теж вартує уваги окремо. Коли воно впритул наближається до значення pm.max_children, пул хоча б раз уже впирався у стелю. Частина трафіку в той момент або чекала, або отримала відмову замість відповіді.
Типові помилки в налаштуванні пулу
Найчастіша помилка, це підняти pm.max_children настільки високо, що при одночасному піку всі процеси разом перевищують фізичну оперативну пам’ять сервера. Систему тоді рятує лише OOM killer, який без розбору вбиває випадкові процеси. Друга за поширеністю проблема, це залишити режим static на маленькому сервері зі спорадичним трафіком, де постійно зарезервована пам’ять простоює більшу частину доби.
Третя помилка стосується не самого FPM, а сусідніх шарів. Адміністратор годинами підбирає pm.max_children, тоді як реальне вузьке місце ховається в повільному SQL-запиті чи зовнішньому виклику. Жодне число в пулі процесів цю затримку не прибере, бо кожен воркер просто довше блокується на тому самому запиті.
Перезапуск і перевірка конфігурації
Перед перезапуском служби варто перевірити синтаксис командою php-fpm8.3 -t, номер версії залежить від встановленого пакета. Команда одразу вкаже рядок з помилкою замість того щоб зупинити продакшн невдалим рестартом. Сама служба перезапускається через systemctl restart php8.3-fpm на системах з systemd. Зміни в пулі набувають чинності лише після цього кроку, гаряче перечитування конфігурації для змінених значень pm недоступне.
Після рестарту варто одразу перевірити сторінку статусу і почекати кілька хвилин реального трафіку, перш ніж робити висновки про ефект зміни. Разові тестові запити не покажуть поведінку пулу під навантаженням, а саме заради неї налаштування й змінювали.