Analyse and calculate php-fpm runner settings

In one of our projects we had a php based CMS that was not performing well. It even resulted in a deadlock between the CMS and another service. As soon as the number of requests to the CMS increased crossed a certain threshold the following error was logged by the CMS:

php-fpm | [17-Nov-2020 16:32:15] WARNING: [pool www] server reached pm.max_children setting (10), consider raising it
php-fpm | [17-Nov-2020 16:33:39] WARNING: [pool www] child 9340 exited on signal 9 (SIGKILL) after 143.926754 seconds from start
php-fpm | [17-Nov-2020 16:33:39] NOTICE: [pool www] child 9349 started
php-fpm | [17-Nov-2020 16:34:07] WARNING: [pool www] server reached pm.max_children setting (10), consider raising it
php-fpm | [17-Nov-2020 16:34:10] WARNING: [pool www] server reached pm.max_children setting (10), consider raising it
php-fpm | [17-Nov-2020 16:34:49] WARNING: [pool www] server reached pm.max_children setting (10), consider raising it
php-fpm | [17-Nov-2020 16:35:47] WARNING: [pool www] server reached pm.max_children setting (10), consider raising it

Analysis

To tweak our settings we first need to get an idea of how much memory is actually being used by fpm:

All fpm processes

$ ps -ylC php-fpm --sort:rss
S   UID     PID    PPID  C PRI  NI   RSS    SZ WCHAN  TTY          TIME CMD
S  2000      85      82  0  80   0 33644 121947 ep_pol ?       00:00:00 php-fpm
S  2000     277      85  0  80   0 83612 146879 inet_c ?       00:00:04 php-fpm
S  2000     276      85  0  80   0 104044 151999 inet_c ?      00:00:04 php-fpm
S  2000     278      85  0  80   0 114160 154561 inet_c ?      00:00:04 php-fpm

Average memory

$ ps --no-headers -o "rss,cmd" -C php-fpm | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/NR/1024,"M") }'
81M

All fpm processes memory

$ ps -eo size,pid,user,command --sort -size | awk '{ hr=$1/1024 ; printf("%13.2f Mb ",hr) } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' | grep php-fpm
        69.11 Mb php-fpm: pool www
        59.10 Mb php-fpm: pool www
        39.10 Mb php-fpm: pool www
         7.61 Mb php-fpm: master process (/home/vcap/app/php/etc/php-fpm.conf)
         0.34 Mb grep --color=auto php-fpm
         0.32 Mb /bin/sh -c $HOME/php/sbin/php-fpm -p "$HOME/php/etc" -y "$HOME/php/etc/php-fpm.conf" -c "$HOME/php/etc"

Runner configuration

We’re currently running 6 instances with 1GB of memory each. We will now apply the following rules:

  • You will want to set the max_spare_servers to x2 or x4 the number of cores. So if you have an 8 core CPU then you can start off with a value of 16 for max_spare_servers and go up to 32.
  • The start_servers value should be around half of the max_spare_servers value.
  • max_requests are not about how many concurrent requests can be processed. It means that after a php child process has handled 500 requests (one at the time), it is killed and then respawned as a fresh new unused process. It helps to fight memory leaks.
  • max_children is calculated as follows:
    pm.max_children = (available_memory / avg_fpm_proc_memory) - reserve
    pm.max_children = (1024MB / 80MB) - reserve
    pm.max_children = 12,8 - 2,8  = 10
    

This gives us the following runner configuration:

pm = dynamic
pm.max_children = 10
pm.start_servers = 8
pm.min_spare_servers = 6
pm.max_spare_servers = 16

Simple debug script

While running a load test I ran the following script to track how the fpm memory usage changed based on load:

FPM_MASTER_PID=85

while true;
do
  printf -- '-%.0s' {1..100}; echo ""
  date --iso-8601=seconds
  echo ""
  pstree $FPM_MASTER_PID -c -l -p  -U
  echo ""
  ps -ylC php-fpm --sort:rss
  echo ""
  ps --no-headers -o "rss,cmd" -C php-fpm | awk '{ sum+=$1 } END { printf ("%d%s\n", sum/NR/1024,"M") }'
  echo ""
  ps -eo size,pid,user,command --sort -size | awk '{ hr=$1/1024 ; printf("%13.2f Mb ",hr) } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }' | grep "php-fpm:"
  echo ""
  sleep 2
done

Sources: