Update 12/8/2014: I’m not going to tell you not to cache all the things. You should absolutely cache all the things. However Otto wrote up a convincing argument about why you shouldn’t cache all the things using transients in the comments. So, when you’re done reading this post, go check the comments and see why what I’m suggesting is a bad idea. 😀
Let’s talk about transients.
Maybe you’ve been to a Wordcamp. Maybe you’ve been to a couple. Maybe you’ve never actually attended a Wordcamp but you read a lot of stuff about WordPress and how to make your site faster. Invariably, when discussing how to speed up your site, the issue of caching will come up and then things start to devolve into an unintelligible mass of arguments and conflicting opinions about what the best caching plugin is and strange words and phrases get thrown around like object cache and Pagespeed and SPDY and Batcache, Memcached and XCache…At this point, probably you just stop listening.
I’ll tell you a secret. My blog is slow as hell. I’m a WordPress developer and, yes, I will admit it, my site — running on a VPS — is slower than a Fail Whale eating a Tumblbeast for Thanksgiving. I went to all those “speed up your WordPress site” presentations — I even learned a couple things — and ultimately I’d come home, try a couple things, realize I needed to make complex server adjustments that were not intuitive, required me spending more time than I was willing to spend on, or required a level of control over the server and the configuration than I was allowed. I switched to Nginx for a while in an attempt to speed things up. I’d talk to tech support and do you know what they’d say? You have too many plugins installed. Try deactivating some plugins and see if the site speeds up.
At this point, I just stopped listening. I decided whatever, my site is slow, I’ll deal with it.
Caching
Caching is when you keep a copy of something so you aren’t requesting that thing when you load a webpage. In the olden days, this often meant building entire HTML files and storing them somewhere locally on the server and serving those files up as opposed to pulling content dynamically — in effect, making your dynamic WordPress site a static set of HTML documents. The problem with this is that producing these cached HTML files usually happened when someone loaded the page being cached, and if no cached file for that page existed, it would increase the load time for that page to create the cached file. Your browser keeps cached copies of webpages or resources (like CSS or JS files) from those pages you visit frequently, too, and this often means that when you go back to a site that has changed, you might need to flush your browser cache in order to see the live version that’s actually on the site.
Most caching plugins these days don’t serve up static HTML documents anymore (although some might) and, instead, they will use some form of server caching — possibly in conjunction with a Content Delivery Network — to cache large, memory-intensive database queries and serve large (or larger) javascript, CSS or image files from a fast server that’s not also responsible for delivering your website. This means that the caching plugin will often use the server’s own filesystem to store cached objects or data instead of dynamically pulling that data from the database when you load a page.
Now, somewhere along the way, you may have heard about object caching. Object caching is generally considered to be a good way of caching things just like caching things is generally considered to be a good thing to do. Object caching stores whatever it is that you are caching into your server’s memory, and only for the amount of time that you need it. It’s not good for persistent caching — when you are storing something for later — unless you are using a separate plugin, but on a high volume site, you might want to store a particular WordPress query into the object cache so all the data is right there when you actually try rendering the content on the page. The problem with object caching is your server needs to support it, and this requires that you know and understand your server configuration and are able to setup an environment where you could use the WordPress Object Cache class. In many cases and in some server environments, caching plugins can cause your site to run slower than it would without a caching plugin.
The ideal, though, at least for site speed, is having some kind of persistent cache — some way to save queries or pages that don’t change often (like less-actively-used tag archive pages, static WordPress pages or even single WordPress posts). Whether this is done by saving data to the server’s hard drive or using object caching — saving it to memory — doesn’t matter hugely, although it may come down to, again, what’s best for your environment. Many caching plugins will step in and use the WordPress Object Cache class for persistent caching, but you can’t assume that that’s the case, which limits the usefulness of these object caching functions.
Transients
Enter transients.
Plugin developers have been using transients for years and, let me tell you, transients are awesome. If you’ve ever felt the thrill as a Windows user hacking your registry to make one minor adjustment that saves you time or tweaks your system in a particularly rewarding way, you might relate to how cool transients are. Transients are like options you save to the WordPress options table but these settings can have an expiration. For example, if I’m a plugin developer, and I have a premium plugin, I might use a transient to store a user’s license key for a year. After the transient expires, the user might get a message that displays on the page that says it’s time for them to renew.
But an even cooler thing about transients is that they can be used to cache things like WordPress queries. A normal query you are running might look like this:
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; $args = array( 'post_type' => 'post', 'paged' => $paged, 'orderby' => 'date', 'order' => 'DESC', 'post_status' => 'publish', 'ignore_sticky_posts' => 1 ); $my_query = new WP_Query( $args );
I can cache this query in a transient like this:
if ( false === ( $my_query = get_transient( 'my_cached_query' ) ) ) : $paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1; $args = array( 'post_type' => 'paged', 'paged' => $paged, 'orderby' => 'date', 'order' => 'DESC', 'post_status' => 'publish', 'ignore_sticky_posts' => 1 ); $my_query = new WP_Query( $args ); set_transient( 'my_cached_query', $my_query, 12 * HOUR_IN_SECONDS ); endif;
It’s the exact same query. But before actually instantiating the WP_Query class, we’re checking against a transient. This transient is named “my_cached_query” and if it doesn’t exist — or if it’s expired — the $my_query = get_transient( 'my_cached_query' )
will return false and we’ll load the query normally. After instantiating the WP_Query with our arguments, we can save our query to the transient — remember it didn’t exist, that’s why we’re here — so it’ll be there next time, and I can give it an expiration — in this case I’m using 12 hours.
The next time this query is run, if it hasn’t expired yet, that conditional will return true — get_transient( 'my_cached_query' )
will exist, and so it will save to the $my_query
variable. The next part — which would be your normal WordPress loop — would work normally. During that twelve hours, anytime someone runs that query, it’ll it should* pull from the cached WP_Query object in the transient rather than running that query directly.
Caveats
Now, obviously there are problems with this method. We don’t want to serve a cached query when there’s new content, so you’ll need to add a save_post
hook to delete any transients that might be storing data that would be updated by the new content, otherwise you’d have a posts archive that doesn’t update except for once every 12 hours or so. And this is based on requests, it’s not run by a cron job, so 12 hours is somewhat relative to when the pages or queries or whatever it is that you are caching are actually executed — that’s when the transient would be checked and recreated if it had expired. And there’s also the fact that, as indicated with the * above, there’s a chance that the transient you set for 12 hours isn’t there when you expect it to be. Maybe there’s a plugin that’s flushing transients, in that case, you’d still be running the query every time.
On the other hand, if you are using a plugin that adds persistent object caching, “the transients functions will use the wp_cache functions” [Codex] added by the WordPress Object Cache class. In effect, the wp_cache_set
and wp_cache_get
functions are used in place the set_*
and get_transient
functions that you may have added in your code. And if there isn’t persistent caching set up on your site, transients will work normally and store in the WordPress database.
Transients are one of those things I never took time to really understand before and now, in retrospect, I wish I had. They are super easy to use, and once you do start using them, it’s easy to come up with a million and one possible uses for them. Even if you aren’t caching queries in transients, it’s still handy to learn the Transients API to temporarily store other bits of information that you might need later. It’s like a little sticky note on your computer reminding you to do something that you toss in the recycling after you’ve done whatever that note was reminding you to do.