Caching can give the impression of being the “holy grail” for all performance problems. It is no wonder that people raise an eyebrow when I say “Stop using caching” in my presentations, meetups or workshops. For some, especially in the WordPress Community, I have become the guy that hates caching. Hence, it is time to make clear what I really mean about this topic, when, and for what it should be used. And maybe most importantly, when not to use caching – and look for alternative solutions.
When should you use Full Page Caching?
Let’s start with the biggest of them all. Full Page Caching, or just page caching. A technique where you temporarily store a pre-generated version of a page to deliver the exact same code (HTML) to visitors in a limited time span. This is how full-page caching works:
- Visitor A visits abc.com/page. This page is not in the cache and hits PHP and gets generated from the database. Before it is served to Visitor A, it is also stored in the full-page cache with an expiry of 10 minutes.
- Visitor B visits abc.com/page 2 minutes after Visitor A, and is therefore served with the same page, but this time directly from the cache. The pages served to Visitor A and B are identical.
- Visitor C visits abc.com/page 15 minutes after Visitor A, and because the cached version of the page is now expired, the request hits PHP again, and the page is re-generated just like for Visitor A.
Because everyone that visits this page in the time span where a valid cached page exists, the page can be delivered very quickly. Almost no resources are used to deliver a cached page, and therefore full page caching can improve the performance and, more importantly, scalability. It can sound very attractive, but full-page caching also has some big disadvantages.
- If you want to deliver dynamic content, personalised or in other ways different content to different users – this has to be solved with javascript (ajax) that runs after the HTML document is delivered to the visitor. This can work nicely, but it requires yet another extra request to the server. If you use caching to avoid writing fast code, you will still run into problems with your dynamic ajax requests.
- Just because something works fast for you, it does not mean it is fast for everyone. Web sites do not have a single point of entry, and visitors come to your site through a variety of pages. This is especially true for WooCommerce, where visitors use Google and come through product pages. Or, if you run Google Shopping on the full catalog with links directly to product pages. To address this problem, some people even try fixing this by “priming” the full page cache using spiders to index the full site with the purpose of making sure that every page is cached beforehand. In practice, this does not work well. It is vulnerable, difficult to set up and configure correctly, difficult to maintain, time-consuming – and completely unnecessary.
- You lose control of the real performance of your website and how the code works (or not works as it should). If you use full-page caching, you lose the sense of what the real response time of your site is and how good your code works. Experience shows that the lack of focus on real performance is the main reason why sites crash on days like Black Friday or when distributing large campaigns.
What about Caching plugins like W3 Total Cache?
WordPress Plugins like W3 Total Cache work like all other page caching. They store a version of a pre-generated page in the file system or memory and serve it to users until it expires. W3 Total Cache is being used by 1 million websites, but that doesn’t mean it is a good idea. Especially not if you use fast hosting.
W3 Total Cache is a very large plugin, and for most sites – this just adds a whole lot of unnecessary code to the website. More code means more things can go wrong. If you really have to do full-page caching, this should not be done in the PHP part of your application because PHP is slow. Full Page Caching should be done as close to the user as possible in the web server (nginx in our case).
W3 Total Cache talks about getting pages to load in less than 2 seconds showing off a theme like twenty-sixteen. To make it clear, a similar test on our servers will be delivered in about 100 milliseconds without the use of caching, not seconds.
So when should you use Full Page Caching, and why?
Full-page caching was made to scale websites with a lot of traffic to easier handle the spikes in traffic. Full Page Caching was invented when people used the web mostly to read news articles. Generating unique content for every visitor is not sensible for newspapers because their pages are mostly static with infrequent updates. For this scenario, Full Page Caching is totally right – but today’s websites are more like applications.
Contemporary websites are more complex, dynamic, and steadily more personalised. The code is increasingly complex, and adding even more complex mechanisms (read caching techniques) on top of already complex code is not always a good idea. In practice, you will then add another layer of technology that introduces new single points of failure and needs dedicated maintenance, upgrades, and operations. It will make bugs harder to solve and general development harder (more expensive). If you base your performance on Full Page Caching, I can close to guarantee that the solution will collapse if the caching stops working.
The answer to the question is, therefore, to write good code and queries and keep in mind that the solution must be able to scale. It is OK to use Full Page Caching to handle sudden spikes in traffic, just like newspapers did and still do.
It should be possible to turn off Full Page Caching on a regular day, with normal traffic, without getting nervous. And if you do things right, you should be able to turn off Full Page Caching on days with a lot of traffic, too – without crashing your website.
On Servebolt, the large majority of shops run without Full Page Caching – even on high-traffic days like Black Friday.
WordPress Object Cache – you use it without knowing it
Many people don’t know that the WordPress Object Cache is in use in close to all WordPress sites. And yes, the Object Cache is effective even if you don’t use an external Object Cache, like MemCached or REDIS.
How does the WordPress Object Cache work?
- You make a request for i.e. postmeta values by using
get_post_meta()
- WordPress runs the request and gets the result from the database, and automatically stores the result in the Object Cache (Memory, RAM)
- You get the result and use it somewhere in your code
- Later in your code, you make another
get_post_meta()
request for the same data - WordPress already has this in the Object Cache, and returns it directly without re-querying the database
- The page is delivered to the visitor, and the Object Cache is flushed (memory freed up)
Not all functions in WordPress save their results in the Object Cache but get_post_meta()
does. This is because the _postmeta table in the database can grow very large, and the requests can become expensive (use a lot of computing resources).
As a developer, you can add results to the Object Cache yourself so that the re-use of the same data later can be faster. If you write your own queries with i.e. WP_Query, you will have to store the results in the Object Cache yourself. It is important to remember, though, that, like with all caching, you can not trust it. Therefore make sure you don’t write code that presupposes that your results are in the Object Cache.
You can check the performance of the Object Cache and the number of queries to the database by installing a plugin like Query Monitor. In my tests with a regular WooCommerce, a normal Object Cache hit rate is between 95% and 98%.
How to use WordPress Object Cache for your own Queries
$result = wp_cache_get( 'some_unique_name' ); if ( false === $result ) { $result = $wpdb->get_results( $query ); wp_cache_set( 'some_unique_name', $result ); } // Do something with $result;
What this code example does is first to try to get a $result
, and then check if there really is a $result. wp_cache_get()
returns false
if it does not exist. If there is no data, we run the query for our data, and then we store it with wp_cache_set()
so it will be available for future requests.
But what about external Object Caches like MemCached and REDIS?
An external Object Cache can provide what, more specifically, is called a “persistent Object cache”. That is an Object Cache that does not get flushed between page views, so it retains its data across different page views. Smart idea, right? But as usual with caching, it is not as simple as that.
- The potential performance gain from using a persistent Object Cache is limited to the hits that miss the Object Cache. Using the default non-persistent cache hit rate of 95%-98%, the potential for performance gains is for between 2% and 5% of the request. In addition, an external object cache will add some extra latency because it is an external application – so it will slow down the 95-98% that were served directly in PHP before. The net gain from added latency + performance gain from external Object Cache often becomes negative for E-commerce stores.
- Even if you use an external Object Cache, you can not trust that the information exists when writing your code.
- If you are dependent on an external Object Cache to make pages load at a decent speed, it means your code is bad, that database queries are too heavy, or that there are too many of them. The real problem is in your code, not how quickly the database responds. That’s why you should not use the band-aid MemCached or Redis.
- If you use a fast database that is optimised and uses indexes correctly, you do not need an external Object Cache.
If you don’t need an external Object Cache, it can be harmful to use one
Harmful may be stretching it, but it often is. This really applies to all technology you include in your web stack that you don’t need. If you have it, the chance of making yourself dependent on it is big, even if you really don’t need it. If you are dependent on it, you increase the chances for something to go wrong. In addition, every added piece of technology in the stack requires installation, configuration, maintenance, and security upgrades – and introduces yet another single point of failure.
Transients can be good, but also break your site
Transients are in use by WordPress and a variety of plugins, and the concept is quite simple. You make a request and save the result, or parts of it, in the database for later reuse. As opposed to the Object Cache, you don’t need any extra technology to make this persistent across page views because data is stored in the database. Transients can have an expiry time or not – that’s your choice.
Personally, I don’t fancy excessive use of transients, and there are many reasons for that
- Transients are saved in WordPress’ _options table. This table is already heavily used, and excessive writing to _options can introduce locking issues (queue).
- Excessive use of transients will result in a large _options table. Every single page view is dependent on this table, and a large options table can reduce performance on all page views.
- Transients that do not expire will be queried on every page load (autoload) through wp_load_alloptions(). This will reduce performance on all page views.
But, with this said – transients can be great for performance if used right. If you query for data that is used often and seldom changes, it can be a good idea to store the result in transients. It is much easier for WordPress to fetch the value from key X in the options table than to run selects across other tables. But, if you use transients – make sure to keep an eye on the _options table to make sure that it doesn’t grow wildly, and make sure to implement cleanup for transients that you no longer use or that have expired. And do never use transients for data that requires frequent updates, it will kill performance.
Fragment Caching is good if used right
Fragment Cache is a type of caching where you store an element, part of a page, or something else that is resource expensive to generate and/or is used often. Many have chosen to implement Mark Jaquith’s function to do Fragment Caching in WordPress.
Fragment Caching means storing a result (HTML output) so that it can be delivered much faster later. The idea is as simple as it is amazing. You can choose what elements to cache, and you don’t need to cache the full output – like Full Page Caching.
The way you do this in WordPress is equivalent to Object Caching because there are no limits to what data you can cache. Therefore, the same principles apply; don’t ever trust that something is cached, make sure you don’t depend on it – and focus on your code rather than taking the fragment cache shortcut.
Many implement memory-based fragment caching. But for that to have an effect, an external Object Cache is required to make the elements available across page views (see above). I recommend gentle use of Fragment Cacheing and storing data in transients instead. In practice, that will give you the same results in terms of performance without adding additional technology to your stack.
This is how you save transients
$output = get_transient('some_unique_key'); if( $output === false ){ $output = 'Some data'; set_transient('some_unique_key', $output, 3600); } // Do something with $output
In this code example, we read $output
from a transient and test if $output exists. If get_transient()
returns false
we generate the data and store it in a transient for later use.
What cache technique should I use, and when?
Use Full Page Caching for scaling, that’s what it’s made for – it’s not made to increase performance. Make sure not to make yourself dependent on it. You should be able to turn off full-page caching without getting any significant performance hits.
Use the Object Cache as much as possible, especially if using WooCommerce – but steer clear of external Object Caches, they will introduce all sorts of new problems. External caches will shift your focus to the wrong parts of your application. A better trick is to get faster hosting, with high performance databases like ours.
You should use transients for frequent queries that don’t change much. Make sure to set a suitable expiry date based on how often the data should update. Also, make sure to delete expired transients. Use fragment cache in transients for elements that are resource intensive to use and that are used frequently.