Caching data in WordPress is easy. Caching data in WordPress in a good and performant way takes a bit more work. For instance, many developers commonly use the Transients API to cache data. As the lowest common denominator in caching, this is okay. It’ll get the job done, even on a $10/year shared hosting plan. But what we should do instead is leverage the WP_Object_Cache functions to provide more functionality and better features.
For instance, let’s say we want to cache the result of an external API request. One way we could do this would be this way:
Note: These examples are terrible, but I hope they get the point across!
function get_api( $value) {
$value = absint( $value );
$api_data = get_transient( 'example-api-data-' . $value );
if ( false === $api_data ) {
$api_data = file_get_contents( 'https://example.com/api/' . $value );
set_transient( 'example-api-data-' . $value, $api_data, HOUR_IN_SECONDS * 6 );
}
return json_decode( $api_data );
}
Code language: PHP (php)
What’s one way we could make this better by using the WP_Object_Cache functions? Well, what happens if the API data structure changes, and you need to invalidate every cache value? It would be pretty hard to know the exact transient keys that you’d need to clear, and clearing the entire cache is a bit too nuclear for this (but it would work). Instead, wp_cache_*()
could be used, which includes the ability to use a cache group that can be changed:
function get_api( $value) {
$value = absint( $value );
$cache_group = 'example-api-data';
$api_data = wp_cache_get( $value, $cache_group );
if ( false === $api_data ) {
$api_data = file_get_contents( 'https://example.com/api/' . $value );
wp_cache_set( $value, $api_data, $cache_group, HOUR_IN_SECONDS * 6 );
}
return json_decode( $api_data );
}
Code language: PHP (php)
With this, if we ever need to invalidate the cache for this API, we just need to change the $cache_group
value, and all cache requests will be new.
Another common theme I see is caching too much data. Let’s say you’re going to do a slow WP_Query, and want to cache the results for better performance:
function get_new_posts() {
$posts = wp_cache_get( 'new-posts' );
if ( false === $posts ) {
$posts = new WP_Query( 'posts_per_page=5000' );
wp_cache_set( 'new-posts', $posts );
}
return $posts;
}
Code language: PHP (php)
Sure, that’s fine and it’ll work but… the WP_Query
object is huge!
echo strlen( serialize( new WP_Query( 'posts_per_page=500' ) ) );
… 2,430,748
That’s 2.5 megs of data needing to be transferred out of cache on every pageload. If your cache is accessed across the network on another server, this introduces more delay as it has to transfer. Also, some caching solutions might put a limit on the size of an individual cache object–which means that an object like this might never be cached!
Instead, we can just grab the IDs of the posts, and do a second, much faster query:
function get_new_posts() {
$post_ids = wp_cache_get( 'new-posts' );
if ( false === $posts ) {
$post_ids = new WP_Query( 'posts_per_page=5000&fields=ids' );
wp_cache_set( 'new-posts', $posts->posts );
}
$posts = new WP_Query( [ 'post__in' => $post_ids ] );
return $posts;
}
Code language: PHP (php)
echo strlen( serialize( $posts->posts ) );
… only 88,838 bytes, that’s like a 96%-something difference!
I had a few more ideas for this post, but it’s been sitting as a draft forever and I don’t remember. It’s possible this topic might be revisited some day 🙂
Leave a Reply