How do PHP Workers Impact WordPress Performance?
Caching is king when it comes to WordPress performance. A well-optimized page cache like that of SpinupWP can unlock blazingly fast load times and handle hundreds of concurrent requests without breaking a sweat. However, not all sites can be page cached. Highly dynamic WordPress sites, such as community forums, often have page caching disabled. Similarly, ecommerce sites (think WooCommerce) will bypass the page cache for specific pages like the cart or checkout to ensure that visitors get a customized checkout experience. PHP workers play an essential role in loading the dynamic pages of your site quickly.
What are PHP Workers?
PHP workers are background processes that run on your server that execute PHP code. In WordPress, whenever Nginx receives a request that needs to be processed by PHP, it will forward the request to PHP-FPM, which will assign the request to an available PHP worker. The PHP worker will execute the PHP code, fetch any required data from MySQL or Redis, and build the page output. The page output will then be passed back to Nginx before being sent to the browser.
In a nutshell, PHP workers are the server processes that run your WordPress code. Whenever a request to an uncached page is received, it’s handled by a PHP worker.
Each site created by SpinupWP gets its own PHP pool, which can be visualized as a container for a group of PHP workers. The PHP pool is also responsible for managing the PHP workers within it, based on the PHP pool config. If you’re using SpinupWP, a sensible PHP pool configuration is used to ensure that enough PHP workers are available to handle incoming requests without using too many server resources (memory and CPU).
SpinupWP uses a dynamic method for determining how many PHP workers should exist in a PHP pool at a given time. At all times, at least one idle PHP worker should be available to handle any incoming requests, up to a maximum of five PHP workers per site.
Limit the Number of Sites Per Server
It can be difficult to determine how many sites you should host on a single server. However, when hosting highly dynamic sites, especially those receiving significant traffic, we recommend keeping it to 1 site per server.
Not only will this make it easier to tune the PHP pool config, but it will also free up system resources. Having multiple sites on the server will mean multiple PHP pools are running, all of which are using CPU and memory.
The rest of this doc will assume that you’re hosting a single site on your server.
Not All Servers Are Equal
Before delving further into PHP workers, you should be aware that there is more to uncached WordPress performance than the number of workers available. In fact, increasing the number of PHP workers available for processing incoming requests can cause performance issues.
If you plan to host a site on your server that gets significant traffic to dynamic pages, we recommend that you provision a CPU-Optimized server. In our testing, merely switching to a CPU-Optimized DigitalOcean Droplet from a Basic DigitalOcean Droplet improved response times by 100%.
Similar to the type of server that you’re selecting, you need to ensure that you have plenty of CPU cores available. Remember that PHP isn’t the only process involved in handling a WordPress request. Nginx, PHP, Redis, and MySQL (the latter being particularly CPU hungry) will all be fighting for CPU time during the lifecycle of a request, so you need plenty of cores. We recommend having at least 8 CPU cores available before you start modifying the default PHP worker config.
Using loader.io to Benchmark Performance
There’s no exact science when it comes to adding additional PHP workers. The only failsafe way we’ve seen is to increase PHP workers and benchmark the results incrementally. Eventually, increasing the number of PHP workers will start to degrade performance, and you’ll know that you’ve reached the limit for your current hardware.
Start by benchmarking your current configuration. In this example, we’re using a 16GB/8vCPUs CPU-Optimized Droplet from DigitalOcean. We’re using loader.io to load test the site. In loader.io, we recommend using the “Maintain client load” test type with your target range of concurrent users. In our case, that’s 0-250 simultaneous users over a 2-minute duration.
You’ll see that the average response time is 492ms (milliseconds), and the maximum is 1167ms once 250 concurrent users is reached. It’s also a good idea to SSH to the server and keep an eye on htop so that you can see system resources.
Notice how our CPU cores aren’t being fully utilized. You’ll also notice that PHP-FPM has five processes running (based on our dynamic SpinupWP pool config) and that Redis, MySQL, and Nginx are the next most CPU intensive processes. This is to be expected for dynamic web requests. As a general rule, you want to tune the PHP pool config to use 80-100% of your CPU capacity while keeping the number of PHP workers as low as possible.
How Do You Add More PHP Workers?
It’s time to open your site’s PHP pool config. The default pool config in SpinupWP looks like this:
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 1
When tuning the number of PHP workers, we recommend that pm
is set to static
, which will ensure that the same number of PHP workers are always available to handle incoming requests. This reduces the overhead involved with the process manager having to scale the number of PHP workers dynamically. The pm.max_children
value will determine the number of PHP worker processes available. All other options are ignored when pm
is set to static
so you don’t need to modify the values.
We’ll start with 5 PHP workers, like so:
pm = static
pm.max_children = 5
Remember to reload the corresponding PHP-FPM version when updating your PHP pool config:
sudo service php{PHP_VERSION}-fpm reload
Then repeat the loader.io test and keep a note of the results. Keep doing this as you gradually increase the number of PHP workers (pm.max_children
) until the performance starts to degrade.
PHP Workers | Avg Response Time | Min Response Time | Max Response Time |
---|---|---|---|
5 | 482 | 90 | 1154 |
10 | 395 | 90 | 1024 |
15 | 395 | 90 | 981 |
20 | 396 | 90 | 989 |
30 | 400 | 91 | 1036 |
50 | 408 | 91 | 1043 |
100 | 416 | 91 | 1222 |
As you can see from the results, the optimal number of PHP workers is 15. Although 10 and 15 PHP workers give us the same average response time, the max response time is higher when running only 10 PHP workers. This suggests that as the number of concurrent requests reaches the top end of our range (250), 15 PHP workers will perform slightly better.
Key Points
The default PHP pool config in SpinupWP is more than adequate for most WordPress sites. Only high traffic, dynamic sites should need to modify the PHP pool config.
Every site is different. A site’s efficiency will play a significant role in the number of PHP workers required. Sites that query the database a lot will perform very differently to those that use object caching to effectively reduce queries. Similarly, WordPress sites with lots of poorly written plugins will perform differently to sites with just a handful of well-optimized plugins. Therefore, PHP pool config isn’t a silver bullet, nor a one-size-fits-all solution.
When performing the above benchmarks, remember to test a URL that isn’t page cached. If the entire site is bypassing the page cache, it’s OK to use the homepage. For ecommerce sites, you should use a dynamic page such as the checkout.