Using the WordPress transients API

Your website’s speed matters a lot, because when your site is slow, your visitors will quickly go away and look for a competitors site that doesn’t waste their time. But computers are fast. Very fast. Yet, there are operations that are computationally intensive, network latency that increases access times, or complex database queries that can bring a even a powerful server to its knees. In such cases, a level of caching should be implemented where possible, so that a stored, pre-computed result is served to your visitors. When will this cache get refreshed is a matter of use-case, but as a general rule of thumb, it should be updated as rarely as possible.

WordPress provides us with two distinct but similar APIs that help us implement caching. One one hand there’s the Cache API that provides us with a set of straightforward CRUD functions to easily implement caching, but doesn’t offer persistence out of the box. On the other hand, there is the Transients API, a set of functions that utilizes the Cache API under the hood (so, it’s a super-set) but also provides persistence out of the box. The latter is the one we’re going to investigate in this tutorial.

transient
/ˈtranzɪənt/
adjective – lasting only for a short time; impermanent.

Introduction to Transients

Transients are, by definition, temporary. Based on this fact, we should never rely on a transient as if it’s holding a piece of information, but consider it as something that will speed things up, if it’s there. If not, we have to regenerate that piece of information. This makes using transients very easy, as we don’t have to think about caching as a separate system. We just code as we usually do, and wherever we could speed things up we’ll just employ transients.

Let’s explore their use using an example.

Let’s say I want to add an advertising text in the content of each post on the Olsen Light demo, that will say things like “Did you know that Olsen Light has been downloaded more than 160,000 times and has an average rating over 95% ?“. Obviously I’m not going to go on each post and write it manually; what if the information changes? What if I misspell something? You guess it, I’ll do it with a hook, but that’s not the point. It would be really neat if those numbers could update themselves. How? Well, WordPress.org provides an API that we can pull those numbers from.

But calling an API incurs an overhead, multiple requests can get us banned, and while we wait for a response from the API, our visitors are waiting as well. And we’ve already established that we don’t want them to wait!

Making an uncached call

I’ve created a simple plugin to wrap the requirements of the aforementioned problem description. All it does is append some text via the ‘the_content‘ filter, and for a little benchmark, start timing on the ‘wp_head‘ action and end (and print the time elapsed) on the ‘wp_footer‘ action.

<?php
/*
Plugin Name: Transients tutorial
Plugin URI:
Description: Transients tutorial code
Version: 1.0
Author: CSSIgniter
Author URI: https://www.cssigniter.com
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
*/

add_action( 'wp_head', 'tr_head' );
function tr_head() {
	$GLOBALS['tr_time_start'] = microtime( true );
}

add_action( 'wp_footer', 'tr_footer' );
function tr_footer() {
	$start = $GLOBALS['tr_time_start'];
	$end   = microtime( true );

	echo sprintf( '<p>Page generated in %0.2f seconds.</p>',
		$end - $start
	);
}

add_filter( 'the_content', 'tr_the_content' );
function tr_the_content( $content ) {
	return $content . '<p>Did you know that <a href="https://wordpress.org/themes/olsen-light">Olsen Light</a> has been downloaded over 160,000 times, and has an average rating of over 95% ?</p>';
}

Since this is a tutorial about Transients and not benchmarking, I’m only counting the execution time from roughly the beginning to the end of the template hierarchy; this doesn’t consider the time WordPress took to load its files, themes, plugins, etc. They are irrelevant to this tutorial. I’m also printing the time elapsed with two decimal places. They are more than enough to demonstrate the point. Activating the plugin, we can see at the end of a post’s content the hardcoded text.

Also, at the very bottom of the page, we can see the time elapsed

Not very scientific, but it’ll do.

Each time the page is refreshed, a different number appears. Mine ranged from about 0.35 to 0.50 seconds. This is normal as WordPress might be doing background stuff, or the computer’s load might be different (which might be caused from a myriad of processes, from the operating system itself, to the applications and services currently running).

Now that we’ve established a speed to compare our future tests to, let’s go ahead and make the text dynamic, by querying the API and incorporating its values on our string.

add_filter( 'the_content', 'tr_the_content' );
function tr_the_content( $content ) {
	return $content . tr_get_text();
}

function tr_get_text() {
	$data = tr_fetch_data();

	if ( empty( $data ) ) {
		return '';
	}

	$text = sprintf( '<p>Did you know that <a href="%1$s">%2$s</a> has been downloaded %3$d times, and has an average rating of %4$d&percnt; ?</p>',
		$data->homepage,
		$data->name,
		$data->downloaded,
		$data->rating
	);

	return $text;
}

function tr_fetch_data() {
	$url = 'http://api.wordpress.org/themes/info/1.1/?action=theme_information&request[slug]=olsen-light';
	$response = wp_safe_remote_get( $url );

	if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
		$body = wp_remote_retrieve_body( $response );
		$json = json_decode( $body );
		if ( ! is_null( $json ) ) {
			return $json;
		}
	}

	return false;
}

