Did you know that WordPress has a built-in caching system called transients? They are easy to use and extremely effective when used correctly. In this article I will demonstrate how we used the WP Transient API functions and made our own Seravo.com front page load on average over 300% faster.
Caching can sometimes be tricky because selecting good expiry time settings and doing cache invalidation requires a profound understanding of the whole website by the developer. However, if you are not doing any at all and don’t have any transient functions in use on your site, then you are surely missing out on some serious WordPress speed improvements.
The WordPress transient API: get_, set_ or delete_transient()
The WordPress transients API is extremely simple. There are only three functions: a setter, a getter and a deleter. With the setter you save something in the cache, and with a getter you fetch it. The setter takes tree parameters: the name of the cache (referred as ‘cache key’), the value/contents to store in the cache, and the expiry time in seconds. The getter is even simpler: you only give it the cache key as a parameter and it will return the value if found, or false if nothing was found. The deleter is used to invalidate keys, meaning they are deleted even before they would otherwise expire. Below is the classic example from the WordPress documentation showing a transient used to store a database query result, so that the potentially heavy database query does not need to execute all the time:
<?php
// Check for transient. If none, then execute WP_Query
if ( false === ( $featured = get_transient( 'foo_featured_posts' ) ) ) {
$featured = new WP_Query(
array(
'category' => 'featured',
'posts_per_page' => 5
));
// Put the results in a transient. Expire after 12 hours.
set_transient( 'foo_featured_posts', $featured, 12 * HOUR_IN_SECONDS );
} ?>
What to store in a transient?
Anything that is heavy to compute and where the result is valid for at least some time is worthwhile to store in a cache.
Case: Seravo.com front page
There are two preferred ways to analyze what is going on in the PHP code when a WordPress page load executes: XDebug or Tideways. On production sites Tideways is the only sensible option. A manually triggered trace on our Seravo.com front page yielded the waterfall graph below, which revealed where in the code most of the execution time was spent. The culprit was the template files that fetched a lot of custom post types from the database.
Below is the contents of the initial version of our frontpage.php WordPress theme template file. It was basically just loading a bunch of template partials, which themselves contained a bit of boilerplate code and some simple loops over custom post types. As the Tideways profiling revealed, producing those partials was fairly heavy on the database. As the front page is rather static, and basically only the section that lists our latest blogs changes, this was a good candidate for caching.
<?php
get_header();
get_template_part( 'partials/fp', 'hero' );
get_template_part( 'partials/fp', 'features' );
get_template_part( 'partials/fp', 'segment-section' );
get_template_part( 'partials/fp', 'products' );
get_template_part( 'partials/fp', 'customers' );
get_template_part( 'partials/fp', 'blog-section' );
get_template_part( 'partials/fp', 'cta-section' );
get_template_part( 'partials/tech', 'logos-section' );
get_footer();
Our approach was to make a wrapper function, that would fetch the template from the transient cache if found, and only if not found it would actually render the partial, store the output using the PHP output buffer and then echo out the partial’s contents that was either just generated or fetched from the cache. The inline code comments explain each step in detail.
<?php
/*
* Wrapper for get_template_part to get the ready-rendered
* template from the WP Transient cache is exists.
*
* Note! This should be only used for static pages that always
* have the same contents, or at least can be so for an hour
* (which is the current cache expiry time).
*/
function templateCache( $template_path, $template_name ) {
if ( cntrst()->helpers->is_mobile() ) {
$cache_key = 'seravo-partials-'. $template_name .'_mobile';
} else {
$cache_key = 'seravo-partials-'. $template_name;
}
if ( ! $output = get_transient( $cache_key ) ) {
ob_start();
get_template_part( $template_path, $template_name );
$output = ob_get_clean();
set_transient( $cache_key, $output, HOUR_IN_SECONDS );
}
echo $output;
}
get_header();
get_template_part( 'partials/fp', 'hero' );
templateCache( 'partials/fp', 'features' );
templateCache( 'partials/fp', 'segment-section' );
templateCache( 'partials/fp', 'products' );
templateCache( 'partials/fp', 'customers' );
templateCache( 'partials/fp', 'blog-section' );
get_template_part( 'partials/fp', 'cta-section' );
get_template_part( 'partials/tech', 'logos-section' );
get_footer();
The result: down to 143 ms from 450 ms
A new manually triggered trace on Tideways validates that the optimization worked as intended. The segment-section
and customers
partials that were clearly visible in the previous trace have basically disappeared but the hero
and cta-section
we did not wrap in our cache wrapper function are visible.
Note that in these two pictures the scale is radically different. In the first Tideways waterfall view the scale is 0–450 milliseconds, while in the second one the span between left and right sides of the graph is only 143 milliseconds.
Running our own wp-speed-test tool from inside the server itself also verifies that the PHP of the front page is generated much faster than before:
Before:
seravocom@seravocom:~$ wp-speed-test
Testing speed URL https://seravo.com...
For an explanation of the different times, please see docs at https://curl.haxx.se/docs/manpage.html
URL TOTAL NAMELOOKUP CONNECT APPCONNECT PRETRANSFER STARTTRANSFER = AVG
https://seravo.com 0.405 0.060 0.061 0.064 0.064 0.403 0.405
https://seravo.com 0.380 0.000 0.000 0.000 0.000 0.379 0.392
https://seravo.com 0.313 0.000 0.000 0.000 0.000 0.312 0.366
https://seravo.com 0.330 0.000 0.000 0.000 0.000 0.328 0.357
https://seravo.com 0.340 0.000 0.000 0.000 0.000 0.339 0.353
https://seravo.com 0.331 0.000 0.000 0.000 0.000 0.330 0.350
After:
seravocom@seravocom:~$ wp-speed-test
Testing speed URL https://seravo.com...
For an explanation of the different times, please see docs at https://curl.haxx.se/docs/manpage.html
URL TOTAL NAMELOOKUP CONNECT APPCONNECT PRETRANSFER STARTTRANSFER = AVG
https://seravo.com 0.098 0.004 0.004 0.008 0.008 0.096 0.098
https://seravo.com 0.092 0.000 0.000 0.000 0.000 0.090 0.095
https://seravo.com 0.092 0.000 0.000 0.000 0.000 0.091 0.094
https://seravo.com 0.086 0.000 0.000 0.000 0.000 0.085 0.092
https://seravo.com 0.089 0.000 0.000 0.000 0.000 0.088 0.091
https://seravo.com 0.090 0.000 0.000 0.000 0.000 0.089 0.091
Thanks to Tideways, we don’t have to rely on a few manually made measurements before and after, as we can also look at the long-term multi-sample history that Tideways keeps. In this case the result was indeed drastic:
Use transients, and a server that has Redis and object cache enabled
We warmly encourage all developers to start using the WordPress transients feature to optimize their website for improved speed. To get the most out of it you should be running a server environment where the transients are stored in RAM memory. At Seravo all of our customers have the Redis server enabled by default and the benefit of using transients is at its highest.