WordPress has two caching APIs which are under-documented and under-utilised (in my experience) – Object Cache and Transient. They allow you to speed up the site even if you need fine-grained control over who sees what – or if you have a complicated page with multiple flavours/filters/views that makes traditional (whole page) caching unsuitable.
Both the Object Cache and Transient APIs aim to achieve a similar thing – saving something that is computationally expensive (time consuming) to fetch or generate so that you can use it again without regenerating/re-fetching it:
Object Cache:
wp_cache_add( $key, $data, $group, $expire ) // saves $data as $key in $group until $expire seconds have passed
wp_cache_get( $key, $group ) //gets the value of $key
Transient:
set_transient( $transient, $value, $expiration ); //saves the value of $value as $transient until $expire seconds have passed
get_transient( $transient ); //gets the value of $transient
In practice both of these are generally used in functions that fetch or calculate a lot of data:
function myExpensiveFunction(){
$cacheKey = "expensive_function_cache";
$cached = json_decode(get_transient($cacheKey));
if($cached){
return $cache;
}else{
$expensiveValue = [do some expensive queries and processing]
set_transient($cacheKey, json_encode($expensiveValue), 1000)
return $expensiveValue
}
}
Using this pattern we’ll only do the “[do some expensive queries and processing]” part once every 1,000 seconds, as the value will be stored the first time by set_transient() and returned on subsequent calls by get_transient()
Object Cache vs Transient differences
Groups
The Object Cache API is a bit more sophisticated in that it has groups which could be anything you define – it’s often a post type (e.g. Events) so that you can clear all event-related cache values at once when a new event is published, for example. You can control which groups are persisted between requests (see below) which allows you to use some groups to store potentially private/personalised data that will be deleted at the end of each page load, while other groups store non-sensitive data that is available for all requests.
Transients do not have groups, but you can generate the keys dynamically – e.g. you can do
$key = "expensive_personalised_function_" . get_current_user_id()
set_transient($key, myExpensiveFunction( get_current_user_id()), 1000)
which will create a new transient value for each user which will be deleted after 1,000 seconds.
Persistence
Both the Object Cache and Transient APIs can be modified by plugins to change where they store their data but the default behaviour is very different.
The Object Cache (by default) only stores the value in memory for the duration of the request. This means that if your code needs access to the same data several times in a request (e.g. once in the routing function, once in the header, once in the footer) then you can save yourself some time but it’s probably quite limited because there is a limit to the number of times you’d want to reuse some data in a single page load.
The Transient API by default stores its values in the wp_options table which means that they can by used by multiple requests (page loads) and will be used until the expiration time is reached. This means that you can probably re-use the data many, many more times that with the Object Cache API – e.g. you can fetch all the latest posts from the different social media channels and all users can use the same data, instead of having to re-fetch it for every user. Of course, you need to be careful not to store sensitive/personalised data in this or you can accidentally serve the data for one user to a different user.
The most common (and useful) way that plugins alter the behaviour of the above APIs is to change where the data is stored. They can save the data to disk or, more commonly, to a fast object store such as Redis or Memcached. The two main advantages of this are that these stores are very fast (typically several times faster than a database or disk) and they can be shared between servers, so that if you have five servers serving the same site, each value only needs to be cached by one of the servers instead of each of the servers.
Which to use – Object Cache or Transient
In my experience the Transient API is what you want in most cases. It persists whether or not you have a Redis-backed plugin or similar and you can always start without that extra infrastructure and add it later if you need it.
The Object Cache API is useful if you’ve got Redis installed already and want to do some more complicated cache invalidation using groups. I’d say the fact it doesn’t persist without the extra effort is one of the biggest sources of confusion amongst WordPress developers – I’ve heard lots of people say they used it and it didn’t speed things up – either because they’d not added Redis, or they weren’t fetching the cached values correctly, or they didn’t add the group to the list of persisted groups, etc.
If in doubt, use set_transient() and get_transient() unless you absolutely need groups and/or don’t mind messing around with Redis and getting it all configured correctly.
Other things you can do to speed your WordPress site:
Outlandish uses a bunch of ways to speed up WordPress sites:
- Minimise the use of third party plugins (or themes) that can be very inefficient
- Add Fast-CGI caching to your server (so that Nginx caches the full HTML response of a page without actually hitting PHP or the database)
- Add Cloudflare as your DNS provider – so that Cloudflare caches the full response and completely bypasses your server
- Add browser caching – so that your users’ browser only requests each file/page once
These are all great and allow us to host complicated sites on and keep them fast without needing expensive servers.
However, they are all a bit crude – they’re basically making a copy of something quite large and serving the copy many times. This doesn’t work well if lots of different people are logging into a site and all getting slightly different views of the data, for example. You can’t cache the whole page because you’d end up sending someone else’s view – potentially including information that another user is not supposed to see.