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 formax_spare_servers
and go up to 32. - The
start_servers
value should be around half of themax_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: