Make WordPress Core

Changeset 36566

Timestamp:
02/17/2016 10:57:33 PM (8 years ago)
Author:
boonebgorges
Message:

More performance improvements to metadata lazyloading.

Comment and term meta lazyloading for WP_Query loops, introduced in 4.4,
depended on filter callback methods belonging to WP_Query objects. This meant
storing WP_Query objects in the $wp_filter global (via add_filter()),
requiring that PHP retain the objects in memory, even when the local variables
would typically be expunged during normal garbage collection. In cases where a
large number of WP_Query objects were instantiated on a single pageload,
and/or where the contents of the WP_Query objects were quite large, serious
performance issues could result.

We skirt this problem by moving metadata lazyloading out of WP_Query. The
new WP_Metadata_Lazyloader class acts as a lazyload queue. Query instances
register items whose metadata should be lazyloaded - such as post terms, or
comments - and a WP_Metadata_Lazyloader method will intercept comment and
term meta requests to perform the cache priming. Since WP_Metadata_Lazyloader
instances are far smaller than WP_Query (containing only object IDs), and
clean up after themselves far better than the previous WP_Query methods (bp
only running their callbacks a single time for a given set of queued objects),
the resource use is decreased dramatically.

See [36525] for an earlier step in this direction.

Props lpawlik, stevegrunwell, boonebgorges.
Fixes #35816.

