Speed Up Your Genesis Theme by Reducing Database Queries

WordPress ships with a bunch of nifty methods to improve performance and knowing when to use one of them could cut the number of database queries on your site by over 60%.

Post Thumbnail Caching

It has to do with the way post thumbnails are displayed in a loop.

By default, whenever get_the_post_thumbnail() is called in the main loop for the first time, WordPress looks up data for all thumbnails that are likely to appear and caches them. Otherwise, the thumbnail data for each post needs to be fetched on each iteration of the loop at a cost of two additional queries per thumbnail: One for the attachment and one for the attachment’s meta data.

So what exactly is the method? It’s a simple function, aptly named update_post_thumbnail_cache().

How It Works

When a new WP_Query is run, all of the selected posts and their metadata are pulled from the database and cached so that any time one of the posts or its metadata is referenced during the same request, it can be read from memory rather than hitting the database again.

Instead of fetching thumbnail info from the database each time it’s needed, WordPress can fetch data for all thumbnails at once and cache them the same way posts are cached, which is what happens when update_post_thumbnail_cache() is called.

It accepts a single, optional argument, which is the WP_Query object for the loop that should be cached. Since main loops are already cached, you would typically only need to use it with custom loops:

$custom_loop = new WP_Query( array(
    'post_type' => 'post',
) );

update_post_thumbnail_cache( $custom_loop );

What’s the Deal with Genesis?

Genesis has its own API method called genesis_get_image() to display post thumbnails and it doesn’t reference get_the_post_thumbnail(), so it doesn’t take advantage of WordPress’ default caching. The Genesis method does have other useful features, like providing a couple of fallback alternatives if a featured image hasn’t been set, but that’s really more of an edge case and there’s no reason it shouldn’t take advantage of the performance benefits provided by update_post_thumbnail_cache().

To do so, here’s a filter that will enable the caching, as well as add a slight enhancement to WP_Query. Activate the code below as a plugin or prefix it and include it directly in your theme:

* Plugin Name: Cache Post Thumbnails
* Description: Prime the post thumbnails cache for individual loops.
* Version: 1.0.0
* Author: Brady Vercher
* Author URI: http://www.blazersix.com/
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.html
* Prime post thumbnail cache.
* Automatically primes the cache for the main loop on the front page and
* archives. The cache can be activated for additional queries using the
* 'blazersix_cache_post_thumbnails' filter.
* Custom queries can also pass a 'blazersix_cache_post_thumbnails' flag via the
* query args to prime the cache.
* @since 1.0.0
* @param array $posts List of posts in the query.
* @param WP_Query $wp_query WP Query object.
* @return array
function blazersix_prime_post_thumbnails_cache( $posts, $wp_query ) {
// Prime the cache for the main front page and archive loops by default.
$is_main_archive_loop = $wp_query->is_main_query() && ! is_singular();
$do_prime_cache = apply_filters( 'blazersix_cache_post_thumbnails', $is_main_archive_loop );
if ( ! $do_prime_cache && ! $wp_query->get( 'blazersix_cache_post_thumbnails' ) ) {
return $posts;
update_post_thumbnail_cache( $wp_query );
return $posts;
add_action( 'the_posts', 'blazersix_prime_post_thumbnails_cache', 10, 2 );

That snippet will automatically cache post thumbnails in main loops that aren’t for singular posts. It also allows you to cache custom loops by passing an additional argument to WP_Query:

$custom_loop = new WP_Query( array(
    'post_type' => 'post',
    'blazersix_update_post_thumbnail_cache' => true,
) );

Checking Your Site

If you want to see how many queries each request on your site is generating, go download the excellent Query Monitor plugin by John Blackbourn.