Do you want to make your WordPress website faster while leveling up your WP programming skills? Object Cache may be your answer!
In the What is an API post, I’ve briefly mentioned WordPress Object Cache and how I am using Redis here in the blog. In this post, we will give a better look into both of those things.
Before we go into Object Cache… What is cache?
Before we start talking about Redis, we need to talk about Object Cache. And before we talk about Object Cache — you saw it coming — we need to talk about cache in general.
Looking for definitions of what is cache on the Internet, common sense seems to be something like this:
Cache is an intermediate storage with quick access, placed between the consumer and the main storage, potentially saving a longer trip.
Look at the example below. In the first request, we go all the way through the origin server but the server cache saves a copy of the response. In the second request, the copy is served, saving time by not going to the origin server.
With that in mind, we can start thinking about all the several trips needed to visit a website and all the intermediate steps we can insert to save time:
Browser cache | Between your browser and the site server, if there is a valid copy of the page on your computer, we save a trip. In fact, we save the entire trip in this case. |
CDN | If you are using a CDN like Cloudflare on your website and if there is a valid copy of the content in the CDN, there is no reason to ask for a new copy. It is enough to send just the copy and we save a trip. |
WP’s cache | In your WordPress website, if a post was already brought from the database and if the version of the post in memory is still valid, why go to the database again? |
There are lots of other intermediate places we could save a version of the information, these are just some examples.
WordPress Object Cache
WordPress Object Cache is simple and understand how it works can make the difference during your next job interview 😉
As I said in the What is an API post, memory access is always faster than disk access. With that in mind, if we brought a post from the database and put that in memory, we do not need to go to the database to fetch it again.
The problem is that in WordPress default implementation, it will be in memory only until the end of the request. If the user refreshes the page, we will need to go to the database again.
MySQL is smart enough to notice if a piece of information was requested recently and will make it available in a quicker way (basically storing it in its own memory part). Although faster, it will not be as faster as not going to MySQL at all.
By default, this post (or any other piece of info we want) requested at the beginning of the execution is stored using a class called WP_Object_Cache. Its main goal is to save trips to the database and it works basically using key-value pairs. The problem is, like I said, all that only lasts one request. For the next one, we have to do everything again.
How to “store” things in memory? Redis or Memcached
Softwares like Redis or Memcached are in-memory databases. They can do more than that — especially Redis — but for our problem with WordPress, its key-pair value in-memory storage is everything we need.
In this post, I will use Redis as an example but Memcached installation and usage are very similar.
What do I need to use WordPress Object Cache with Redis?
The requirements to use WordPress Object Cache with Redis are quite simple:
- A WordPress website
- Redis running in your infrastructure
- A WordPress plugin to connect WP to Redis
The best way to have step #2 done is to contact your hosting provider. On Cloudways, for example, you need to click on a button on their Dashboard to install Redis.
The Redis Object Cache WordPress plugin
As I said in the other post, the plugin I use is Redis Object Cache. Install and configure it is not hard. Out of the box, it will try to connect to a Redis instance in the same server (127.0.0.1
) on port 6379
using Redis database number 0
.
To have the plugin working, it will need to copy its object-cache.php
file from its own includes folder to the wp-content folder of your WordPress installation. If for whatever reason like directory permissions, it is not possible to copy the file automatically, you will need to copy it manually.
Same Redis for different sites at the same server
I have another blog running on this same server, sharing the same Redis installation. To avoid conflicts between stored information for each site, I had to define define( 'WP_REDIS_DATABASE', 0 );
on one and define( 'WP_REDIS_DATABASE', 1 );
on the other.
To adjust the configuration you will need to place some constants in your wp-config.php file. The full list of settings is available on the plugin wiki.
How the Object Cache plugin works
Inside the wp_start_object_cache() function (see below), executed at the beginning of WordPress load flow, WP will detect if there is an object-cache.php
drop-in or not. If it is available, the file is loaded, otherwise, the default implementation is used. After that, the cache is initiated.
function wp_start_object_cache() {
...
if ( file_exists( WP_CONTENT_DIR . '/object-cache.php' ) ) {
require_once WP_CONTENT_DIR . '/object-cache.php';
...
}
...
// If there is no external cache, loads WP's.
if ( ! wp_using_ext_object_cache() ) {
require_once ABSPATH . WPINC . '/cache.php';
}
require_once ABSPATH . WPINC . '/cache-compat.php';
...
wp_cache_init();
...
}
Redis Object Cache‘s object-cache.php
contents can be seen here. It is formed by an implementation of the WP_Object_Cache
class (last part of the file) and the several functions that use it like wp_cache_add
, wp_cache_get
, and wp_cache_delete
.
Let’s give a look at some code
If you need some code to understand things better that is okay, I am one of those too. Here is a part of the code executed when you call get_post( 123 );
in WordPress 6.0.1.
The code starts in wp-includes/post.php, where get_post calls WP_Post::get_instance( $post )
.
Then, in wp-includes/class-wp-post.php:
final class WP_Post {
...
public static function get_instance( $post_id ) {
$_post = wp_cache_get( $post_id, 'posts' );
if ( ! $_post ) {
...
wp_cache_add( $_post->ID, $_post, 'posts' );
}
...
}
}
At first, it checks if the cached value exists (wp_cache_get
). If it exists it is used, otherwise, the post is fetched and then cached (wp_cache_add
).
If we look at the wp_cache_add
function code, we can see it is nothing more than a simple wrapper for the add
method of the WP_Object_Cache
instance stored in the global variable $wp_object_cache
.
function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {
global $wp_object_cache;
return $wp_object_cache->add( $key, $data, $group, (int) $expire );
}
Cache functions available in WordPress
The official documentation lists some of the most important cache functions and I am transcribing them here. WordPress’s core itself uses them in several places but you can call them too in your theme or plugin.
wp_cache_add( $key, $data, $group = '', $expire = 0 ) // If exists, do not overwrite
wp_cache_set( $key, $data, $group = '', $expire = 0 ) // If exists, overwrite
wp_cache_replace( $key, $data, $group, $expire ) // If does not exist, do nothing
wp_cache_get( $key, $group = '', $force = false, $found = null )
wp_cache_delete( $key, $group = '' )
wp_cache_flush()
Key, value, group, and expiration
Most of the wp_cache_*
functions use four parameters:
- Key: the identifier for the object stored in cache. The post ID, for example.
- Value: the object stored in cache. In our example, the post.
- Group (optional): You can optionally group values. Previously, it was used simply to organize things but coming in WordPress 6.1, there will be a new
wp_cache_flush_group
function to flush all objects of a certain group. You will be able, for example, to delete just keys of a certain plugin. - Expiration (optional): for how long a cached value should be considered valid. Let’s give it a closer look now.
Expiration
When we talk about cache, one of the most important concepts is expiration. Is the information we have still valid? This is a key part of any cache implementation, including WordPress Object Cache.
So, how do we decide for how long a cached object should be considered valid?
Event-driven
Let’s say you are caching the list of posts of the biggest content. You get all the posts, apply the the_content
filter in all of them, and check how many characters they have. Pretty intensive, right? This deserves to be cached.
What can change the result of this process? Only if a post is created, deleted, or edited, right?
In this case, our cache does not need to expire. It is enough to invalidate the result we have using a related hook.
Time-driven
If what you are caching comes, for example, from something external like an API, the event that invalidates the cache is not under our control. In this case, we need to check the results from time to time in the external API and update the value we have stored.
Transients API and Object Cache
If you read the post or watched the YouTube video about the WordPress Transients API, the central idea here is not new: a key-value pair that avoids a long process is the essential idea behind both things. In fact, in a certain aspect, it is indeed the same thing. Look at the implementation of the get_transient
function:
function get_transient( $transient ) {
if ( wp_using_ext_object_cache() || wp_installing() ) {
$value = wp_cache_get( $transient, 'transient' );
} else {
...
}
...
}
Basically, if you are using an external mechanism for Object Caching, the transient will be stored there. Otherwise, the regular database will be used. Interesting, huh? Make sure to check that content too!