Оптимизация PHP-FPM: Используем ‘pm static’ для максимальной производительности Перевод. Принимая PHP всерьёз Доводы против PHP

Давайте кратко рассмотрим, как лучше настроить PHP-FPM для высокой пропускной способности, низкой задержки и более стабильного использования процессора и памяти. В большинстве дефолтных настроек PHP-FPM есть строка с PM (process manager), установленным в dynamic , а также рекомендации по использованию ondemand , в том случае если вы столкнулись с проблемами доступной памяти. Однако, давайте взглянем в документацию на php.net и сравним оба варианта управления, а также сравним с моей любимой настройкой под высокую посещаемость... pm = static:

pm = dynamic - количество дочерних процессов устанавливается динамически, основываясь на следующих директивах: pm.max_children , pm.start_servers , pm.min_spare_servers , pm.max_spare_servers .

pm = ondemand - процессы плодятся по требованию (при необходимости, в отличие от динамического варианта, где pm.start_servers запускаются при запуске сервиса).

pm = static - количество дочерних процессов фиксировано директивой pm.max_children .

…вы можете посмотреть полный список директив php-fpm.conf для получения дополнительной информации.

Менеджер процессов (process manager) PHP-FPM-а схож с CPUFreq Governor

Это может показаться немного не по теме, но я надеюсь связать его с нашей оптимизацией PHP-FPM. Да, все мы когда-то натыкались на проблемы с производительностью процессора, будь то ноутбук, виртуальная машина или выделенный сервер. Помните масштабирование частоты процессора? (CPUFreq governor) Эти параметры, доступные на *nix и Windows, могут повысить производительность и отзывчивость системы путем изменения настройки CPU governor с ondemand на performance . Сейчас давайте сравним описания и поищем сходства:

Governor = ondemand - Динамически увеличивает/уменьшает тактовую частоту процессора в зависимости от загруженности системы. Выводит до максимальной частоты, а потом уменьшает по мере увеличения времени простоя.

Governor = conservative - Похож на ondemand, но более экономный (предпочтение отдаётся меньшим тактовым частотам). Частота растёт более плавно.

Governor = performance - Поддерживает процессор(ы) на максимальной тактовой частоте.

… для дополнительной информации см. полный список настроек CPUFreq governor .

Заметили сходство? Я хотел сначала использовать это сравнение, чтобы более наглядно и лучше описать рекомендацию использовать pm static для PHP-FPM в качестве вашего первого выбора.

Настройка performance в CPU governor - это довольно безопасный прирост производительности, потому что это почти полностью зависит от предела процессора вашего сервера. Но есть несколько побочных эффектов (при постоянном удерживании частоты вашего процессора на 100%) - такие, как нагрев, время автономной работы (ноутбук) и другие. Однако это действительно самый быстрый параметр для вашего процессора.

Использование pm = static для максимальной производительности вашего сервера

Настройка pm = static в PHP-FPM сильно зависит от того, сколько свободной памяти на сервере. В основном, если вы страдаете от нехватки памяти сервера, то pm ondemand или dynamic могут оказаться лучшими вариантами. С другой стороны, если у вас достаточно свободной памяти, вы можете избежать большей части накладных расходов менеджера процессов, установив pm static до максимальной емкости сервера. Другими словами, когда вы делаете расчёты, pm.static нужно установить на максимальное количество PHP-FPM процессов, которые могут выполняться без создания проблем доступности памяти или кеша; однако, не так высоко, чтобы перегрузить процессор(ы) и иметь кучу отложенных операций PHP-FPM-а.

На скриншоте выше, у этого сервера установлен pm = static и pm.max_children = 100 , которые используют максимум около 10 ГБ из 32 ГБ установленных. Обратите внимание на выделенные столбцы, которые говорят сами за себя. В момент этого скриншота было около 200 активных пользователей (за последние 60 секунд). При таком уровне пользователей, около 70% из дочерних процессов PHP-FPM по-прежнему простаивает. Это означает, что PHP-FPM настроен так, что всегда использует максимум возможности ресурсов вашего сервера вне зависимости от текущего трафика. Процессы, находящиеся в простое, остаются "онлайн", ожидая всплеска трафика, чтобы мгновенно ответить, а не ждать PM пока он насоздаёт дочерних процессов, а потом ещё будет убивать их после того, как истечёт pm.process_idle_timeout . В моих настройках pm.max_requests установлен чрезвычайно высоко, т. к. это боевой сервер, в котором не должно быть утечки памяти в PHP. Вы можете использовать pm.max_requests = 0 при статическом режиме, если у вас есть 110% уверенности в ваших нынешних и будущих PHP-скриптах. Однако, рекомендуется перезапускать скрипты время от времени. Устанавливайте ваше количество запросов до перезапуска в наибольшее значение, чтобы избежать оверхеда PM-а. Например, как минимум pm.max_requests = 1000 ... основывайтесь на вашем количестве pm.max_children и количестве запросов в секунду.

