I recently switched to Linode for a basic VPS (512 MB RAM, 20 GB storage). I wanted to be able to install absolutely what ever version of what ever software I wanted. Since I have become very familiar with Arch Linux (I run it on all of my home computers now) I decided to make that my server OS for the time being. Along with being lightweight, minimalistic and always up-to-date arch offers an awesome set of packages and the best package manager I have ever used. Par of Arch Linux's appeal is the Arch Wiki. A repository of important information for installing and configuring packages for Arch. So when following the guide for nginx, I also installed PHP-FPM for PHP/CGI.
All is well until weeks later I notice my memory usage tends to balloon after a few days (some times hours). After hours of toying, poking, prodding, and messing with WordPress settings, I figured out that PHP-FPM was the culprit. At times consuming 300+ MB of memory! Something had to be done, but what? After a good bit of digging and a few key parameter changes later PHP-FPM was well within the memory constraints of my server and website performance became noticeably faster!
The Problem
First we need to take a look at the default config file for PHP-FPM. You are going to want to find the section that looks like this:
; Choose how the process manager will control the number of child processes.
; Possible Values:
; static - a fixed number (pm.max_children) of child processes;
; dynamic - the number of child processes are set dynamically based on the
; following directives:
; pm.max_children - the maximum number of children that can
; be alive at the same time.
; pm.start_servers - the number of children created on startup.
; pm.min_spare_servers - the minimum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is less than this
; number then some children will be created.
; pm.max_spare_servers - the maximum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is greater than this
; number then some children will be killed.
; Note: This value is mandatory.
pm = dynamic
; 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 value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI.
; Note: Used when pm is set to either 'static' or 'dynamic'
; Note: This value is mandatory.
pm.max_children = 60
; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 20
; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 20
; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 35
; 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.max_requests = 500
Here, if you read the descriptions you get a basic idea of what these options are for. If we take a look at the line:
pm.max_children = 60
you see that the max "children" is a nice and high 60. This means that PHP-FPM will spawn a max of 60 child processes each requiring some amount of memory.
The next line to look at is:
pm.start_servers = 20
This shows us that there will be 20 server processes started.
Both:
pm.min_spare_servers = 20
...
pm.max_spare_servers = 20
limit how many resources are consumed when the server is idle, you want at least a few processes available in case a spike happens but you don't need a bunch sitting around after one dies off.
Finally the last line we need to tweak is:
pm.max_requests = 500
This causes processes to be killed off and re-started every 50 requests regardless if they need to be. This is perfect for fighting memory leaks in something like a poorly written WordPress plugin.
Now these would be considered sane defaults if our server was a bit more beefy (at least 1 GB of ram) but because were in such a limited environment, way to many PHP processes are getting spawned and combined with local caching the whole thing is a bit frustrating. So what can we do to fix it?
The Solution
Looking at the file and better understanding what the options mean is a great step but what numbers should be used? Well there really is little information on what is right because there are way to many variables to take this into account. However, lowering all these variables to more sane values will sure help a lot in the total memory consumption of PHP-FPM and the responsiveness of your server.
Currently here are the values that I am using:
pm = dynamic
pm.max_children = 30
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 50[/cc]
You want to make sure you are using:
pm = dynamic
With out it, it will just use a fixed size (fine but not necessary and makes some settings pointless).
After some testing, the server seems to idle at around ~140 MB of memory (for everything) which is much improved over the ~420 MB it was using previously.
The goal here is to find values that work for you. I plan on testing my site with Apache JMeter for a better idea just how significant the savings are in terms of page load times.