To quickly explain what’s happening, we’ve replaced the hard-coded string with a call to tr_get_text() in order to keep the logic of the hooked tr_the_content() function simple. tr_get_text() calls tr_fetch_data() which it expects to be an object, and if it is, it sprintf()‘s the homepage (the theme’s URL), name (the theme’s name), downloaded (the number of download times) and rating (average rating percentage) attributes into the string.

Now, the tr_fetch_data() function simply makes a request to the API, and if successful, it decodes the JSON string the server responded with, and returns the decoded object. If you don’t know what the functions wp_safe_remote_get(), wp_remote_retrieve_response_code() and wp_remote_retrieve_body() do, you might want to take a look at the WordPress HTTP API.

Now the text is changed when we refresh the page. We now have exact numbers.

Did you know that Olsen Light has been downloaded 166104 times, and has an average rating of 96% ?

Also the elapsed time has changed. It now ranges from 0.95 to 1.6 seconds, just from a single remote request! This is bad, this is slow, and this is unacceptable!

Caching the call

It’s time to use transients to cache the call. Download numbers and ratings are not extremely important, so it’s acceptable if they only update -say- once per day.

function tr_get_text() {
	$data = get_transient( 'tr_theme_info' );

	if ( false === $data ) {
		$data = tr_fetch_data();

		set_transient( 'tr_theme_info', $data, DAY_IN_SECONDS );
	}

	if ( empty( $data ) ) {
		return '';
	}

	$text = sprintf( '<p>Did you know that <a href="%1$s">%2$s</a> has been downloaded %3$d times, and has an average rating of %4$d&percnt; ?</p>',
		$data->homepage,
		$data->name,
		$data->downloaded,
		$data->rating
	);

	return $text;
}

If you inspect the tr_get_text() function, you’ll see that we’ve only wrapped the $data = tr_fetch_data(); line with a bit of code. That’s all it takes.

The get_transient() call on line two, tries to retrieve the information stored under the key ‘tr_theme_info‘. If the key doesn’t exist, or the transient has expired, it returns false (more on expiration in a moment). On either case, we have no data, so we need to retrieve it. As we already said, we do that with the tr_fetch_data() call, so that’s what we do inside the if block, and once we have a $data object, we store it using the set_transient() function, under the ‘tr_theme_info‘ key, with an expiration of one day (in seconds).

That’s it!

If we now refresh the page, the first call should still take a bit longer as it needs to query the API and store the response, but page refreshes after this one should be considerably faster. Let’s see.

My first page refresh took 1.18 seconds. My second refresh took 0.40 seconds. My third refresh took 0.38 seconds.

Yeap! It works as expected! By adding literally 3 lines of code, we managed to shave off a whole seconds from each page refresh. That’s not a simple second. That’s a second multiplied by the daily pageviews our (and your) server gets. If you get 10,000 pageviews per day, it’s more than 2 hours wasted of your server waiting for the API to respond. The numbers quickly add up.

Here is the full code in case you’re looking for a quick copy-paste:

<?php
/*
Plugin Name: Transients tutorial
Plugin URI:
Description: Transients tutorial code
Version: 1.0
Author: CSSIgniter
Author URI: https://www.cssigniter.com
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
*/

add_action( 'wp_head', 'tr_head' );
function tr_head() {
	$GLOBALS['tr_time_start'] = microtime( true );
}

add_action( 'wp_footer', 'tr_footer' );
function tr_footer() {
	$start = $GLOBALS['tr_time_start'];
	$end   = microtime( true );

	echo sprintf( '<p>Page generated in %0.2f seconds.</p>',
		$end - $start
	);
}

add_filter( 'the_content', 'tr_the_content' );
function tr_the_content( $content ) {
	return $content . tr_get_text();
}

function tr_get_text() {
	$data = get_transient( 'tr_theme_info' );

	if ( false === $data ) {
		$data = tr_fetch_data();

		set_transient( 'tr_theme_info', $data, DAY_IN_SECONDS );
	}

	if ( empty( $data ) ) {
		return '';
	}

	$text = sprintf( '<p>Did you know that <a href="%1$s">%2$s</a> has been downloaded %3$d times, and has an average rating of %4$d&percnt; ?</p>',
		$data->homepage,
		$data->name,
		$data->downloaded,
		$data->rating
	);

	return $text;
}

function tr_fetch_data() {
	$url = 'http://api.wordpress.org/themes/info/1.1/?action=theme_information&request[slug]=olsen-light';
	$response = wp_safe_remote_get( $url );

	if ( 200 === wp_remote_retrieve_response_code( $response ) ) {
		$body = wp_remote_retrieve_body( $response );
		$json = json_decode( $body );
		if ( ! is_null( $json ) ) {
			return $json;
		}
	}

	return false;
}
transients-tutorial.php

Further considerations

First of all and for the sake of completeness, let me just mention that there’s a function to delete a previously set transient, named delete_transient(). Practically though, if you need transients that expire based on time, you are almost never going to use it, not unless you distribute a theme or plugin and you need to clean up after yourself on deactivation/uninstallation, or you need fresh data during development.