На скриншоте используется линуксовый top . Тут отображаются не все процессы, а только та часть, что вместилась в ваше терминальное окно. В нашем случае отсортированных по %CPU (потреблению процессора). Чтобы увидеть все 100 PHP-FPM процессов, вы можете использовать что-то вроде этого:

Top -bn1 | grep php-fpm

Когда использовать pm ondemand и dynamic

Используя режим dynamic , вы можете наткнуться на подобные ошибки:

WARNING: seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 32 children, there are 4 idle, and 59 total children

Вы можете попытаться увеличить/изменить настройки и по-прежнему видеть ту же ошибку. Подобная ситуация описана в этом вопросе на Serverfault . В таком случае, pm.min была слишком низкой, а т. к. трафик сильно колеблется, режим dynamic достаточно сложно настроить правильно. Общий совет: используйте ondemand , как советуют в этом же вопросе . Однако что еще хуже, т. к. ondemand будет завершать процессы в простое вплоть до 0 когда мало трафика, то после вы получите настолько много накладных расходов, насколько скакнёт трафик. Если, конечно, вы не установите время ожидания чрезвычайно высоким. В этом случае вам просто нужно использовать pm = static + высокий pm.max_requests .

Несмотря на это, всё-таки режим dynamic и особенно ondemand может спасти вас когда у вас есть несколько PHP-FPM Pool-ов. Например, при хостинге нескольких cPanel-аккаунтов или нескольких сайтов под разными pool-ами. У меня есть сервер, к примеру, со 100+ аккаунтами cPanel и около 200+ доменами, и для режима static или даже dynamic было бы невозможно поддерживать хорошую производительность. И только режим ondemand справляется с этим хорошо, т. к. более двух третей из веб-сайтов имеют маленький трафик, а с ondemand это значит, что все "дети" будут завершены, экономя тонны серверной памяти! К счастью, разработчики cPanel это поняли и теперь по умолчанию стоит ondemand . Ранее с динамическим режимом по умолчанию, использовать PHP-FPM на общих серверах было не вариантом. Многие использовали suPHP , потому что dynamic -режим съедал память даже в простое PHP-FPM Pool-ов. Вероятно, если вы имеете хороший трафик, вы не будете размещены на сервере с большим количеством PHP-FPM Pool-ов (shared hosting).

Заключение

Когда дело доходит до PHP-FPM, раз вы начали получать серьезный трафик, то режимы ondemand и dynamic могут ограничить пропускную способность из-за свойственного оверхеда. Исследуйте вашу систему и установите количество процессов в наибольшее, с которым справится ваш сервер. Начните с pm.max_children , установленным на основе максимального использования режимов dynamic или ondemand , а затем увеличивайте до точки, где память и процессор остаются не перегруженными. Вы заметите, что с pm = static , т. к. вы держите всё в памяти, всплески трафика со временем будут меньше влиять на всплески загрузки процессора, а показатели загрузки и средней загрузки станут более сглаженными. Средний размер вашего PHP-FPM-процесса будет зависеть от конкретного веб-сервера, требующего ручной настройки, вот почему автоматизированные режимы - dynamic и ondemand - являются более популярными рекомендациями. Надеюсь, это была полезная статья.

Покопавшись у себя в черновиках, я нашел довольно интересную тему — настройка процесс менеджера для работы php-fpm и nginx. Я постараюсь рассказать как можно больше информации по этой теме. Расскажу на наглядных примерах что лучше и как считается некоторые важные параметры в php-fpm.

И так, что такое «process manager» в php-fpm? Process manager в php-fpm — это менеджер процессов для PHP-FPM, который создает и использует PHP процессы, чтобы получить максимальную отдачу от вашего сервера.

