Tuning PHP-FPM
How it started -
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | ; 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 and 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
1 2 3 4 5 6 | pm = dynamic pm.max_children = 30 pm.start_servers = 2 pm.min_spare_servers = 2 pm.max_spare_servers = 5 pm.max_requests = 50 |
You want to make sure you are using pm = dynamic management. 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.
There are 8 Comments to "Tuning PHP-FPM"
what happens if you add APC in the game? is memory shared?
I can confirm OP’s observation of the resulting footprint of ~140.
# free
total used free shared buffers cached
Mem: 512 245 266 0 0 101
-/+ buffers/cache: 144 367
Swap: 512 0 511
CentOS 6.x (2.6.32-042stab063.2 #1 SMP Tue Oct 23 16:24:09 MSK 2012 x86_64), 512MB OpenVZ VPS w/ Nginx 1.2.6, php53u-fpm.x86_64 5.3.19-1.ius.el6 via *nix sockets, APC enabled @ defaults.
~0 load, but this ‘lil VPS just flies. Thanks for the tips, OP.
Exactly the trouble I am dealing with and very help. thank you very much!
A bit late, but for completeness…
Yes, APC shares it’s cache between all children when running php-fpm, making it a major performance enhancement.
[...] Then you can do some fine tuning to the /etc/php5/fpm/php5-fpm.conf to fit your server setup. There’s a great post about it here. [...]
Checking to see if this is not a typo: with
you mean the process gets respawned after every 500 requests and not 50 correct?
Great writeup!
@Roelven
The default is 500, but he has pm.max_requests = 50 works fine
I’m a long time watcher and I just considered I’d drop by and say howdy there for the really initial time.