PHP-FPM: Process Manager – dynamic vs ondemand vs static

By | 06/05/2018
 

Беглый обзор и примеры для настройки 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 FastCGI
  • pm.start_servers: кол-во дочерних процессов, которые будут созданы при старте пула, используется только при dynamic конфигурации
  • pm.min_spare_servers: минимальное кол-во дочерних процессов в статусе ожидания (idle), только для dynamic конфигурации
  • pm.max_spare_servers: максимальное кол-во дочерних процессов в статусе ожидания (idle), только для dynamic конфигурации
  • pm.max_requests: максимальное кол-во запросов, которое должен обработать процесс FPM перед его перезапуском (помогает избежать утечек памяти)

После применения такой конфигурации  процессы будут выглядеть следующим образом:

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

Каждый такой процесс, включая дочерние, будет кушать память, независимо от того – требуется он сейчас или нет.

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 уничтожит дочерний процесс, который не обслуживает запросы.

Перезапускаем сервис, и проверяем процессы:

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

Однако, если сделать вызов к сайту, который использует этот пул – FPM запустит дочерний процесс для его обслуживания, и продержит его 10 секунд, если не будет других запросов:

curl localhost
<html>
<head>
<title>PHP Test</title>
</head>
<body>
<p>PoolName PHP page</p>
</body>
</html>

Процессы PHP-FPM:

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

И через 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 – проверяем объём занятой памяти:

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

5.5 ГБ занято всего, из них 1.6 – это процессы php-fpm, 129 штук.

Значит, если не учитывать процессы FPM – сервер кушает 3.9 ГБ памяти на остальные сервисы из 15 доступных:

free  -m
total        used        free      shared  buff/cache   available
Mem:          15475        5968        2542         184        6963        8990
Swap:          1023           9        1014

Само собой – тут необходимо учитывать корреляцию между временем суток и кол-вом пользователей, так что мониторинг нам в помощь.

Тем не менее идея такова.

Из 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 мб:

echo 1600/130 | bc
12

Если выделять 8 ГБ чисто под FPM – значит получается:

echo 8000/12 | bc
666

(какая замечательная циферка)

Очевидно, что такое кол-во процессов будет чрезмерным.

Кроме того – 12 мб является средним значением, тогда как самые “толстые” процессы fpm кушают почти 100 мб:

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

Исходя из предположения, что все процессы будут потреблять максимальное кол-во памяти – мы получаем уже всего лишь 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

Настраиваем php-fpm