WordPress Object Cache

WordPress Object Cache

Do you want an easy way to make your WordPress website faster and also level up your programming skills with WP? Object Cache might be the answer to both!

Would you like an easy way to make your WordPress site faster and, on top of that, give a boost to your programming skills with WP? Object Cache can be the answer to both!

Before Object Cache... What is cache?

Before we talk about Redis, we need to talk about Object Cache. And before we talk about Object Cache, you guessed it, let's briefly discuss cache in general.

Looking for definitions of cache on the internet, the common understanding seems to be something like the following:

Cache is an intermediary storage and quick-access location situated between the consumer and the primary storage, potentially saving a longer round trip.

Take a look at the example below. The first time the request goes to the end server, but the cache server stores a copy. The second time, the copy is served, saving the trip to the server.

With that in mind, we can consider the various trips required to open a website and the multiple intermediaries we can introduce to save trips:

  • Browser cache: Between your browser and the website server, if there is a valid copy of the website on your computer, we save a trip. In fact, the entire trip in this case.
  • CDN: If your website goes through a CDN like Cloudflare, and if the content stored in the CDN is still valid, there is no need to request a new copy. Simply sending the old one will suffice, saving a trip.
  • Cache do WP: On your WordPress website, if a post has already been fetched from the database once and the information we have in memory is still valid, why go back to the database again?

There are many other trips where we can add an intermediary location to save a version of the information. These are just a few examples. Oh, and the pronunciation is the same as "CASH," not "cachet" or anything else.

The Object Cache in WordPress.

Memory access is always faster than disk access. With that in mind, if we have already fetched a post into memory at the beginning of processing a page, there is no need to go back to the database to retrieve it.

The issue is that, in the default implementation of WordPress, the post will only remain in memory until the end of processing that particular page. Do we need it again if the user reloads the page? We will have to go back to the database.

MySQL is smart enough to recognize if information has been recently requested and make it available more quickly, but it is still not as fast. Ultimately, if we are going to MySQL, we are making a longer trip anyway.

By default, the post requested at the beginning of the process (or any other desired information) is stored using a class called WP_Object_Cache. Its main purpose is to save trips to the database, and it functions essentially as a key-value pair. The problem, as you mentioned, is that all of this only lasts for one request. For the next request, we start all over again.

To "store" things in memory, you can use either Redis or Memcached.

Programs like Redis or Memcached are in-memory data stores that store information in memory. They can do more than that, especially Redis, but for our problem with WordPress, the Redis key-value storage in memory is all we really need.

Throughout the post, I will use Redis as an example, but the installation and usage with Memcached are quite similar.

What do I need to use Object Cache with Redis in WordPress?

The list of prerequisites to use WordPress Object Cache with Redis is quite straightforward:

  • A WordPress website.
  • Redis up and running on your infrastructure.
  • A WordPress plugin to connect WordPress to Redis.

Regarding the second item, the fastest way is to contact your hosting company. For instance, on Cloudways, you can simply click a button in the control panel to install Redis.

Redis Image 01

The Redis Object Cache plugin

Installing and configuring it is relatively simple. By default, it will attempt to connect to a Redis instance located on the local server (127.0.0.1) on port 6379, using Redis database number 0.

Redis Image

For the plugin to function properly, it needs to copy the object-cache.php file from its own includes folder to the wp-content folder of your WordPress installation. If, for any reason, such as directory permissions, it is not possible to copy the file automatically, you will need to copy it manually.

Even Redis with different sites on the same server

If you have two blogs, for example, you can share the same Redis installation. To prevent the stored values from conflicting between the two sites, you need to define define( 'WP_REDIS_DATABASE', 0 ); in one and define( 'WP_REDIS_DATABASE', 1 ); in the other.

The different configuration parameters from the default settings need to be added to the wp-config.php file of your site. The complete list of parameters can be found in the plugin's wiki.

How does the Object Cache plugin work?

In the wp_start_object_cache() function (excerpt below), executed at the beginning of the WordPress loading process, it detects the presence or absence of the drop-in file object-cache.php. If available, the file is loaded; otherwise, the default implementation takes effect. Then, the cache is instantiated.

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 object cache, use WP's default.
	if ( ! wp_using_ext_object_cache() ) {
		require_once ABSPATH . WPINC . '/cache.php';
 	}
 
	require_once ABSPATH . WPINC . '/cache-compat.php';
 
	...
	wp_cache_init();
	...
}

The content of the Redis Object Cache drop-in file object-cache.php can be seen here. It consists of an implementation of the WP_Object_Cache class (towards the end of the file) and various functions that utilize it, such as wp_cache_add, wp_cache_get, and wp_cache_delete.

Taking a code dive

If you need code to understand things better, I understand. I'm the same way. Here's a portion of the path 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).

And 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' );
		}
		...
	}
}

First, it checks if the cache exists (wp_cache_get). If it exists, it is used; otherwise, the post is fetched and saved in the cache (wp_cache_add).

And just to take a look at the content of the wp_cache_add function, it is simply a 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 );
}

The available cache functions in WordPress.

The official documentation lists some of the most important cache functions, and I'm transcribing them here. The core itself uses them in various places, but you can also call them in your theme or plugin

// If exists, does not overwrite
wp_cache_add( $key, $data, $group = '', $expire = 0 )
// If exists, overwrites
wp_cache_set( $key, $data, $group = '', $expire = 0 )
// If does not exist, does nothing
wp_cache_replace( $key, $data, $group, $expire )
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 of the object stored in the cache. For example, the post ID.

  • Value: The actual object being stored in the cache. The post itself.

  • Group (optional): The group is used to - you guessed it - group different keys together. It used to be more for organization purposes, but in WordPress 6.1, you'll be able to call the new function wp_cache_flush_group to delete all objects from a specific group, such as everything cached by a particular plugin.

  • Expiration (optional): How long the cache is considered valid. Let's take a closer look at this.

When it comes to caching, a crucial concept is expiration time. Is the cached information still valid? This is a key aspect of any caching implementation, including the Object Cache in WordPress.

So, how do we determine how long a cached object should be considered valid?

Based on an event

Let's say you're caching the list of posts with the most content. You retrieve all the posts, apply the "the_content" filter to each of them, and measure the number of characters in each. Quite resource-intensive, right? This deserves to be cached.

What can cause the result of this process to change? Only the creation of a new post or the editing of an existing post, correct?

In this case, our cache doesn't need to expire. We simply invalidate the result we have using a related hook, such as the creation of a new post or the editing of an existing post.

Based on time

If what you're caching comes, for example, from an external environment like an API, the event that invalidates the cache is not under our control. In this case, we need to periodically check the result from the external API and update the stored value accordingly.

Transients API and Object Cache

If you have read the post, the main idea here is not new: a key-value pair that avoids a time-consuming process is exactly the idea behind both. In fact, in a certain aspect, they are the same thing. Take a 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 Cache, the transient will be stored there. Otherwise, the regular database will be used. Interesting, isn't it? Take a look there!

Thank you for reading this far