Location:
trunk
Files:
1 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/comment-template.php

    r36527 r36566  
    13931393     */
    13941394    $wp_query->comments = apply_filters( 'comments_array', $comments_flat, $post->ID );
    1395 
    1396     // Set up lazy-loading for comment metadata.
    1397     add_action( 'get_comment_metadata', array( $wp_query, 'lazyload_comment_meta' ), 10, 2 );
    13981395
    13991396    $comments = &$wp_query->comments;
     
    20302027    if ( null === $r['reverse_top_level'] )
    20312028        $r['reverse_top_level'] = ( 'desc' == get_option('comment_order') );
     2029
     2030
    20322031
    20332032    if ( empty( $r['walker'] ) ) {
  • trunk/src/wp-includes/comment.php

    r36542 r36566  
    470470
    471471/**
     472
     473
     474
     475
     476
     477
     478
     479
     480
     481
     482
     483
     484
     485
     486
     487
     488
     489
     490
     491
     492
     493
     494
     495
    472496 * Sets the cookies used to store an unauthenticated commentator's identity. Typically used
    473497 * to recall previous comments by this commentator that are still held in moderation.
  • trunk/src/wp-includes/meta.php

    r36511 r36566  
    853853
    854854/**
     855
     856
     857
     858
     859
     860
     861
     862
     863
     864
     865
     866
     867
     868
     869
     870
     871
    855872 * Given a meta query, generates SQL clauses to be appended to a main query.
    856873 *
  • trunk/src/wp-includes/post.php

    r36498 r36566  
    59495949
    59505950/**
     5951
     5952
     5953
     5954
     5955
     5956
     5957
     5958
     5959
     5960
     5961
     5962
     5963
     5964
     5965
     5966
     5967
     5968
     5969
     5970
     5971
     5972
     5973
     5974
     5975
     5976
     5977
     5978
     5979
     5980
     5981
     5982
     5983
     5984
     5985
     5986
     5987
    59515988 * Update the custom taxonomies' term counts when a post's status is changed.
    59525989 *
  • trunk/src/wp-includes/query.php

    r36524 r36566  
    36063606            $this->posts = array_map( 'get_post', $this->posts );
    36073607
    3608 
    3609         if ( $q['update_post_term_cache'] ) {
    3610             add_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );
    3611         }
    3612 
    36133608        if ( ! $q['suppress_filters'] ) {
    36143609            /**
     
    37393734        // If comments have been fetched as part of the query, make sure comment meta lazy-loading is set up.
    37403735        if ( ! empty( $this->comments ) ) {
    3741             add_filter( 'get_comment_metadata', array( $this, 'lazyload_comment_meta' ), 10, 2 );
     3736            );
    37423737        }
    37433738
     
    37693764            $this->post_count = 0;
    37703765            $this->posts = array();
     3766
     3767
     3768
     3769
    37713770        }
    37723771
     
    48354834
    48364835    /**
    4837      * Lazy-loads termmeta for located posts.
    4838      *
    4839      * As a rule, term queries (`get_terms()` and `wp_get_object_terms()`) prime the metadata cache for matched
    4840      * terms by default. However, this can cause a slight performance penalty, especially when that metadata is
    4841      * not actually used. In the context of a `WP_Query` instance, we're able to avoid this potential penalty.
    4842      * `update_object_term_cache()`, called from `update_post_caches()`, does not 'update_term_meta_cache'.
    4843      * Instead, the first time `get_term_meta()` is called from within a `WP_Query` loop, the current method
    4844      * detects the fact, and then primes the metadata cache for all terms attached to all posts in the loop,
    4845      * with a single database query.
    4846      *
    4847      * This method is public so that it can be used as a filter callback. As a rule, there is no need to invoke it
    4848      * directly, from either inside or outside the `WP_Query` object.
     4836     * Lazyload term meta for posts in the loop.
    48494837     *
    48504838     * @since 4.4.0
    4851      * @access public
    4852      *
    4853      * @param mixed $check  The `$check` param passed from the 'get_term_metadata' hook.
    4854      * @param int  $term_id ID of the term whose metadata is being cached.
    4855      * @return mixed In order not to short-circuit `get_metadata()`. Generally, this is `null`, but it could be
    4856      *               another value if filtered by a plugin.
     4839     * @deprecated 4.5.0 See wp_queue_posts_for_term_meta_lazyload().
     4840     *
     4841     * @param mixed $check
     4842     * @param int   $term_id
     4843     * @return mixed
    48574844     */
    48584845    public function lazyload_term_meta( $check, $term_id ) {
    4859         // We can only lazyload if the entire post object is present.
    4860         $posts = array();
    4861         foreach ( $this->posts as $post ) {
    4862             if ( $post instanceof WP_Post ) {
    4863                 $posts[] = $post;
    4864             }
    4865         }
    4866 
    4867         if ( ! empty( $posts ) ) {
    4868             // Fetch cached term_ids for each post. Keyed by term_id for faster lookup.
    4869             $term_ids = array();
    4870             foreach ( $posts as $post ) {
    4871                 $taxonomies = get_object_taxonomies( $post->post_type );
    4872                 foreach ( $taxonomies as $taxonomy ) {
    4873                     // Term cache should already be primed by 'update_post_term_cache'.
    4874                     $terms = get_object_term_cache( $post->ID, $taxonomy );
    4875                     if ( false !== $terms ) {
    4876                         foreach ( $terms as $term ) {
    4877                             if ( ! isset( $term_ids[ $term->term_id ] ) ) {
    4878                                 $term_ids[ $term->term_id ] = 1;
    4879                             }
    4880                         }
    4881                     }
    4882                 }
    4883             }
    4884 
    4885             /*
    4886              * Only update the metadata cache for terms belonging to these posts if the term_id passed
    4887              * to `get_term_meta()` matches one of those terms. This prevents a single call to
    4888              * `get_term_meta()` from priming metadata for all `WP_Query` objects.
    4889              */
    4890             if ( isset( $term_ids[ $term_id ] ) ) {
    4891                 update_termmeta_cache( array_keys( $term_ids ) );
    4892                 remove_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );
    4893             }
    4894         }
    4895 
    4896         // If no terms were found, there's no need to run this again.
    4897         if ( empty( $term_ids ) ) {
    4898             remove_filter( 'get_term_metadata', array( $this, 'lazyload_term_meta' ), 10, 2 );
    4899         }
    4900 
     4846        _deprecated_function( __METHOD__, '4.5.0' );
    49014847        return $check;
    49024848    }
    49034849
    49044850    /**
    4905      * Lazy-load comment meta when inside of a `WP_Query` loop.
    4906      *
    4907      * This method is public so that it can be used as a filter callback. As a rule, there is no need to invoke it
    4908      * directly, from either inside or outside the `WP_Query` object.
     4851     * Lazyload comment meta for comments in the loop.
    49094852     *
    49104853     * @since 4.4.0
    4911��     *
    4912      * @param mixed $check     The `$check` param passed from the 'get_comment_metadata' hook.
    4913      * @param int  $comment_id ID of the comment whose metadata is being cached.
    4914      * @return mixed The original value of `$check`, to not affect 'get_comment_metadata'.
     4854     * @deprecated 4.5.0 See wp_queue_comments_for_comment_meta_lazyload().
     4855     *
     4856     * @param mixed $check
     4857     * @param int   $comment_id
     4858     * @return mixed
    49154859     */
    49164860    public function lazyload_comment_meta( $check, $comment_id ) {
    4917         // Don't use `wp_list_pluck()` to avoid by-reference manipulation.
    4918         $comment_ids = array();
    4919         if ( is_array( $this->comments ) ) {
    4920             foreach ( $this->comments as $comment ) {
    4921                 $comment_ids[] = $comment->comment_ID;
    4922             }
    4923         }
    4924 
    4925         /*
    4926          * Only update the metadata cache for comments belonging to these posts if the comment_id passed
    4927          * to `get_comment_meta()` matches one of those comments. This prevents a single call to
    4928          * `get_comment_meta()` from priming metadata for all `WP_Query` objects.
    4929          */
    4930         if ( in_array( $comment_id, $comment_ids ) ) {
    4931             update_meta_cache( 'comment', $comment_ids );
    4932             remove_filter( 'get_comment_metadata', array( $this, 'lazyload_comment_meta' ), 10, 2 );
    4933         } elseif ( empty( $comment_ids ) ) {
    4934             remove_filter( 'get_comment_metadata', array( $this, 'lazyload_comment_meta' ), 10, 2 );
    4935         }
    4936 
     4861        _deprecated_function( __METHOD__, '4.5.0' );
    49374862        return $check;
    49384863    }
  • trunk/src/wp-settings.php

    r36557 r36566  
    136136require( ABSPATH . WPINC . '/meta.php' );
    137137require( ABSPATH . WPINC . '/class-wp-meta-query.php' );
     138
    138139require( ABSPATH . WPINC . '/general-template.php' );
    139140require( ABSPATH . WPINC . '/link-template.php' );
  • trunk/tests/phpunit/tests/term/meta.php

    r35585 r36566  
    135135                $num_queries = $wpdb->num_queries;
    136136                $this->assertSame( 'bar', get_term_meta( $terms[0], 'foo', true ) );
    137                 $this->assertSame( $num_queries + 1, $wpdb->num_queries );
     137                $num_queries++;
     138                $this->assertSame( $num_queries, $wpdb->num_queries );
    138139
    139140                // Second and third requests should be in cache.
    140141                $this->assertSame( 'bar', get_term_meta( $terms[1], 'foo', true ) );
    141142                $this->assertSame( 'bar', get_term_meta( $terms[2], 'foo', true ) );
    142                 $this->assertSame( $num_queries + 1, $wpdb->num_queries );
     143                $this->assertSame( $num_queries, $wpdb->num_queries );
    143144
    144145                // Querying a term not primed should result in a hit.
     146
    145147                $this->assertSame( 'bar', get_term_meta( $orphan_term, 'foo', true ) );
    146                 $this->assertSame( $num_queries + 2, $wpdb->num_queries );
     148                $this->assertSame( $num_queries, $wpdb->num_queries );
    147149            }
    148150        }
    149151    }
    150152
    151     /**
    152      * @ticket 34073
    153      */
    154     public function test_term_meta_should_be_lazy_loaded_only_for_the_queries_in_which_the_term_has_posts() {
    155         global $wpdb;
    156 
    157         $posts = self::factory()->post->create_many( 3, array( 'post_status' => 'publish' ) );
    158         register_taxonomy( 'wptests_tax', 'post' );
    159         $terms = self::factory()->term->create_many( 6, array( 'taxonomy' => 'wptests_tax' ) );
    160 
    161         wp_set_object_terms( $posts[0], array( $terms[0], $terms[1] ), 'wptests_tax' );
    162         wp_set_object_terms( $posts[1], array( $terms[2], $terms[3] ), 'wptests_tax' );
    163         wp_set_object_terms( $posts[2], array( $terms[0], $terms[4], $terms[5] ), 'wptests_tax' );
    164 
    165         foreach ( $terms as $t ) {
    166             add_term_meta( $t, 'foo', 'bar' );
    167         }
    168 
    169         $q0 = new WP_Query( array( 'p' => $posts[0], 'cache_results' => true ) );
    170         $q1 = new WP_Query( array( 'p' => $posts[1], 'cache_results' => true ) );
    171         $q2 = new WP_Query( array( 'p' => $posts[2], 'cache_results' => true ) );
    172 
    173         /*
    174          * $terms[0] belongs to both $posts[0] and $posts[2], so `get_term_meta( $terms[0] )` should prime
    175          * the cache for term matched by $q0 and $q2.
    176          */
    177 
    178         // First request will hit the database.
    179         $num_queries = $wpdb->num_queries;
    180 
    181         // Prime caches.
    182         $this->assertSame( 'bar', get_term_meta( $terms[0], 'foo', true ) );
    183 
    184         // Two queries: one for $q0 and one for $q2.
    185         $num_queries += 2;
    186         $this->assertSame( $num_queries, $wpdb->num_queries );
    187 
    188         // Next requests should be in cache.
    189         $this->assertSame( 'bar', get_term_meta( $terms[1], 'foo', true ) );
    190         $this->assertSame( 'bar', get_term_meta( $terms[4], 'foo', true ) );
    191         $this->assertSame( 'bar', get_term_meta( $terms[5], 'foo', true ) );
    192         $this->assertSame( $num_queries, $wpdb->num_queries );
    193 
    194         // Querying for $terms[2] will prime $terms[3] as well.
    195         $this->assertSame( 'bar', get_term_meta( $terms[2], 'foo', true ) );
    196         $num_queries++;
    197         $this->assertSame( $num_queries, $wpdb->num_queries );
    198 
    199         $this->assertSame( 'bar', get_term_meta( $terms[3], 'foo', true ) );
    200         $this->assertSame( $num_queries, $wpdb->num_queries );
    201     }
    202 
    203     public function test_adding_term_meta_should_bust_get_terms_cache() {
     153    public function test_updating_term_meta_should_bust_get_terms_cache() {
    204154        $terms = self::factory()->term->create_many( 2, array( 'taxonomy' => 'wptests_tax' ) );
    205155
    206156        add_term_meta( $terms[0], 'foo', 'bar' );
     157
    207158
    208159        // Prime cache.
     
    220171        $this->assertEqualSets( array( $terms[0] ), $found );
    221172
    222         add_term_meta( $terms[1], 'foo', 'bar' );
     173        _term_meta( $terms[1], 'foo', 'bar' );
    223174
    224175        $found = get_terms( 'wptests_tax', array(
     
    236187    }
    237188
    238     public function test_updating_term_meta_should_bust_get_terms_cache() {
     189    public function test_ting_term_meta_should_bust_get_terms_cache() {
    239190        $terms = self::factory()->term->create_many( 2, array( 'taxonomy' => 'wptests_tax' ) );
    240191
    241192        add_term_meta( $terms[0], 'foo', 'bar' );
    242         add_term_meta( $terms[1], 'foo', 'baz' );
     193        add_term_meta( $terms[1], 'foo', 'ba' );
    243194
    244195        // Prime cache.
     
    254205        ) );
    255206
    256         $this->assertEqualSets( array( $terms[0] ), $found );
    257 
    258         update_term_meta( $terms[1], 'foo', 'bar' );
     207        $this->assertEqualSets( array( $terms[0] ), $found );
     208
     209        te_term_meta( $terms[1], 'foo', 'bar' );
    259210
    260211        $found = get_terms( 'wptests_tax', array(
     
    269220        ) );
    270221
    271         $this->assertEqualSets( array( $terms[0], $terms[1] ), $found );
    272     }
    273 
    274     public function test_deleting_term_meta_should_bust_get_terms_cache() {
    275         $terms = self::factory()->term->create_many( 2, array( 'taxonomy' => 'wptests_tax' ) );
    276 
    277         add_term_meta( $terms[0], 'foo', 'bar' );
    278         add_term_meta( $terms[1], 'foo', 'bar' );
    279 
    280         // Prime cache.
    281         $found = get_terms( 'wptests_tax', array(
    282             'hide_empty' => false,
    283             'fields' => 'ids',
    284             'meta_query' => array(
    285                 array(
    286                     'key' => 'foo',
    287                     'value' => 'bar',
    288                 ),
    289             ),
    290         ) );
    291 
    292         $this->assertEqualSets( array( $terms[0], $terms[1] ), $found );
    293 
    294         delete_term_meta( $terms[1], 'foo', 'bar' );
    295 
    296         $found = get_terms( 'wptests_tax', array(
    297             'hide_empty' => false,
    298             'fields' => 'ids',
    299             'meta_query' => array(
    300                 array(
    301                     'key' => 'foo',
    302                     'value' => 'bar',
    303                 ),
    304             ),
    305         ) );
    306 
    307222        $this->assertEqualSets( array( $terms[0] ), $found );
    308223    }
Note: See TracChangeset for help on using the changeset viewer.