This topic has been addressed accross the Internet, but I’m deeply unsatisfied with how it’s addressed because the materials I see don’t directly answer my key questions. The manual doesn’t do a great job of explaining how to configure these parameters. This page explains the configuration descisions I made for this website.
Background
The server that hosts this page runs two web applications: PhpMyAdmin and WordPress. PhpMyAdmin has an access pattern where nearly all the time there are no clients to serve. It’s accessible only through local ports for database administrators and should not risk exposure to the public Internet. Therefore, there will be no bots, no traffic spikes, and overwhelmingly no dynamic behavior to consider.
WordPress on the other hand lives more dangerously. It faces the public Internet and must withstand rapidly changing demands, user patterns, bots, and malicious actors. Users and search engines are highly sensitive to latency and will not tolerate a slow page. Conditions change from moment to moment, and the site must adapt or the user experience will suffer.
Process Management
There are three process management schemes available to the administrator presented here in order of increasing complexity:
pm = static
You define a fixed number of PHP workers using the fieldpm.max_children
. Exactly that many processes will be started, and any additional requests will queue until a worker becomes available to handle the request up to the maximum queue depth at which point requests are dropped. In practice, we can ignore the queue depth because impatient users will leave long before the request queue overflows.
In this mode, we cannot react to changing demands. Under low load, all our workers might be idle, consuming memory for little benefit. Under high or bursty loads, unless we have already spawned as many processes as memory can handle, we hold requests in the queue that could have been served if only we had a means to spawn a few more temporary workers.
However,static
is the simplest mode to understand and comes with zero surprises.pm = ondemand
Ondemand only spawns workers when a request needs to be served. Once a request comes in, we spawn up tomax_children
to handle each request. When requests finish, a worker has up toprocess_idle_timeout
to find more work or be killed. The number of worker processes is closely matched to the load, becoming stricter the smaller the value ofprocess_idle_timeout
.
This sounds ideal becauseondemand
frees memory for other tasks (such as disk caches) when workers aren’t needed. However, spawning workers has a cost. It takes time to spawn a worker, time that could have been used to process a request instead. The first request especially must wait for the first process to spin up, increasing response times. A traffic spike can catch this strategy with its pants down, forcing PHP to do extra work to start up processes before it can start looking at the request. Ideally we want to serve those requests right now. Not a few seconds from now when the workers are ready. Now! The users are waiting!pm = dynamic
In this mode, the number of PHP workers varies according to the load by managing the number of idle workers to ready for the next batch of requests. It’s complicated, but bear with me for a breakdown.
Initially, you start withpm.start_servers
workers. As requests come in, PHP-FPM strives to keep at leastpm.min_spare_servers
ready at all times to deal with the next incoming requests quickly, up to the maximum ofpm.max_children
. The goal is to service a request right away even if we have to spawn a new worker to do it. When load decreases enough such thatpm.max_spare_servers
is exceeded, processes are killed to save memory.
Briefly,dynamic
mode means that we will keep spawning workers as needed to deal with the load, up to a maximum, very similar to howondemand
works. Additionally, we try to keep a range of workers ready at all times so that a request doesn’t have to wait for a new worker process. The key todynamic
is to balance readiness against the opportunity cost of memory that could be used elsewhere on the server.
To help explain the behavior better, let’s look at some examples. Ifmin_spare_servers = max_spare_servers
= N,dynamic
always tries to keep N idle workers, spawning and killing as needed but never exceedingmax_children
.
Here’s a more complex case. Consider the config:pm = dynamic
pm.max_children = 7
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
We start with one worker process. When a request comes in, we have no spare processes, so we spin up a second worker in the background while dealing with the current request just in case another request comes in. Maintaining ourmin_spare_servers
setting helps ensure we always have workers ready to run. As requests complete, we kill workers if needed to satisfymax_spare_servers
because we don’t want too many idle workers wasting memory that could be used for other tasks.min_
andmax_spare_servers
express a balance between how ready we want to be for new work (min_spare_servers
) and how tight we want to be with unused workers (max_spare_servers
) bounded by our memory limits (max_children
).
Now that we understand how the process management modes work, let’s assign the right ones to our use cases for this small site.
Admin Apps (PhpMyAdmin)
Your website is your proverbial baby. As such, admins are not sensitive to latency. There’s not much advantage to keeping worker processes ready at all times when admin work is very ocassional and a fraction of a second delay isn’t going to cause you to browse elsewhere.
For this app, ondemand
is the best fit. We can have zero extra workers for the app when an admin isn’t using it, which means more resources to run WordPress, backups, or other server activities. We can even set process_idle_timeout
to the length of an admin session (maybe 20 minutes) to get reasonable performance by delaying worker termination during a typical session.
Client Apps (WordPress)
To be clear, Client App here means the app that faces general users. It’s very important that we are ready to handle requests at all times, therefore ondemand
is out the window. The leaves static
and dynamic
.static
can work very well for a small site depending on the opportunity cost of keeping around so many idle workers. If your server doesn’t do anything else, spawning a large number of workers isn’t wasting memory. You have the memory so why not use it?
dynamic
however seems to be the best choice for small sites, especially sites operated by an individual such as this one. This server does more than serve WordPress. It performs other small tasks in the background throughout the day. For those tasks, I’d like to have the flexibility of using the memory that would otherwise be occupied by workers. Finding the balance on how many idle workers to maintain is key, and that will depend on the traffic patterns and characteristics of your load as well as how much memory you have installed. On this tiny VPS, I allow bursting up to ten workers but keep 3 ready for incoming requests.
Leave a Reply