PHP5 to PHP7 unexpected increase in memory usage inside container

Last week we updated several wordpress sites which are running Alpine Linux as containers inside a host (Ubuntu 20.04) through LXD.

A summary of the update is as follows:

Alpine Linux v3.8 -> 3.14
PHP 5.3.6 -> 7.4.24
Wordpress 5.0.3 -> 5.7.3

Problem

We started having issues with the server performance after those updates and we discovered that the updated containers were using 3 times or more memory (resident memory) than the older ones (about 150MB vs 50MB), which caused the server to start swapping more often.

In the older versions (using PHP 5.3), the memory used by php (process) increases as the page is being processed (as expected), but just after it finishes, it goes back to normal. In other words, something like: 10MB —> 95MB —> 10MB.

In the updated containers, the memory used by php increases in the same way, but it does not go back to “normal”: 10MB —> 95MB —> 95MB. And each time a new process is used, the same happens, increasing the memory usage by the number of available child processes (which in this case are 4 per site).

What I have tried

  • Downgrading the PHP version up to 7.2.x and 7.3.x : same thing
  • Updated to php 8.0.11 : same problem
  • Using apache2 instead of lighttpd (currently php is running as fcgi) : same behavior
  • Updating only Alpine and PHP to identify if WordPress may be the cause : wordpress is not the cause
  • Running wordpress without plugins (to know if some plugin may be causing an issue) : no change
  • Executed a simple concatenation loop (pure php) : same thing
  • Tested in a different server with a different wordpress site : same behavior

What is the reason it is not recovering the memory?
How can it be fixed?

Update

  • I setup a clean Alpine 3.14 container and performed the “simple loop” test. In that case, the resident memory was reduced as expected. However, once I tested with an actual wordpress site, the problem persisted.
  • I setup a clean Ubuntu 20.04 container and did the same tests. The result was the same as with the clean Alpine 3.14.

Answer

According to this bug report it is not really a bug, but a feature in PHP7+ under the Zend Engine Memory Management:

cmb@php.net : This is expected behavior. On request shutdown, the Zend
memory manager does not free all allocated chunks, but rather retains
some[1] to avoid the need to reallocate them possibly for the next
request.

The suggested solution is to call: gc_mem_caches(). You can use auto_prepend_file and auto_append_file directives in php.ini to execute it always if needed.

However that solution didn’t help in my situation, so it is not a warranty that it will work.

As there is no easy way to change that behavior at the current moment, I found another way to solve the memory issue (it should work for PHP7,PHP8):

  1. Instead of using php-cgi, use php-fpm
  2. Setup FPM configuration to use the least number of children processes, but let it create children if needed, for this, you can either use ondemand mode or dynamic:

/etc/php7/php-fpm.d/www.conf :

pm = ondemand
; Adjust as needed:
pm.max_children = 10

or:

pm = dynamic
; Adjust as needed:
pm.max_children = 10
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 1

The main difference between them is that ondemand will use less memory when idle, but it will be slower when a client connects.

This is a comparison of my results:

PHP Mode Children Max Idle Mem. Max Mem. Load Time Max Time*
PHP5 CGI 4 4 50MB 200MB 5s 15s
PHP7 CGI 4 4 200MB 200MB 5s 30s
PHP7 FPM / ondemand 0 10 15MB 500MB 7s 10s
PHP7 FPM / dynamic 1 10 25MB 500MB 6s 10s
  • Max Load Time is tested running 50 clients simultaneously

Values in the table are approximate and only for illustration purposes (not a real benchmark in any way).

Attribution
Source : Link , Question Author : lepe , Answer Author : lepe

Leave a Comment