Second, make sure you store in a transient only the data you need. In the tutorial’s example, we could have (or should have) stored the resulting string into the transient. I chose to store the json response however, so that the reader can easily play around with the rest of the API’s properties. Had I included this code in an actual theme/plugin, I would have stored just the final “Did you know…” string.

Development

While you are developing your theme/plugin and you’re still debugging, it would be sane to set the expiration to something low, such as a few minutes or seconds, so that in case you realize that you need fresh data, you only have to have for a few minutes. If however, you accidentally set the expiration to a day or more, but you need fresh data right now, you have two choices:

  1. Do a delete_transient() right before the get_transient() call. There will be nothing to get, forcing the if block to execute.
  2. Open up your database on the wp_options table, and order the option_name column ascending. Each transient usually gets two entries in the table, one named ‘_transient_{your_key}‘ and another one named ‘_transient_timeout_{your_key}‘, where {you_key} is the transient key we provided, e.g. ‘tr_theme_info‘ (curly braces used as separators, not actually included in the name).

The ‘_transient_timeout_{your_key}‘ entries hold the expiration timestamp of the transient. You might find that some transients don’t have a respective _timeout_ entry though. This is because you can have non-expiring transients. Just pass 0 instead of a number of seconds to set_transient() and the underlying values will never expire.

Non-expiring transients

Non-expiring transients are very useful when cache invalidation (i.e. the deletion of the transient’s value) shouldn’t happen based on time, but rather based on any other condition. For example, if we only wanted to update the example’s text whenever a post was changed, we would set the expiration to 0 and and hook on the save_post action where we would make a call to delete_transient(). The tutorial’s example text isn’t a very good use-case for what I just described, so consider perhaps, counting how many revisions a post has and displaying it to the user. The number of revisions only changes whenever a post is saved, so invalidating our transient every hour, doesn’t make any sense at all.

Persistence

WordPress transients use the database by default to store their values. This is a last resort however, and they will happily work with whatever object storage system you choose to implement. Systems like memcached and redis are very popular as they store information in-memory, providing a huge speedup. With this as fact, you can’t expect transients to be stored as options in the database, as they may live in a totally different server. And being temporary by their very nature, they may not be even there at all.

Criticism

Many WordPress-related people/blogs/magazines, unfortunately hold the idea that transients somehow pollute your database and are making it slow, and propose using plugins that regularly delete them. This however is far from the truth, in fact, quite the opposite. We just showed that transients are there to speed things up. Each time they get deleted, they need to be re-generated, causing seconds of unnecessary delay. Sure, if you frequently switch themes and plugins, you might have a few leftover transients, but the mere existence of theme doesn’t cause any noticeable delays. Modern database systems (such as MySQL that WordPress uses) are designed to handle millions of records before their performance degrades.

Conclusion

Transients are a great feature of WordPress that allow us to easily cache data and reduce server load. The fact that the whole Transients API is just three functions in total, makes it one of the most digestible and easy to remember WordPress APIs.

So, what do you think? Do you have any cool uses for the Transients API? Let us know in the comments.

Subscribe to our newsletter.

Get fresh WordPress content straight into your inbox. We hate spam more than you do.

3 comments

  1. Excellent post Anastis. Not everyone is aware Transient uses an object storage like memcache and redis when they exist.

    Armed with the above knowledge, I stopped using wp_cache_* functions.

  2. Sebastian Gärtner says:

    Great summary.

    I was using the transient API lately for “caching” API responses – similar to your use case.

    But then I also wanted to make things work with a caching system activated.

    Transients only really have a benefit on pages that aren’t / can’t be cached completely, right?

    I solved that by:
    – having a function for calling the API
    – a function that calls that function – only if the transient is not set anymore (time to life: 1 day)
    – a shortcode that displays whatever the transient-or-fresh function returns
    – a js function that checks if the checksum of the cached shortcode is still up to date – if not, that get the updated HTML, replace it and clear the transient while doing that

    I guess the best would be, to have the still-up-to-date-ajax-call-handler also clearing the cache for that page, but as I didn’t know what caching plugin might be used I guess I am not able to trigger a page refresh.

    I am not sure, if my post is clear enough :)

    My question and goal is:
    How to provide changing data from an API in a fast and reliable way even if the page is cached :)

    1. Anastis Sourgoutsidis says:

      Don’t think of transients as something related to complete page caching. While related, it’s really disconnected.
      Your full page cache may need to be invalidated every few minutes (e.g. because new comments are approved) but the temperature on the site’s header only needs to update every 3 hours. In this case the temperature can (and will be) included in the full page cache, but the API will get queried every time the full page cache is cleared, which is useless really. The temperature in this case is a good candidate for transients.

      Not sure what/why/how your js function does what it does, but it sounds kinda strange. What checksum does it check? How it determines if it’s up to date? If it’s time-based, then you’ve just replicated what transients do by themselves. If you’re regenerating something just for checking, then you’re not actually avoiding any processing, you just defer it.

      Now, if you need to clear you full page cache when your transients expire… well, that will need some custom code, depending on your caching plugin and your requirements.

Leave a Reply

Your email address will not be published. Required fields are marked *