Беглый обзор и примеры для настройки PHP-FPM Process Manager —
dynamic
, ondemand
и static
.
Я не выполнял нагрузочного тестирования при использовании различных конфигураций, так что все выводы о применимости того или иного подхода чисто умозрительные.
Тем не менее — тестирование проводиться, думаю, будет, по возможности — добавлю результаты отдельным постом.
Содержание
pm = dynamic
Большинство HowTo по настройке PHP-FPM (и этот блог — не исключение) рекомендуют использование Process Manager для PHP-FPM в dynamic
конфигурации, при которой кол-во процессов PHP-FPM меняется динамически, в зависимости от количества запросов, при этом имеется минимальное кол-во процессов, которые будут запущены при старте FPM-сервиса.
Пример такой конфигурации как правило выглядит так:
[pool_name] ... pm = dynamic pm.max_children = 5 pm.start_servers = 3 pm.min_spare_servers = 2 pm.max_spare_servers = 4 pm.max_requests = 200
Тут:
pm.max_children
: кол-во дочерних процессов, которое будет создано для пула при использованииstatic
, или максимальное кол-во дочерних процессов, которое будет создано для пула при использованииdynamic
, задаёт максималное кол-во запросов, которое может обслужить пул, аналогApacheMaxClients
для Apache2 илиPHP_FCGI_CHILDREN
для PHP FastCGIpm.start_servers
: кол-во дочерних процессов, которые будут созданы при старте пула, используется только приdynamic
конфигурацииpm.min_spare_servers
: минимальное кол-во дочерних процессов в статусе ожидания (idle), только дляdynamic
конфигурацииpm.max_spare_servers
: максимальное кол-во дочерних процессов в статусе ожидания (idle), только дляdynamic
конфигурацииpm.max_requests
: максимальное кол-во запросов, которое должен обработать процесс FPM перед его перезапуском (помогает избежать утечек памяти)
После применения такой конфигурации процессы будут выглядеть следующим образом:
[simterm]
# ps aux | grep fpm root 18660 0.4 1.7 487532 36124 ? Ss 13:59 0:00 php-fpm: master process (/etc/php/7.2/fpm/php-fpm.conf) www-data 18661 0.0 0.5 487532 10292 ? S 13:59 0:00 php-fpm: pool pool-name www-data 18662 0.0 0.5 487532 10292 ? S 13:59 0:00 php-fpm: pool pool-name www-data 18663 0.0 0.5 487532 10292 ? S 13:59 0:00 php-fpm: pool pool-name
[/simterm]
Каждый такой процесс, включая дочерние, будет кушать память, независимо от того — требуется он сейчас или нет.
pm = ondemand
Кроме dynamic
конфигурации так же можно использовать ondemand
, при котором FPM не будет создавать дочерних процессов вообще, пока не появится реальный запрос для обработки, а запустит только мастер-процесс самого php-fpm
.
Выглядеть такой конфиг может так:
... pm = ondemand pm.max_children = 5 pm.process_idle_timeout = 10s pm.max_requests = 200 ...
Тут в pm.process_idle_timeout
задаётся время, через которое Process Manager уничтожит дочерний процесс, который не обслуживает запросы.
Перезапускаем сервис, и проверяем процессы:
[simterm]
# ps aux | grep fpm root 18771 0.4 1.7 487532 36264 ? Ss 14:15 0:00 php-fpm: master process (/etc/php/7.2/fpm/php-fpm.conf) root 18773 0.0 0.0 12784 980 pts/0 S+ 14:16 0:00 grep fpm
[/simterm]
Однако, если сделать вызов к сайту, который использует этот пул — FPM запустит дочерний процесс для его обслуживания, и продержит его 10 секунд, если не будет других запросов:
[simterm]
# curl localhost <html> <head> <title>PHP Test</title> </head> <body> <p>PoolName PHP page</p> </body> </html>
[/simterm]
Процессы PHP-FPM:
[simterm]
# ps aux | grep php root 18771 0.0 1.7 487532 36264 ? Ss 14:15 0:00 php-fpm: master process (/etc/php/7.2/fpm/php-fpm.conf) www-data 18814 0.0 0.7 487860 14408 ? S 14:20 0:00 php-fpm: pool pool-name root 18816 0.0 0.0 12784 924 pts/0 S+ 14:20 0:00 grep php
[/simterm]
И через 10 секунд — опять ни одного дочернего процесса не будет.
Такой вариант отлично подойдёт для сервера с небольшим объёмом RAM и который обслуживает нечастые запросы клиентов, что бы не тратить попусту память.
pm = static
И третий вариант конфигурации Process Manager — static
, когда указывается количество дочерних процессов, которые будут созданы при запуске пула независимо от количества запросов:
[pool-name] listen = /var/run/php/php-fpm-pool-name.sock user = www-data group = www-data listen.owner = www-data listen.group = www-data pm = static pm.max_children = 5 pm.max_requests = 200 ...
Эти процессы будут находится в режиме ожидания новых запросов.
Преимущество такого подхода в том, что при активном использовании пула (часто посещаемый вебсайт, например) — системе не придётся постоянно создавать/убивать дочерние процессы для PHP, тем самым будет экономиться и время процессора, и потребуется меньшее врмея для обслуживания нового запроса, т.к. FPM уже будет иметь готовый дочерний процесс, а не начнёт его создавать.
Единственной сложностью тут может быть вопрос — как определить кол-во процессов для создания.
Рассмотрим на нашем production сервере, где сейчас pm = dynamic
.
Используя ps_mem
— проверяем объём занятой памяти:
[simterm]
# python ps_mem.py Private + Shared = RAM used Program 52.0 KiB + 14.5 KiB = 66.5 KiB atopacctd 48.0 KiB + 40.0 KiB = 88.0 KiB rabbitmq-server (2) 100.0 KiB + 13.5 KiB = 113.5 KiB erl_child_setup ... 311.7 MiB + 47.5 KiB = 311.8 MiB memcached 1.4 GiB + 35.9 MiB = 1.4 GiB php7.2 (92) 1.5 GiB + 65.9 MiB = 1.6 GiB php-fpm7.2 (129) 1.7 GiB + 26.0 KiB = 1.7 GiB redis-server --------------------------------- 5.5 GiB
[/simterm]
5.5 ГБ занято всего, из них 1.6 — это процессы php-fpm
, 129 штук.
Значит, если не учитывать процессы FPM — сервер кушает 3.9 ГБ памяти на остальные сервисы из 15 доступных:
[simterm]
# free -m total used free shared buff/cache available Mem: 15475 5968 2542 184 6963 8990 Swap: 1023 9 1014
[/simterm]
Само собой — тут необходимо учитывать корреляцию между временем суток и кол-вом пользователей, так что мониторинг нам в помощь.
Тем не менее идея такова.
Из 15 ГБ занято под другие процеcсы и систему 4 ГБ, значит свободно 11. Из них можно выделить 70-80% под процессы FPM.
Используя значение из ps_mem
:
1.5 GiB + 65.9 MiB = 1.6 GiB php-fpm7.2 (129)
Считаем, что в среднем один дочерний процесс php-fpm
потребляет 12 мб:
[simterm]
# echo 1600/130 | bc 12
[/simterm]
Если выделять 8 ГБ чисто под FPM — значит получается:
[simterm]
# echo 8000/12 | bc 666
[/simterm]
(какая замечательная циферка)
Очевидно, что такое кол-во процессов будет чрезмерным.
Кроме того — 12 мб является средним значением, тогда как самые «толстые» процессы fpm
кушают почти 100 мб:
[simterm]
# ps -u www-data -o rss,cmd | sort -k 1 | tail -n 5 88376 php-fpm: pool www 92032 php-fpm: pool www 96176 php-fpm: pool www 98276 php-fpm: pool www RSS CMD
[/simterm]
Исходя из предположения, что все процессы будут потреблять максимальное кол-во памяти — мы получаем уже всего лишь 80 процессов.
Как обычно — «Истина обычно лежит посередине».
Значение в pm.max_children = 300
для начала было бы вполне подходящим.
Далее — тестируем нагрузки с apache bench или его аналогами, и при необходимости меняем значение.
Ссылки по теме
List of global php-fpm.conf directives
PHP-FPM tuning: Using ‘pm static’ for max performance
Nginx and PHP-FPM Configuration and Optimizing Tips and Tricks
PHP-FPM ‘ondemand’ Process Manager VS ‘dynamic’
Optimizing php-fpm the Promet Way