Get an Expired Transient in WordPress: Good Idea or Crazy Talk?

I wonder if there’s an easy way to get an expired transient in WordPress? Now, for this to make sense I guess I should provide a little more context, so here it goes :)

The Transients API is an extremely easy way to cache parts of your WordPress code, that may be CPU/memory intensive, or rely on a third-party server and so on. A great example is grabbing a tweet from Twitter and caching it for a few minutes, so that we don’t query the Twitter API on every page load. So in theory:

if ( false === ( $tweet = get_transient( 'my_latest_tweet' ) ) ) {
    $tweet = get_my_latest_tweet(); // Queries the Twitter API
    set_transient( 'my_latest_tweet', $tweet, 60*60 );
}

Is a great way to cache my latest tweet for an hour. Perfect, but there’s a problem. The code above will work well for 60 minutes. When the transient expires, it will have to spend time again to fetch the tweet, thus impacting your page load time. Once every 60 minutes.

Suppose Twitter is being slow today and taking 2 seconds to respond back, and suppose I want to cache the tweet for 5 minutes, not an hour. This means that every 5 minutes, somebody will have to wait two extra seconds for my page to load. Suppose I’m querying the Flickr API, the Facebook API and fetching a few RSS feeds too, that will all add up to the page load time, to that unlucky person who visited my site, when the cache was expired. Bummer!

Is there a solution? I don’t know, but I can think of one — always serve cached data, even if it’s expired. That way visitors will never have to wait extra when you’re speaking to third party servers and APIs. I can think of a way to accomplish this by fetching the new data in a different request, transparent to the user.

Like an asynchronous request with jQuery (aka AJAX) to a special action that would revalidate expired cache. Crazy talk? Here’s the scenario:

  1. You visit your page when caches are empty, you don’t see the tweet.
  2. An async jQuery request is fired to the server which grabs the latest tweet and caches it, this happens behind your back.
  3. You refresh the page and you see the tweet from cache. Yey!
  4. Ten minutes have passed, cache is expired, but not trashed. You visit your page, you see the old tweet from (expired) cache.
  5. An async request in the back fetches a new tweet from Twitter and replaces the one expired.
  6. You refresh the page and you see the (new) tweet from cache. Yey!

So in steps 1, 3, 4 and 6 you’re always serving what’s in cache, whether it expired or not, even if it’s empty (first step) so you’re serving as fast as possible. Steps 2 and 5 happen behind your back, they don’t impact page load time, and they never return anything back. You won’t see them happening unless you’re looking at your Netwok tab in Chrome’s developer tools or Firebug.

Now, suppose Twitter is down. The visible steps are not impacted, because they return stored data, they don’t have to fetch it from Twitter. The hidden steps will fail, but your visitors will never know, since they’ll be still seeing the old tweet fetched some time earlier, right?

This can easily be done with the Options API but hey, transients are already a great tool for caching. Wouldn’t it be even easier if transients never actually expired? And a function to fetch such an expired transient, get_expired_transient perhaps? :)

Okay, that’s just off the top of my head, it’s quite late so, sorry if I’m totally on the wrong track. Let me know if there’s an easier solution, or maybe a caching plugin that already does this. Object caching can benefit too by the way. Share your thoughts in the comments section below!

About the author

Konstantin Kovshenin

WordPress Core Contributor, ex-Automattician, public speaker and consultant, enjoying life in Moscow. I blog about tech, WordPress and DevOps.

6 comments

    • Ryan, sounds fair, but the problem is that WordPress will eventually kill the transient as soon as I try to fetch it. I don’t want to kill the transient, unless I have valid data that can replace it, so what I’m talking about is a transient that has an expiration, but is never actually deleted without asking me first.

      I’ve been able ta achieve something very close to this with the Options API, and just like with transients, I have another option holding the expiry date. Now, when I’m fetching the option to output to the user, I don’t even check that timeout value, because I don’t care whether it’s expired or not, I just need to show what I have. When an AJAX request is fired though, I check the timeout value, and if it’s expired, I fetch new data, and only if the new data is valid (the API is not down, etc) I replace the outdated option and bump the expiry date.

      I’ll try to come up with more code that I can share soon. Thanks for your comment! :)

  • Why not to fetch the data for transients using WordPress Cron API and serve even expired transients? WP Cron tasks are run in background, so no one will suffer from slowpoke server responses.

    • Hey karevn, thanks for your comment! “Fetching” an expired transient is what actually kills it in the first place, but fetching it as an option instead won’t hurt. Unless we filter and short-circuit the get_transient function, which can be even more complicated. However, the idea to use the Cron API for cache busting is definitely interesting, we did have a chat about it with @SoulSeekah the other day, and seems like it’s much better than using something like XHR, which can create unnecessary load on the server, even with caching.

      Thanks for stopping by!

  • I think it’s not about dealing with expired transients mostly, but about solving the use-case. nothing prevents us from using options API directly or just write get_expired_transient function based on get_transient…

    • Karevn, good thinking, getting the transient without using get_transient is an option, and then getting the transient timeout without get_transient when checking whether to kill cache, everything sounds good, but it feels like we’re reinventing the Transient API :)