Разновидности Process manager в php-fpm:

  • dynamic — Имеет динамическое число pm.max_children (дочерние процессы). Которое изменяется и задается на основании: pm.max_children, pm.start_servers, pm.min_spare_servers, pm.max_spare_servers.
  • static — Имеет фиксированное число pm.max_children (дочерние процессы).
  • ondemand — Дочерние процессы не запускаются с самого начала. Количество процессов, создаются по требованию (по мере появления запросов, в отличии от PM dynamic, когда запускается заданное количество процессов, равное pm.start_servers, при старте службы.

Это 3 основных процесс менеджера для php-fpm-а.

Настройка process manager как dynamic для php-fpm в Unix/Linux

Стандартный менеджер процессов и он хорош тем, что имеет несколько PHP-FPM мастеров.

И так, редактируем свой пул и выставляем:

Pm = dynamic

И так, сейчас можно определить максимальное количество процессов. Для этого имеется формула:

Total Max Processes = (Total Ram - (Used Ram + Buffer)) / (Memory per php process)

Предположим, что на сервере имеется 6Гб RAM, но посмотреть можно следующей утилитой:

# cat /proc/meminfo | grep -E "MemTotal" MemTotal: 6291456 kB

Проверяем сколько использует памяти PHP-FPM с помощью следующей команды:

# ps -aylC "php-fpm" | grep -Ev "grep" | sort -n | awk "{sum+=$8; ++n} END {print "Tot="sum"("n")";print "Avg="sum"/"n"="sum/n/1024"MB"}" Tot=3126148(46) Avg=3126148/46=66.3669MB

Чтобы вывести все php-fpm процессы, используйте:

# ps --no-headers -o "rss,cmd" -C php-fpm | sort -n | awk "{print $1/1024 " Mb"}"

Самый «жирный» процесс:

# ps --no-headers -o "rss,cmd" -C php-fpm | sort -n | awk "{print $1/1024,"Mb"}" | tail -n1

Еще один способ:

В 1-й вкладке запускаем:

# while true; do curl https://сайт &> /dev/null; sleep 1; done

Во 2-й вкладке запускаем проверку:

# ps aux | grep -E "php-fpm"| grep -Ev "grep" | awk "{print $6/1024, "Mb"}"

Снова таки, я показал список всех процессов. Как правильно поступить и взять среднее значение или самое больше — решать вам. Нужно все тестировать! Количество памяти занятой процессами, можно проверить через утилиту ps_mem:

Тогда максимальное количество процессов можно посчитать (Я решил отдать под использование PHP всю RAM):

(1024*6) / 66 = 93.0909

Видим что можно использовать 93 max servers для php-fpm:

Pm = dynamic pm.max_children = 93 pm.start_servers = 30 pm.min_spare_servers = 30 pm.max_spare_servers = 45 pm.process_idle_timeout = 10s;

Главные параметры:

  • pm = dynamic — Как уже говорилось, это менеджер процессов. pm = dynamic
  • pm.max_children — Задается максимальное количество дочерних процессов (процессы-потомки), которые создаются PM. pm.max_children= (Общее количество виртуальной памяти)/(память выделенная 1 php процессу) = 93
  • pm.start_servers — Задается количество процессов-потомков, которые будут созданы PM при запуске PHP-FMP. pm.start_servers = ~(pm.max_children/3) = 31
  • pm.min_spare_servers — Задается количество процессов, которые должны оставаться в idle, как “запасные”, ожидая задач на выполнение, если количество меньше – будут созданы новые; pm.min_spare_servers = (устанавливаю в 1 ядро) ИЛИ ~(pm.max_children/3) = 31
  • pm.max_spare_servers — наоборот, максимальное количество процессов, которые должны оставаться в idle,если количество больше – некоторые потомки будут уничтожены: pm.max_spare_servers = (общему количеству ядер) ИЛИ ~(pm.max_children/2) = 45
  • pm.max_requests – количество запросов процесса, после которого он будет пересоздан, полезно для предотвращения утечек памяти (memory-leak); pm.max_requests = 100000
  • pm.process_idle_timeout — время в секундах после чего простаивающий процесс будет удалён. pm.process_idle_timeout = 10s

Обратите внимание, что я не принял во внимание (Used Ram + Buffer). Это было бы нужно, если на сервере имелись базы данных:

# cat /proc/meminfo | grep -E "(MemTotal|MemFree|Cached)" MemTotal: 6291456 kB MemFree: 1678704 kB Cached: 2205940 kB

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

# free -m total used free shared buff/cache available Mem: 6144 1521 1622 641 3000 3716 Swap: 0 0 0

Total Max Processes = (6144 - (1521 + 3000)) / 66 = 24

Перезапускаем php-fpm службу:

# service php-fpm restart

Теперь будем использовать htop/top и ab чтобы проверить что и как работает.

Выполняем стрестест:

Ab -n 10000 -c 500 http://localhost/index.php

# cd /usr/local/src && wget https://сайт/wp-content/uploads/scripts/php-fpm/php-fpmpal.sh -q -O - | bash

Дает вполне годные указания:

() (((*)\) (/()\))\))\) (` ((()/()\())(()/((()/((()/()\))())\ /(_))((_)\ /(_)) /(_)) /(_))((_)()\ `) (/(((_) (_)) _((_)(_)) (_))_|(_)) (_()((_) /(/()(_)) _ | _ \ | || || _ \ ___ | |_ | _ \ | \/ |((_)_\ ((_)_ | | | _/ | __ || _/|___|| __| | _/ | |\/| || "_ \)/ _` || | |_| |_||_||_| |_| |_| |_| |_|| .__/ \__,_||_| ========================================= |_| ============= AH00558: httpd: Could not reliably determine the server"s fully qualified domain name, using 31.187.70.238. Set the "ServerName" directive globally to suppress this message AH00558: httpd: Could not reliably determine the server"s fully qualified domain name, using 31.187.70.238. Set the "ServerName" directive globally to suppress this message ===== List of PHP-FPM pools ===== --- nagios --- Configuration file: /etc/php-fpm.d/nagios.conf List of processes: 53028 53029 53030 53031 53032 Number of processes: 5 Current max_children value: 50 Total memory usage for pool in KB: 54440 Average memory usage per process in KB: 10888 Total potential memory usage for pool (based on average process) (KB): 544400 Largest process in this pool is (KB): 10888 Total potential memory usage for pool (based on largest process) (KB): 544400 --- www --- Configuration file: /etc/php-fpm.d/www.conf List of processes: 53033 53034 53035 53036 53037 53038 53039 53040 53041 53042 53043 53044 53045 53046 53047 53048 53049 53050 53051 53052 53053 53054 53055 53056 53057 53058 53059 53060 53061 53062 53083 53119 53434 53491 61608 73379 Number of processes: 36 Current max_children value: 50 Total memory usage for pool in KB: 2295440 Average memory usage per process in KB: 63762 Total potential memory usage for pool (based on average process) (KB): 3188100 Largest process in this pool is (KB): 67792 Total potential memory usage for pool (based on largest process) (KB): 3389600 ===== Server memory usage statistics ===== Total server memory in KB: 6291456 =Total Apache memory usage in KB: 0 =Total nginx memory usage in KB: 55868 =Total Varnish memory usage in KB: 0 0 =Total MySQL memory usage in KB: 201324.8 =Total PHP-FPM memory usage in KB: 2349880 Memory available to assign to PHP-FPM pools in KB: 6099670 (total free memory + PHP-FPM"s current memory usage) Total potential PHP-FPM memory usage based on largest processes (KB): 3934000 (64.49%) ...GOOD 🙂 Total potential PHP-FPM memory usage based on average process size (KB): 3732500 (61.19%) ...GOOD 🙂 ===== Recommendations per pool ===== -- nagios -- currently uses 54440 KB memory (2.31% of all PHP-FPM memory usage). It should be allowed to use about 140902 KB of all available memory. Its average process size is 10888 KB so this means max_children should be set to ~12. It is currently set to 50 (this can be changed in /etc/php-fpm.d/nagios.conf). -- www -- currently uses 2295440 KB memory (97.68% of all PHP-FPM memory usage). It should be allowed to use about 5958157 KB of all available memory. Its average process size is 63762 KB so this means max_children should be set to ~93. It is currently set to 50 (this can be changed in /etc/php-fpm.d/www.conf). ===== Other considerations to take into account ===== From the PHP-FPM error logfiles (/var/log/php-fpm/error.log): - pool www had reached its max_children value of 50 on 2 occasion(s) For these pools you may want to compare the recommended max_children value to this information, and decide whether the recommended value would be high enough to prevent max_children from being hit in future. Note: It is not ideal to run PHP-FPMpal shortly after restarting PHP-FPM or your webservices. This is because PHP-FPMpal makes recommendations based on the average pool process size, and if PHP-FPM was restarted a short while ago then the likelihood is high that there won"t have been many requests made to the sites since the restart, and metrics will be skewed and not show a normalised average. It is also worth noting that if you"ve recently restarted any services that normally use up a large amount of memory then you probably want to wait a while before running PHP-FPMpal (e.g. if MySQL normally uses 50% of memory, but you"ve just restarted it then it may only use 10% of memory right now, thus the recommendations will be very skewed). ============================================================

Настройка process manager как static для php-fpm в Unix/Linux

Статический менеджер процессов почти не используется, из-за того, что имеется 1 PHP-FPM master.

Открываем свой пул из php-fpm-а и меняем параметры:

Pm = static pm.max_children = 5

Перезапускаем php-fpm службу:

$ sudo service php5-fpm restart

# service php-fpm restart

PS: Используйте команду в зависимости типа ОС.

Выполняем стрестест:

# ab -n 1000 -c 10 http://localhost/index.php # ab -n 5000 -c 20 http://localhost/index.php

Больше об утилите «ab» можно узнать в статье:

Открываем конфигурационный файл для php-fpm пула. Его можно найти поискам (если кто-то не знает где лежит он).

Раскомментируем строчку с «pm.max_requests» опцией:

Pm.max_requests = 500

Снова перезапускаем php-fpm и проверяем нагрузку:

# ab -n 5000 -c 20 http://localhost/index.php

Можно видеть, что некоторые процессы — умерли, а затем некоторые процессы были убиты, а затем они сново породились!

Настройка process manager как ondemand для php-fpm в Unix/Linux

Самый продвинутый (на мой взгляд) менеджер процессов. С названия очевидно, что он не оставляет процессы, но создает их, по мере необходимы.

Пример пула выглядит следующим образом:

User = www-data group = www-data listen = /var/run/php5-fpm/linux-notes.og/sock listen.owner = www-data listen.group = www-data listen.mode = 770 chdir = /srv/www/wp-content/linux-notes.og/ pm = ondemand pm.max_children = 5 pm.max_requests = 500

Главные параметры:

  • pm = ondemand — Как уже говорилось, это менеджер процессов. pm = ondemand
  • pm.max_children — Задаем максимальное число дочерних-процессов. pm.max_children= (Общее количество виртуальной памяти)/(память выделенная 1 php процессу) = 93
  • pm.process_idle_timeout — Задаем время в секундах после чего простаивающий процесс будет удалён. pm.process_idle_timeout = 10s
  • pm.max_requests — Задаем количество обработаных запросов, после которых процессы php-fpm будут перезагружены. pm.max_requests = 100000

Если вам нужна большая производительность, то менеджер процессов — «OnDemand» не подойдет вам. Тем не менее, на 90% случаев, конфигурация PHP-FPM в режиме OnDemand лучше, чем static или dynamic.

FPM uses php.ini syntax for its configuration file - php-fpm.conf , and pool configuration files.

List of global php-fpm.conf directives

pid string

Path to PID file. Default value: none.

Error_log string

Path to error log file. Default value: #INSTALL_PREFIX#/log/php-fpm.log . If it"s set to "syslog", log is sent to syslogd instead of being written in a local file.

Log_level string

Error log level. Possible values: alert, error, warning, notice, debug. Default value: notice.

Log_limit integer

Log limit for the logged lines which allows to log messages longer than 1024 characters without wrapping. Default value: 1024. Available as of PHP 7.3.0.

Log_buffering boolean

Experimental logging without extra buffering. Default value: yes. Available as of PHP 7.3.0.

Syslog.facility string

used to specify what type of program is logging the message. Default value: daemon.

Syslog.ident string

Prepended to every message. If you have multiple FPM instances running on the same server, you can change the default value which must suit common needs. Default value: php-fpm.

Emergency_restart_threshold int

If this number of child processes exit with SIGSEGV or SIGBUS within the time interval set by emergency_restart_interval , then FPM will restart. A value of 0 means "Off". Default value: 0 (Off).

Emergency_restart_interval mixed

Interval of time used by emergency_restart_interval to determine when a graceful restart will be initiated. This can be useful to work around accidental corruptions in an accelerator"s shared memory. Available Units: s(econds), m(inutes), h(ours), or d(ays). Default Unit: seconds. Default value: 0 (Off).

Process_control_timeout mixed

Time limit for child processes to wait for a reaction on signals from master. Available units: s(econds), m(inutes), h(ours), or d(ays) Default Unit: seconds. Default value: 0.

Process.max int

The maximum number of processes FPM will fork. This has been design to control the global number of processes when using dynamic PM within a lot of pools. Use it with caution. Default value: 0.

Process.priority int

Specify the nice(2) priority to apply to the master process (only if set). The value can vary from -19 (highest priority) to 20 (lower priority). Default value: not set.

Daemonize boolean

Send FPM to background. Set to "no" to keep FPM in foreground for debugging. Default value: yes.

Rlimit_files int

Set open file descriptor rlimit for the master process. Default value: Set open file descriptor rlimit for the master process.

Rlimit_core int

Set max core size rlimit for the master process. Default value: 0.

Events.mechanism string

Specify the event mechanism FPM will use. The following is available: select, pool, epoll, kqueue (*BSD), port (Solaris). Default value: not set (auto detection).

Systemd_interval int

When FPM is build with systemd integration, specify the interval, in second, between health report notification to systemd. Set to 0 to disable. Default value: 10.

List of pool directives

With FPM you can run several pools of processes with different setting. These are settings that can be tweaked per pool.

Listen string

The address on which to accept FastCGI requests. Valid syntaxes are: "ip.add.re.ss:port", "port", "/path/to/unix/socket". This option is mandatory for each pool.

Listen.backlog int

Set listen(2) backlog. A value of "-1" means unlimited. Default value: -1.

Listen.allowed_clients string

List of IPv4 addresses of FastCGI clients which are allowed to connect. Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original PHP FastCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address must be separated by a comma. If this value is left blank, connections will be accepted from any ip address. Default value: any. IPv6 addresses are allowed since PHP 5.5.20 and 5.6.4.

Listen.owner string

Set permissions for unix socket, if one is used. In Linux, read/write permissions must be set in order to allow connections from a web server. Many BSD-derived systems allow connections regardless of permissions. Default values: user and group are set as the running user, mode is set to 0660.

Listen.group string

See listen.owner .

Listen.mode string

See listen.owner .

Listen.acl_users string

When POSIX Access Control Lists are supported you can set them using this option. When set, listen.owner and listen.group are ignored. Value is a comma separated list of user names. Since PHP 5.6.5.

Listen.acl_groups string

See listen.acl_users . Value is a comma separated list of group names. Since PHP 5.6.5.

User string

Unix user of FPM processes. This option is mandatory.

Group string

Unix group of FPM processes. If not set, the default user"s group is used.

Pm string

Choose how the process manager will control the number of child processes. Possible values: static , ondemand , dynamic . This option is mandatory.

static - the number of child processes is fixed (pm.max_children ).

ondemand - the processes spawn on demand (when requested, as opposed to dynamic, where pm.start_servers are started when the service is started.

dynamic - the number of child processes is set dynamically based on the following directives: pm.max_children , pm.start_servers , pm.min_spare_servers , pm.max_spare_servers .

Pm.max_children int

The number of child processes to be created when pm is set to static and the maximum number of child processes to be created when pm is set to dynamic . This option is mandatory.

This option sets the limit on the number of simultaneous requests that will be served. Equivalent to the ApacheMaxClients directive with mpm_prefork and to the PHP_FCGI_CHILDREN environment variable in the original PHP FastCGI.

Pm.start_servers int

The number of child processes created on startup. Used only when pm is set to dynamic . Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2.

Pm.min_spare_servers int

The desired minimum number of idle server processes. Used only when pm is set to dynamic

Pm.max_spare_servers int

The desired maximum number of idle server processes. Used only when pm is set to dynamic . Also mandatory in this case.

Pm.process_idle_timeout mixed

The number of seconds after which an idle process will be killed. Used only when pm is set to ondemand . Available units: s(econds)(default), m(inutes), h(ours), or d(ays). Default value: 10s.

Pm.max_requests int

The number of requests each child process should execute before respawning. This can be useful to work around memory leaks in 3rd party libraries. For endless request processing specify "0". Equivalent to PHP_FCGI_MAX_REQUESTS . Default value: 0.

Pm.status_path string

The URI to view the FPM status page. If this value is not set, no URI will be recognized as a status page. Default value: none.

Ping.path string

The ping URI to call the monitoring page of FPM. If this value is not set, no URI will be recognized as a ping page. This could be used to test from outside that FPM is alive and responding. Please note that the value must start with a leading slash (/).

Ping.response string

This directive may be used to customize the response to a ping request. The response is formatted as text/plain with a 200 response code. Default value: pong.

Process.priority int

Specify the nice(2) priority to apply to the worker process (only if set). The value can vary from -19 (highest priority) to 20 (lower priority). Default value: not set.

Process.dumpable boolean

Set the process dumpable flag (PR_SET_DUMPABLE prctl) even if the process user or group is different than the master process user. It allows to create process core dump and ptrace the process for the pool user. Default Value: no. Since PHP 7.0.29, 7.1.17 and 7.2.5.

Prefix string

Specify prefix for path evaluation

Request_terminate_timeout mixed

The timeout for serving a single request after which the worker process will be killed. This option should be used when the "max_execution_time" ini option does not stop script execution for some reason. A value of "0" means "Off". Available units: s(econds)(default), m(inutes), h(ours), or d(ays). Default value: 0.

Request_slowlog_timeout mixed

The timeout for serving a single request after which a PHP backtrace will be dumped to the "slowlog" file. A value of "0" means "Off". Available units: s(econds)(default), m(inutes), h(ours), or d(ays). Default value: 0.

Slowlog string

The log file for slow requests. Default value: #INSTALL_PREFIX#/log/php-fpm.log.slow .

Rlimit_files int

Set open file descriptor rlimit for child processes in this pool. Default value: system defined value.

Rlimit_core int

Set max core size rlimit for child processes in this pool. Possible Values: "unlimited" or an integer greater or equal to 0. Default value: system defined value.

Chroot string

Chroot to this directory at the start. This value must be defined as an absolute path. When this value is not set, chroot is not used.

Chdir string

Chdir to this directory at the start. This value must be an absolute path. Default value: current directory or / when chroot.

Catch_workers_output boolean

Redirect worker stdout and stderr into main error log. If not set, stdout and stderr will be redirected to /dev/null according to FastCGI specs. Default value: no.

Decorate_workers_output boolean

Enable the output decoration for workers output when catch_workers_output is enabled. Default value: yes. Available as of PHP 7.3.0.

Clear_env boolean

Clear environment in FPM workers. Prevents arbitrary environment variables from reaching FPM worker processes by clearing the environment in workers before env vars specified in this pool configuration are added. Since PHP 5.4.27, 5.5.11, and 5.6.0. Default value: Yes.

Security.limit_extensions string

Limits the extensions of the main script FPM will allow to parse. This can prevent configuration mistakes on the web server side. You should only limit FPM to .php extensions to prevent malicious users to use other extensions to execute php code. Default value: .php .phar

Access.log string

The access log file. Default value: not set

Access.format string

The access log format. Default value: "%R - %u %t \"%m %r\" %s"

It"s possible to pass additional environment variables and update PHP settings of a certain pool. To do this, you need to add the following options to the pool configuration file.

Example #1 Passing environment variables and PHP settings to a pool

env = $HOSTNAME env = /usr/local/bin:/usr/bin:/bin env = /tmp env = /tmp env = /tmp php_admin_value = /usr/sbin/sendmail -t -i -f [email protected] php_flag = off php_admin_value = /var/log/fpm-php.www.log php_admin_flag = on php_admin_value = 32M

PHP settings passed with php_value or php_flag will overwrite their previous value. Please note that defining disable_functions or disable_classes will not overwrite previously defined php.ini values, but will append the new value instead.

Settings defined with php_admin_value and php_admin_flag cannot be overriden with ini_set() .

As of 5.3.3, PHP settings are also possible to be set in webserver.

Example #2 set PHP settings in nginx.conf

set $php_value "pcre.backtrack_limit=424242"; set $php_value "$php_value \n pcre.recursion_limit=99999"; fastcgi_param PHP_VALUE $php_value; fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/htdocs";

Caution

Because these settings are passed to php-fpm as fastcgi headers, php-fpm should not be bound to a worldwide accessible address. Otherwise, anyone could alter the PHP configuration options. See also

Ракета Союз, доставленная на поезде на пусковую площадку. Фото из общественного достояния NASA.

Добродетели PHP

PHP имеет несколько очень глубоких и, однозначно, верных особенностей.

Первая, состояние . Каждый веб-запрос начинается с совершенно чистого листа. Его пространство имён и глобальные переменные не инициализированы, за исключением некоторых стандартных глобальных переменных, функций и классов, которые предоставляют примитивную функциональность и жизнеобеспечение. Начиная каждый запрос с известного состояния, мы получаем своего рода изоляцию от возможных ошибок; если запрос t сталкивается с неполадкой ПО и завершается с ошибкой, данный баг не оказывает никакого влияния на выполнение последующего запроса t+1. В действительности, конечно же, помимо программной кучи, состояние приложения находится и в других местах, и вполне возможно полностью испортить базу данных, memcache или файловую систему. Но PHP разделяет эту слабость со всеми мыслимыми средами, которые позволяют сохранять состояние. В то же время, разделение программных куч между запросами снижает цену большинства программных ошибок.

Вторая, параллелизм . Индивидуальный запрос работает в одном PHP потоке. На первый взгляд, это кажется глупым ограничением. Но так как наше приложение выполняется в контексте веб-сервера, мы имеет натуральный источник параллелизма: веб запросы. Просто отправляя асинхронные запросы самим себе, мы легко получаем параллелизм с изолированными состояниями и вызовами по копированию-восстановлению. На практике, это безопаснее и устойчивее к ошибкам, чем подход с блокировками и разделяемым состоянием, который используется в других языках общего назначения.

В заключении, тот факт, что PHP программы оперируют на уровне запросов, означает, что рабочий процесс программиста является быстрым и эффективным, и остаётся быстрым после изменения приложения. Множество языков продуктивной разработки претендуют на это, но если они не очищают своё состояние при каждом запросе, и основной поток событий разделяет программный уровень состояния между запросами, они почти всегда требуют некоторое время на запуск. Для типичного сервера приложений на Python"е, типичный цикл отладки будет выглядеть примерно как «подумать, отредактировать, перезапустить сервер, отправить несколько тестовых запросов». Даже если «перезапустить сервер» занимает всего несколько секунд из общего количества часов, это забирает большой срез из 15-30 секунд наших человеческих мозгов на необходимость удержания в голове самой ненужной части текущего состояния.

Я утверждаю, что разработка на PHP в стиле «подумать, отредактировать и перезагрузить страницу» делает разработчиков более продуктивными. В долгих и сложных циклах разработки проектов это даёт ещё больший прирост.

Доводы против PHP

Если всё это является правдой, почему же его так ненавидят ? Когда вы уберёте все красочные гиперболы в сторону, что основные жалобы о PHP кластере сведутся к следующим проблемам:


Чтобы не показаться нерефлективным апологетом PHP: всё это серьёзные проблемы, которые позволяют более вероятно создавать дефекты. Они являются явными ошибками (unforced errors). Здесь нет присущего компромисса между Хорошими Частями PHP и данными проблемами. Должна быть реализована возможность создать PHP, который разрешит данные недостатки, сохранив при этом все хорошие стороны.

HHVM и Hack

Этот преемник системы PHP зовётся Hack

Hack - это такой язык программирования, который люди называют «постепенная система типов» для PHP. «Система типов» значит, что он позволяет программисту составлять автоматически проверяемые инварианты о данных, которые протекают через код: данная функция берёт строку или число и возвращает лист Fribbles, как например в Java или C++ или Haskell, или в любом другом статически типизированном языке, который вы выберете. «Постепенная» означает, что некоторые части вашей кодовой базы могут быть статически типизированными, в то время как другие её части могут всё ещё находится в беспорядочном, динамическом PHP. Возможность совмещать эти подходы позволяет постепенно мигрировать большие кодовые базы.

Вместо того чтобы разлить здесь тонны чернил в описании системы типов Hack и того, как она работает, просто поиграйтесь с ним . Я буду здесь, когда вы вернётесь.

Это аккуратная система, и она весьма амбициозна в том что она позволяет вам выразить. И наличие возможности постепенной миграции проекта на Hack, в случаях когда он разрастается сильнее, чем вы ожидали изначально, является уникальным преимуществом экосистемы PHP. Проверки типов Hack сохраняют рабочий процесс в стиле «думать, отредактировать, перезагрузить страницу», потому что они запускаются в фоне, постепенно обновляя модель кодовой базы, когда он видит модификации в файловой системе. Проект Hack предоставляет интеграции со всеми популярными редакторами и IDE, так что вы сможете увидеть обратную связь об ошибках типов уже тогда, когда завершите печатать код, также как в веб-демонстрации.

Давайте рассмотрим совокупность реальных рисков, которые создаёт PHP, в свете Hack:


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

HHVM

Hack изначально был разработан как часть виртуальной машины HipHop , или HHVM, виртуальной среды с открытым исходным кодом для PHP. HHVM предоставляет другую важную опцию для успешного проекта: возможность запустить ваш сайт быстрее и более экономно. Facebook

In the previous articles we have evaluated PHP performance on different runtimes, adding server resources (CPU & RAM), and comparing the Symfony Proxy and Varnish - using eZ Platform - a CMS built on the Symfony Framework.

Now let"s try an unconventional method of executing PHP applications, PHP-PM.

PHP became popular partly due to the fact that it"s very easy and safe to run. Upon each request the PHP starts from scratch, bootstraps the application, sends the response and dies.

PHP developers don"t need to worry about the complexities of a long living application with memory leaks, but the continuous repetition of the same basic tasks does have an effect on performance. Opcaching helps improve performance, but there is still a lot of work done for "nothing".

Close to two years ago, in February 2014, an alternative way of running PHP was introduced: Bring High Performance Into Your PHP App (with ReactPHP) . After first causing some waves in the PHP community, it faded into the background.

But it did lead directly to PHP-PM and probably influenced the creation of PHPFastCGI . Both run PHP as a continuous process that responds to requests, making it possible to remove routine bootstrapping out of the equation.

This brings increased performance, but makes the application more vulnerable to developer mistakes and memory leaks due to the longer lifecycle. This is why the method is still considered experimental, but thought by some as being the future of executing PHP.

PHP-PM supports Symfony Framework applications by default and eZ Platform as one worked as expected, right out of the box. So it"s worth a round of tests to see how it holds up in a real world application.

PHP-PM vs. PHP-FPM Performance

Inline with the previous tests I ran requests against the eZ Platform Demo installation . The traditional PHP-FPM setup was running on PHP 7.0.1. PHP-PM was behind Nginx as a load balancer and both HHVM 3.11 and PHP 7.0.1 were tested.

Process counts for PHP-PM were set at double to the CPU core count, PHP-FPM had a max_child limit of 10 - Try free trial for high performance VPS on UpCloud . Request concurrency was set at 10. Tests were ran against the Front Page and the REST API calls, without Symfony Proxy or other high level caching. Tests were repeated three times and average results are reported.

Front Page without Symfony Proxy

For the complete page rendering PHP-PM displays a significant advantage to the traditional PHP-FPM setup. PHP-FPM remains in the same ballpark with PHP-PM. HHVM is consistently more performant than the PHP 7 counterpart.


API without Symfony Proxy

For the API calls PHP-PM runs yield significantly higher results than the PHP-FPM setup, indicating that a larger percentage of time in these is spent bootsrapping while processing is minimal. HHVM starts out on top and after a tie at 2 CPUs, PHP 7 provides higher returns with the 4 and 8 CPU setups.

Comparing PHP-FPM, PHP-PM, Symfony Proxy and Varnish

To put the numbers into a larger perspective with cached results they are compared with results for the Symfony Proxied results (ran with PHP-FPM) and Varnish.


Full Frontpage

For full page loads both the Symfony PHP Reverse Proxy and Varnish trounce any dynamically generated page loads. With a good caching strategy and merging fragments with ESI on proxy level and a PHP-FPM backend will likely yield best real-world results.


REST API Call

For REST API calls with limited processing PHP-PM with PHP 7 inches closer to Symfony Proxy performance. While the PHP proxy is still twice as fast, it"s worth considering that for hard-to-cache-and-purge requests a fast-enough dynamic backend might be the practical option.

Conclusions

Running long living PHP processes remains a curiosity, but results offer a promise of significantly improved performance when time spent bootstrapping the application is relatively long compared to the total length of the processing.

An example is a continuosly running daemon in the currently fashionable world of tiny API calls. Behind the scenes they might become a cascade over a long chain, e.g. a service calls on a REST API that calls a REST API that calls a REST API that calls a REST API that calls a REST API...

Unfortunately PHP-PM is not yet a practical solution for running production services as both PHP 7 and HHVM end up crashing from time to time. Coming from a pure gut feeling HHVM seems to be a bit more stable out of the pair with PHP-PM.