PHP-FPM Small Site Configuration

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 field pm.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 to max_children to handle each request. When requests finish, a worker has up to process_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 of process_idle_timeout.

    This sounds ideal because ondemand 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 with pm.start_servers workers. As requests come in, PHP-FPM strives to keep at least pm.min_spare_servers ready at all times to deal with the next incoming requests quickly, up to the maximum of pm.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 that pm.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 how ondemand 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 to dynamic 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. If min_spare_servers = max_spare_servers = N, dynamic always tries to keep N idle workers, spawning and killing as needed but never exceeding max_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 our min_spare_servers setting helps ensure we always have workers ready to run. As requests complete, we kill workers if needed to satisfy max_spare_servers because we don’t want too many idle workers wasting memory that could be used for other tasks. min_ and max_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.


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *