Make WordPress Core


Ignore:
Timestamp:
01/15/2025 10:11:15 PM (15 months ago)
Author:
peterwilsoncc
Message:

Options/Meta APIs: Optimize cache hits for non-existent options.

Optimize the order of checking the various options caches in get_option() to prevent hitting external caches each time it is called for a known non-existent option.

The caches are checked in the following order when getting an option:

  1. Check the alloptions cache first to prioritize existing loaded options.
  2. Check the notoptions cache before a cache lookup or DB hit.
  3. Check the options cache prior to a DB hit.

Follow up to [56595].

Props adamsilverstein, flixos90, ivankristianto, joemcgill, rmccue, siliconforks, spacedmonkey.
Fixes #62692.
See #58277.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/option/option.php

    r58945 r59631  
    113113
    114114        $before = get_num_queries();
    115         $value  = get_option( 'invalid' );
    116         $after  = get_num_queries();
     115        get_option( 'invalid' );
     116        $after = get_num_queries();
    117117
    118118        $this->assertSame( 0, $after - $before );
     
    128128
    129129        $before = get_num_queries();
    130         $value  = get_option( 'invalid' );
    131         $after  = get_num_queries();
     130        get_option( 'invalid' );
     131        $after = get_num_queries();
    132132
    133133        $notoptions = wp_cache_get( 'notoptions', 'options' );
     
    136136        $this->assertIsArray( $notoptions, 'The notoptions cache should be set.' );
    137137        $this->assertArrayHasKey( 'invalid', $notoptions, 'The "invalid" option should be in the notoptions cache.' );
    138     }
    139 
    140     /**
    141      * @ticket 58277
    142      *
    143      * @covers ::get_option
    144      */
    145     public function test_get_option_notoptions_do_not_load_cache() {
    146         add_option( 'foo', 'bar', '', false );
    147         wp_cache_delete( 'notoptions', 'options' );
    148 
    149         $before = get_num_queries();
    150         $value  = get_option( 'foo' );
    151         $after  = get_num_queries();
    152 
    153         $notoptions = wp_cache_get( 'notoptions', 'options' );
    154 
    155         $this->assertSame( 0, $after - $before, 'The options cache was not hit on the second call to `get_option()`.' );
    156         $this->assertFalse( $notoptions, 'The notoptions cache should not be set.' );
    157138    }
    158139
     
    549530        $this->assertArrayNotHasKey( $option_name, $updated_notoptions, 'The "foobar" option should not be in the notoptions cache after adding it.' );
    550531    }
     532
     533    /**
     534     * Test that get_option() does not hit the external cache multiple times for the same option.
     535     *
     536     * @ticket 62692
     537     *
     538     * @covers ::get_option
     539     *
     540     * @dataProvider data_get_option_does_not_hit_the_external_cache_multiple_times_for_the_same_option
     541     *
     542     * @param int    $expected_connections Expected number of connections to the memcached server.
     543     * @param bool   $option_exists        Whether the option should be set. Default true.
     544     * @param string $autoload             Whether the option should be auto loaded. Default true.
     545     */
     546    public function test_get_option_does_not_hit_the_external_cache_multiple_times_for_the_same_option( $expected_connections, $option_exists = true, $autoload = true ) {
     547        if ( ! wp_using_ext_object_cache() ) {
     548            $this->markTestSkipped( 'This test requires an external object cache.' );
     549        }
     550
     551        if ( false === $this->helper_object_cache_stats_cmd_get() ) {
     552            $this->markTestSkipped( 'This test requires access to the number of get requests to the external object cache.' );
     553        }
     554
     555        if ( $option_exists ) {
     556            add_option( 'ticket-62692', 'value', '', $autoload );
     557        }
     558
     559        wp_cache_delete_multiple( array( 'ticket-62692', 'notoptions', 'alloptions' ), 'options' );
     560
     561        $connections_start = $this->helper_object_cache_stats_cmd_get();
     562
     563        $call_getter = 10;
     564        while ( $call_getter-- ) {
     565            get_option( 'ticket-62692' );
     566        }
     567
     568        $connections_end = $this->helper_object_cache_stats_cmd_get();
     569
     570        $this->assertSame( $expected_connections, $connections_end - $connections_start );
     571    }
     572
     573    /**
     574     * Data provider.
     575     *
     576     * @return array[]
     577     */
     578    public function data_get_option_does_not_hit_the_external_cache_multiple_times_for_the_same_option() {
     579        return array(
     580            'exists, autoload'       => array( 1, true, true ),
     581            'exists, not autoloaded' => array( 3, true, false ),
     582            'does not exist'         => array( 3, false ),
     583        );
     584    }
     585
     586    /**
     587     * Helper function to get the number of get commands from the external object cache.
     588     *
     589     * @return int|false Number of get command calls, false if unavailable.
     590     */
     591    public function helper_object_cache_stats_cmd_get() {
     592        if ( ! wp_using_ext_object_cache() || ! function_exists( 'wp_cache_get_stats' ) ) {
     593            return false;
     594        }
     595
     596        $stats = wp_cache_get_stats();
     597
     598        // Check the shape of the stats.
     599        if ( ! is_array( $stats ) ) {
     600            return false;
     601        }
     602
     603        // Get the first server's stats.
     604        $stats = array_shift( $stats );
     605
     606        if ( ! is_array( $stats ) ) {
     607            return false;
     608        }
     609
     610        if ( ! array_key_exists( 'cmd_get', $stats ) ) {
     611            return false;
     612        }
     613
     614        return $stats['cmd_get'];
     615    }
    551616}
Note: See TracChangeset for help on using the changeset viewer.