2

I'm working at a WordPress theme. Since I need to display featured posts, related posts, some widgets with recent posts and so on, I need to use multiple custom loops. Because of this, the number of database queries have also increased.

In an attempt to optimize the theme for better performance I came across http://codex.wordpress.org/Transients_API , which seems like a good way to cache the loops that I don't need to be updated to every page reload.

So far so good, I used transient for wp_nav_menu and it worked as it should. I managed to decrease a bit the number of db queries.

The problem occured when I tried to use the transient for custom loops.

The transient is saved. I can get the transient value. The only problem is that the number of the database queries seems to be significantly higher than when using WP_Query without a transient.

Here is my custom loop:

if( false === ( $loop = get_transient('featured') ) ) {
    $loop = new WP_Query( array( 'posts_per_page' => 20 ) );
    set_transient('featured', $loop, 60 * MINUTE_IN_SECONDS);
}

if( $loop->have_posts() ) :

    while( $loop->have_posts() ) : $loop->the_post();

        the_title();
        the_post_thumbnail('thumb');

    endwhile;

endif;
wp_reset_postdata();

The code used to display the number of database queries and execution time is the following:

<?php echo get_num_queries(); ?> queries in <?php timer_stop(1); ?> seconds.

Using a query debug plugin, I see that update_meta_cache() generates a lot of queries.

I can't explain why the number of database queries increases instead of decreasing and why this worked for menues, but not for custom loops. Maybe I missed something. Any help is appreciated.

  • I'm glad to hear this, I'll wait your answer. Thanks for your time. – Marius Sep 8 '14 at 11:46
2

I have actually worked on a post yesterday (check it out here) and was hit by the same problem. I'm also new to the Transient API, never actually worked with it :-).

The problem here is that for a specific loop you need to use a certain value outside of your transient. You are simply storing the wrong values, and because of this, you are getting these overload of db hits.

To explain, here is part of the code from the answer I've referred to. I need to create a list of categories with post titles belonging to these categories. To accomplish this, I run my custom query and then use the value of $q to create my list

    $args = array( 
        'posts_per_page' => -1
    );

    $query = new WP_Query($args); 

    $q = array();

    while ( $query->have_posts() ) { 

        $query->the_post(); 

        $a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';

        $categories = get_the_category();

        foreach ( $categories as $key=>$category ) {

            $b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';    

        }

        $q[$b][] = $a; // Create an array with the category names and post titles
    }


    /* Restore original Post Data */
    wp_reset_postdata();

This gives me 6 queries in 0.06348 seconds.

OK, if you look at what the example from the codex, you should put your new WP_Query result in a transient. If I do that, the results is totally haywire

<?php
if( false === ( $query = get_transient('custom_query') ) ) {
    $args = array( 
        'posts_per_page' => -1
    );

    $query = new WP_Query($args); 
 set_transient('custom_query', $query, 60 * MINUTE_IN_SECONDS);
}
    $q = array();

    while ( $query->have_posts() ) { 

        $query->the_post(); 

        $a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';

        $categories = get_the_category();

        foreach ( $categories as $key=>$category ) {

            $b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';    

        }

        $q[$b][] = $a; // Create an array with the category names and post titles
    }


    /* Restore original Post Data */
    wp_reset_postdata();


?>

With the code above, my results looks like this 31 queries in 0.19141 seconds. This is 25 queries more and takes about 0.13 seconds more. The reason for this is, for each post, Wordpress needs to revisit the db to retrieve the categories the post belongs to, as this is not stored in you transient. 25 extra queries is the amount of posts in my db. So, you see, you have actually wasted a lot of resources by storing the wrong info. I do not need the info from the new WP_Query, but the result/value from $q

So, to properly make use of transients, you have to store the correct values you are after. To do this, I need to add my complete query into a transient to make sure that it is only run once, and that is when the transient is created. After that, the custom query is not needed anymore and is redundant, the only thing that needs to be stored and that I'm after now is the value $q. So, this is how the query is altered to remove the custom query after the transient is created, and to save just the value of $q

Just to note, I've changed my transient names along the way just for testing purposes to show the times and queries. Also note that when creating your transient, you need to use the value (in this case $q) you need to save

 if ( false === ( $q = get_transient( 'category_list' ) ) ) {

    $args = array( 
        'posts_per_page' => -1
    );

    $query = new WP_Query($args); 

    $q = array();

    while ( $query->have_posts() ) { 

        $query->the_post(); 

        $a = '<a href="'. get_permalink() .'">' . get_the_title() .'</a>';

        $categories = get_the_category();

        foreach ( $categories as $key=>$category ) {

            $b = '<a href="' . get_category_link( $category ) . '">' . $category->name . '</a>';    

        }

        $q[$b][] = $a; // Create an array with the category names and post titles
    }


    /* Restore original Post Data */
    wp_reset_postdata();

set_transient( 'category_list', $q, 12 * HOUR_IN_SECONDS );
}

This give me 2 queries in 0.00195 seconds. You see how I have now effectively made use of transients. $q which is an array of many values` is now available to create my list, and it only cost me 2 db hits to accomplish my goal

I hope this all makes sense

  • Thank you Pieter Goosen for this great, well explained answer. – Marius Sep 8 '14 at 12:50
0

Your code has 3 problems

  1. you use one DB query to save another DB query. You query a transient value instead of the post table which might or might not be faster but you don't save the penalty of communicating with the DB.

  2. You should not store objects in transients or options. According to the documentation values there are supposed to be scalars only which might be the reason why get_transient fails at your code. One of the important parts of what wp_query does that you skip by that which might influence the performance of the rest of your code is cache priming by storing the returned post objects in the cache (cache in this context is the short lived per session cache)

  3. the right way to cache is to cache the generated HTML. Your code attempts to save one query but you will save 40 more if you cache the HTML

  • 1) I use a DB query to store another DB query because set_transient will be only executed once at a specific time interval. The same query will be then reused until the transient will expire. 2) I've used the example provided in codex: codex.wordpress.org/Transients_API#Complete_Example 3) The code does not attempt to save only one query. If you use a custom loop in WordPress to grab multiple posts, WordPress will generate a lot of queries. get_transient only uses 2 db queries. – Marius Sep 8 '14 at 13:09
  • "set_transient will be only executed once at a specific time interval." - the extra query is the get, and if you think that transients will hold a value till the expiry time then you don't understand the API as transients will store a value at most for that time. "I've used the example provided in codex:" - not the only place in which the codex speaks BS. Not only it is wrong to store an object in a DB in general, and initializing a wp_query like you do skips the cache priming part that wp_query does which might explain the extra queries. – Mark Kaplun Sep 8 '14 at 13:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.