Make WordPress Core

Changeset 56595


Ignore:
Timestamp:
09/15/2023 04:13:52 PM (9 days ago)
Author:
spacedmonkey
Message:

Options, Meta APIs: Optimize get_option by relocating notoptions cache lookup.

In the get_option function, a cache lookup for the notoptions key is performed, which stores an array of keys for options known not to exist. This optimization prevents repeated database queries when certain options are requested. However, the cache lookup for notoptions was conducted before checking if the requested option exists in the cache. Given that it's more likely that the option does exist, this commit reorders the checks to first verify the option's existence in the cache before confirming its absence. This adjustment reduces redundant queries and also eliminates an unnecessary cache lookup, improving overall performance.

Props spacedmonkey, costdev, flixos90, azaozz.
Fixes #58277.

Location:
trunk
Files:
2 edited

Legend:

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

    r56508 r56595  
    162162
    163163    if ( ! wp_installing() ) {
    164         // Prevent non-existent options from triggering multiple queries.
    165         $notoptions = wp_cache_get( 'notoptions', 'options' );
    166 
    167         // Prevent non-existent `notoptions` key from triggering multiple key lookups.
    168         if ( ! is_array( $notoptions ) ) {
    169             $notoptions = array();
    170             wp_cache_set( 'notoptions', $notoptions, 'options' );
    171         }
    172 
    173         if ( isset( $notoptions[ $option ] ) ) {
    174             /**
    175              * Filters the default value for an option.
    176              *
    177              * The dynamic portion of the hook name, `$option`, refers to the option name.
    178              *
    179              * @since 3.4.0
    180              * @since 4.4.0 The `$option` parameter was added.
    181              * @since 4.7.0 The `$passed_default` parameter was added to distinguish between a `false` value and the default parameter value.
    182              *
    183              * @param mixed  $default_value  The default value to return if the option does not exist
    184              *                               in the database.
    185              * @param string $option         Option name.
    186              * @param bool   $passed_default Was `get_option()` passed a default value?
    187              */
    188             return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default );
    189         }
    190 
    191164        $alloptions = wp_load_alloptions();
    192165
     
    197170
    198171            if ( false === $value ) {
     172                // Prevent non-existent options from triggering multiple queries.
     173                $notoptions = wp_cache_get( 'notoptions', 'options' );
     174
     175                // Prevent non-existent `notoptions` key from triggering multiple key lookups.
     176                if ( ! is_array( $notoptions ) ) {
     177                    $notoptions = array();
     178                    wp_cache_set( 'notoptions', $notoptions, 'options' );
     179                } elseif ( isset( $notoptions[ $option ] ) ) {
     180                    /**
     181                     * Filters the default value for an option.
     182                     *
     183                     * The dynamic portion of the hook name, `$option`, refers to the option name.
     184                     *
     185                     * @since 3.4.0
     186                     * @since 4.4.0 The `$option` parameter was added.
     187                     * @since 4.7.0 The `$passed_default` parameter was added to distinguish between a `false` value and the default parameter value.
     188                     *
     189                     * @param mixed  $default_value  The default value to return if the option does not exist
     190                     *                               in the database.
     191                     * @param string $option         Option name.
     192                     * @param bool   $passed_default Was `get_option()` passed a default value?
     193                     */
     194                    return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default );
     195                }
     196
    199197                $row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );
    200198
     
    204202                    wp_cache_add( $option, $value, 'options' );
    205203                } else { // Option does not exist, so we must cache its non-existence.
    206                     if ( ! is_array( $notoptions ) ) {
    207                         $notoptions = array();
    208                     }
    209 
    210204                    $notoptions[ $option ] = true;
    211205                    wp_cache_set( 'notoptions', $notoptions, 'options' );
  • trunk/tests/phpunit/tests/option/option.php

    r54147 r56595  
    102102
    103103    /**
     104     * @ticket 58277
     105     *
     106     * @covers ::get_option
     107     */
     108    public function test_get_option_notoptions_cache() {
     109        $notoptions = array(
     110            'invalid' => true,
     111        );
     112        wp_cache_set( 'notoptions', $notoptions, 'options' );
     113
     114        $before = get_num_queries();
     115        $value  = get_option( 'invalid' );
     116        $after  = get_num_queries();
     117
     118        $this->assertSame( 0, $after - $before );
     119    }
     120
     121    /**
     122     * @ticket 58277
     123     *
     124     * @covers ::get_option
     125     */
     126    public function test_get_option_notoptions_set_cache() {
     127        get_option( 'invalid' );
     128
     129        $before = get_num_queries();
     130        $value  = get_option( 'invalid' );
     131        $after  = get_num_queries();
     132
     133        $notoptions = wp_cache_get( 'notoptions', 'options' );
     134
     135        $this->assertSame( 0, $after - $before, 'The notoptions cache was not hit on the second call to `get_option()`.' );
     136        $this->assertIsArray( $notoptions, 'The notoptions cache should be set.' );
     137        $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', '', 'no' );
     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.' );
     157    }
     158
     159    /**
    104160     * @covers ::get_option
    105161     * @covers ::add_option
Note: See TracChangeset for help on using the changeset viewer.