Speed Up Your Plugin With the WordPress Object Cache

Posted on:

Behind the scenes, WordPress has an incredibly useful set of functions that is a staple in what ensures it runs as quickly as possible. These functions control WordPress' caching system, and it's called the WordPress object cache.

These functions allow WordPress to do some really cool things, such as reduce the number of queries it runs. we can use these functions in our own plugins to allow our plugin to bypass re-running certain functions in our code. This post will go into how you can use the WordPress object cache for your own needs, as well as a few other ways to create your own methods to cache items.

What Is the WordPress Object Cache?

Simply put, the WordPress object cache is a registry of arbitrary data that is stored, and accessed whenever you need it. Just like most other registries, this cache gets built on the fly on every request to WordPress. In-other words, the data will only be accessible from the cache during the same request in which the cache was stored.

WordPress uses the cache extensively in database queries. After a database query is made, WordPress saves the data the data to the cache so it can be re-used later without running another database query. This is why you can run things like get_post or get_the_id as many times as you want, and WordPress doesn't need to run another database query to fetch the data - it stores the data in the object cache and fetches it from there instead. This is much faster than running a database query.

This is also one of the biggest reasons why I like to use BerlinDB for my custom database tables instead of just setting up my own manually. Not only does it feel like you're working with a WordPress database, it also caches like WordPress, too.

The object cache isn't reserved for database queries. There are plenty of other opportunities to use caching to speed up your WordPress plugin. Let's dig into how.

How To Use It

WordPress includes three key functions that you can use to manipulate the cache:

  1. wp_cache_add - Allows you to add to the cache.
  2. wp_cache_delete - Allows you to delete the item from the cache.
  3. wp_cache_get - Allows you to get the cached value. Returns false if the cached item doesn't exist (which drives me nuts

Most of the time, you use wp_cache_get to see if the cached item exists, and if it doesn't, calculate the value, and add it to the cache for next-time. Kind-of like this:

{
  "name": "acf/snippet",
  "attributes": {
    "id": "block_6105c744ded38",
    "name": "acf/snippet",
    "data": {
      "language": "php",
      "_language": "field_60d7f32b1b540",
      "code": "function slow_cached_task_example(){\r\n\r\n   $result = wp_cache_get( 'slow_task_example_unique_key' );\r\n\r\n   // If the cache could not be found, do the thing.\r\n   if(false === $result){\r\n      // Do an expensive, time-consuming thing.\r\n      $result = [];\r\n\r\n      for($i = 0; $i < 100; $i++){\r\n         $result[] = 'Appended item ' . $i;\r\n      }\r\n\r\n      // Then add that result to the cache\r\n      wp_cache_add( 'slow_task_example_unique_key', $result );  \r\n   }\r\n\r\n   // Return the result\r\n   return $result;\r\n}",
      "_code": "field_60d7f36c1b541"
    },
    "align": "",
    "mode": "edit"
  },
  "innerBlocks": []
}

The key here is that the key must be unique, otherwise you would end up getting the wrong cached item, and that could be very bad. The example above is a really simple execution of this, but what happens if we made this less-trivial, and added some arguments to actually change the result? What if we made it so that you could specify the number of times the loop runs? That would make it possible for the cached result to change, and would ruin our cache if the value was different next time. To get around this, it's best to use the arguments to generate a cache key, like this:

{
  "name": "acf/snippet",
  "attributes": {
    "id": "block_6105c751ded39",
    "name": "acf/snippet",
    "data": {
      "language": "php",
      "_language": "field_60d7f32b1b540",
      "code": "function slow_cached_task_example( $number_of_items = 1000 ){\r\n\r\n   // Append $number_of_items to the end of the key.\r\n   $cache_key = 'slow_task_example_unique_key_' . $number_of_items;\r\n\r\n   $result = wp_cache_get( $cache_key );\r\n\r\n   // If the cache could not be found, do the thing.\r\n   if(false === $result){\r\n      // Do an expensive, time-consuming thing.\r\n      $result = [];\r\n\r\n      for($i = 0; $i < $number_of_items; $i++){\r\n         $result[] = 'Appended item ' . $i;\r\n      }\r\n\r\n      // Then add that result to the cache\r\n      wp_cache_add( $cache_key, $result );  \r\n   }\r\n\r\n   // Return the result\r\n   return $result;\r\n}",
      "_code": "field_60d7f36c1b541"
    },
    "align": "",
    "mode": "edit"
  },
  "innerBlocks": []
}

