Traffic surges and overall growth are an indicator of business success. A big splash of attention resulting in lots of people coming to your site means you’ve done something right, but you’ve also got a lot on the line. You want to make sure your site keeps running fast no matter how many hits it gets. As your site traffic grows, it puts more load on your server resources. If you’ve got limited server resources, then you can’t scale.
These principles are simple enough to understand, but what about when you’re serving content at scale and dealing with the most demanding applications? The technical fundamentals of a well-performing site must be in place. In reality, this is essential for all sites but, it’s a non-negotiable for business-critical sites that are prone to traffic surges (looking at you, ecomm and elite publishers).
PHP workers and CPU core counts often get thrown around with hosting, but there’s a misconception as to what actually matters for scaling a site.
So, let’s talk about performance and dig into PHP.
There are a few simple rules that your site’s performance is based on, which we’ll go into more detail on later. For now, it boils down to three things:
- Use the caching layer, whenever possible.
- Don’t have slow database queries.
- Do as little as possible in PHP.
Scaling vs. Performance
Planning for scalability shouldn’t sacrifice the performance of your site.
Performance = how long it takes to serve a request = low latency
Scaling = ability to handle more requests at once = high throughput
Digging into PHP
Things that are single threaded, like the most common installation of PHP, don’t perform better based on more CPU cores. After all, each request can only run on one of them.
But, extra cores when cache + PHP + MySQL are all on the same server can help by reducing resource contention.
As it relates to hosting, the focus can often be on the wrong metrics here: number of PHP workers. In reality, the number of workers doesn’t actually matter, as it’s only a tuning parameter. The key is in the number of CPU cores you get.
There is an exception here though, and that’s if your hosting stack is just Apache + mod_php. In this case, if you run out of PHP workers, you get an error.
With PHP-fpm, Nginx-unit, Litespeed lsapi, or stacks with varnish/Nginx in front of Apache then extra requests can be queued between the different layers making the number of workers immaterial to scaling.
Our goal with PHP is to have the right number of workers to keep our CPU cores at 100%, all the time.
But it’s not only PHP running on servers, often times we’re seeing:
- A kernel, doing memory management, TCP stack
- Some logging infrastructure
- Some monitoring infrastructure
- In some cases, an Nginx cache (this is the standard Pagely setup) and it might have an additional logging infrastructure
- In some cases, a Redis object cache (also standard at Pagely)
- In some cases, a MySQL database (not at Pagely, but this is the case at many VPS providers)
Generally, this doesn’t add up to a lot, unless the database is on the same server. At very high traffic sites Nginx, logging, Kernel, and Redis can add up to a lot, so on an 8 or 16 core server doing a lot of traffic you might want to plan for PHP to use 75% of the cores.
So if your page just uses PHP, doesn’t use MySQL, doesn’t talk to any APIs (making http requests somewhere), doesn’t use Redis, then things are pretty simple. 1 PHP worker per core would suffice and it would run at maximum efficiency giving you the least amount of overhead. That means the best performance (lowest latency) and the highest throughput (average requests per second).
But that’s not the real world.
In the real world, we need to talk to data stores, read files from disks, and make requests to APIs. So, we generally want to be in the 2-4 workers per core range for WordPress.
A few exceptions to that include:
- If you do a ton of very fast, very simple requests you might want to be higher, because there’s some overhead to the request read/respond cycle that is doing IO between, say, Nginx and PHP-fpm, and we need extra workers to keep that busy.
- If you’re building an application hitting a bunch of APIs, or making a bunch of slow database connections, you might want even more.
As a baseline at Pagely, we generally don’t run things higher than 4 workers per CPU core.
So what is the downside for optimizing PHP for efficiency?
If your site suddenly gets much slower, due to a slow external API call, you will no longer be maxing out your CPU since you’ll be spending so much time waiting. The upside here is that more workers don’t help you much anyway, and having a bunch can expose you to lots of interesting server death conditions due to running out of RAM.
Stay tuned for a future post on these server death conditions… we’ll get to it someday soon.
When it comes down to it, you want to optimize PHP so that it uses your target number of CPU cores without wasting resources. This ensures:
- Best performance (lowest latency)
- Good reliability (low chance of running out of memory)
- Best efficiency (highest throughput when maxed out)
Optimizing Your PHP Code for Higher Performance
In general, optimizing your PHP code for higher performance fits into two categories:
- Running less code
- Making fewer calls to external resources.
1. Running Less Code
Running less code is done in both the obvious way; turning off plugins where the feature adds little value and, the less obvious way; transient caching and code optimization.
Transient caching is pretty straight forward:
- Find a section of code that takes a lot of time to produce some HTML.
- Figure out if it’s personalized, or can be cached globally or as some group of variants.
- Use the WordPress Transients API to cache that HTML.
A tool like New Relic can help you identify slow chunks of code, so you can make those fixes efficiently.
Transient caching can also be used for datasets instead of just HTML but, in general, the closer to final output that you can do caching, the more impact you will have.
Code optimization is harder, which is where you likely need a developer to dig into things. It might be as simple as switching to a more efficient algorithm, but often it will take a larger rethink and rework of how things are being done.
2. Making Fewer External Calls
When you make a MySQL query or an http request to an API like Twitter, PHP sits and waits for the response.
You want to do less of this.
The Transients API can be the answer here, caching the data from API calls where it doesn’t need to be fresh. On the database side, you might be able to fetch less data from smart page design.
Does a page really need to show content from 100 posts at once? Probably not.
I spoke on how to determine these problems and presented strategies for solving them at WordCamp Phoenix. You can watch the whole talk below:
Huge wins are possible, but look for easy wins from transient caching and design first.
When it comes to your site’s performance, and how PHP effects that, it’s all about being smart. Understand what’s needed in terms of PHP worker counts vs CPU cores, and how to use your resources efficiently without extra code hanging around to slow things down.
If it’s over your head, talk to a managed WordPress host that can help alleviate the pressure and get things running smoothly.