When Should You Use It?

The object cache is worth considering any time you have a slow function that will be called frequently enough to justify taking the time to generate, and check the cache for the item. Look at the code above - there is extra time and effort used to figure out if the item is in the cache, and these are very basic examples. If you have to do something like sort the arguments, or convert the data to a hash, you might end up spending more time checking the hash than actually calculating the value. To check this, I usually do a benchmark check to see how many times my function has to run to make the effort spent caching the data worth it.

Caching Without The Object Cache

It's pretty common to cache data without the object cache, too. This is usually done as a getter method for a property in a class. It's the same idea, but instead of checking against the cache, you check against a class property. This is a slightly different pattern, than using the object cache because you don't have to worry about collisions with other keys. The disadvantage is that you can't take advantage of the object cache should two different classes generate the same result. Again, if you're not sure if you should use object cache, a class-based cache, or no cache at all - the best way to check is by doing some benchmarking.

Let's take the function above, and turn it into a class:

{
  "name": "acf/snippet",
  "attributes": {
    "id": "block_6105c76bded3a",
    "name": "acf/snippet",
    "data": {
      "language": "php",
      "_language": "field_60d7f32b1b540",
      "code": "class Slow_Task_Example{\r\n\r\n    protected $slow_task_result;\r\n\r\n    public function __construct( $number_of_times = 100 ){\r\n       $this->number_of_times = (int) $number_of_times;\r\n    }\r\n\r\n    public function get_slow_task_result(){\r\n\r\n        // If the slow task result is not an array, make it one.\r\n        if( ! is_array( $this->slow_task_result ) ){\r\n               // Do an expensive, time-consuming thing.\r\n               $this->slow_task_result = [];\r\n\r\n               for($i = 0; $i < $this->number_of_times; $i++){\r\n                  $this->slow_task_result[] = 'Appended item ' . $i;\r\n               }\r\n            }\r\n\r\n            // Return the result\r\n            return $this->slow_task_result;\r\n        }\r\n}",
      "_code": "field_60d7f36c1b541"
    },
    "align": "",
    "mode": "edit"
  },
  "innerBlocks": []
}

This caches the value so when get_slow_task_result is ran again it will simply return Slow_Task_Example::slow_task_result, much like how the object cache works. In many cases, this is probably good-enough, especially if you have cached tasks that aren't slow enough to justify creating the key. The difference is what happens when you have two instances that are identical? They would store the value in their own class, and even though the result is the same, the function would have ran twice.

{
  "name": "acf/snippet",
  "attributes": {
    "id": "block_6105c774ded3b",
    "name": "acf/snippet",
    "data": {
      "language": "php",
      "_language": "field_60d7f32b1b540",
      "code": "$example_1 = new Slow_Task_Example(1000);\r\n$example_2 = new Slow_Task_Example(1000);\r\n\r\n// Would execute the loop since the cache is not set yet.\r\n$example_1->get_slow_task_result();\r\n\r\n// Would execute the loop since example_2's instance does not have the cache set yet.\r\n$example_2->get_slow_task_result();",
      "_code": "field_60d7f36c1b541"
    },
    "align": "",
    "mode": "edit"
  },
  "innerBlocks": []
}

I like to use getter methods with caching baked-into classes in cases where I'm not doing something that's slow, but I still don't want to run through the function every time that's called. It's a good solution for that

Conclusion

In the right conditions, the WordPress object cache can improve your plugin's performance, without adding too much complexity to your plugin in the process. Learn how to use them, make sure your cache key is unique, and benchmark to confirm that it's actually worth caching, and you'll find your plugin doesn't work nearly as hard to get the same result.