Make WordPress Core


Ignore:
Timestamp:
04/03/2024 09:29:13 PM (15 months ago)
Author:
flixos90
Message:

Options, Meta APIs: Use more sensible default for autoloading options which allows WordPress core to make a decision.

An excessive amount of autoloaded options is a common cause for slow database responses, sometimes caused by very large individual autoloaded options. As it is not mandatory to provide an autoload value when adding an option to the database, it tends to be ignored, which in combination with a default value of "yes" and lack of documentation can lead to the aforementioned problem.

This changeset enhances the option autoloading behavior in several ways:

  • Update the function documentation to encourage the use of boolean true or false to explicitly provide an autoload value for an option.
  • Use new string values on and off for explicitly provided values stored in the database, to distinguish them from yes and no, since yes does not allow determining whether it was set intentionally by the developer or only as a default.
  • Effectively deprecate the values yes and no. They are still supported for backward compatibility, but now discouraged.
  • Use null as new default autoload value for add_option(). If the developer does not provide an explicit value, this will now trigger WordPress logic to determine an autoload value to use:
    • If WordPress determines that the option should not be autoloaded, it is stored in the database as auto-off. As part of this changeset, the single heuristic introduced for that is to check whether the option size is larger than a threshold of 150k bytes. This threshold is filterable via a new wp_max_autoloaded_option_size filter.
    • If WordPress determines that the option should be autoloaded, it is stored in the database as auto-on. No logic to make such a decision is introduced as part of this changeset, but a new filter wp_default_autoload_value can be used to define such heuristics, e.g. by optimization plugins.
    • If WordPress cannot determine whether or not to autoload the option, it is stored in the database as auto.
    • This effectively means that any option without an explicit autoload value provided by the developer will be stored with an autoload value of auto, unless the option's size exceeds the aforementioned threshold. Options with a value of auto are still autoloaded as of today, most importantly for backward compatibility. A new function wp_autoload_values_to_autoload() returns the list of autolaod values that dictate for an option to be autoloaded, and a new filter wp_autoload_values_to_autoload can be used to alter that list.

These behavioral changes encourage developers to be more mindful of autoloading, while providing WordPress core and optimization plugins with additional control over heuristics for autoloading options where no explicit autoload value was provided.

At the same time, the changes are fully backward compatible from a functionality perspective, with the only exception being that very large options will now no longer be autoloaded if the developer did not explicitly request for them to be autoloaded. Neither WordPress core nor plugins are able to override an explicitly provided value, which is intentional to continue giving developers full control over their own options.

Props pbearne, flixos90, joemcgill, azaozz, spacedmonkey, swissspidy, mukesh27, markjaquith.
Fixes #42441.

File:
1 edited

Legend:

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

    r57110 r57920  
    393393
    394394    $grouped_options = array(
    395         'yes' => array(),
    396         'no' => array(),
     395        'on' => array(),
     396        'off' => array(),
    397397    );
    398398    $results         = array();
    399399    foreach ( $options as $option => $autoload ) {
    400400        wp_protect_special_option( $option ); // Ensure only valid options can be passed.
    401         if ( 'no' === $autoload || false === $autoload ) { // Sanitize autoload value and categorize accordingly.
    402             $grouped_options['no'][] = $option;
     401        if ( 'off' === $autoload || 'no' === $autoload || false === $autoload ) { // Sanitize autoload value and categorize accordingly.
     402            $grouped_options['off'][] = $option;
    403403        } else {
    404             $grouped_options['yes'][] = $option;
     404            $grouped_options['on'][] = $option;
    405405        }
    406406        $results[ $option ] = false; // Initialize result value.
     
    466466
    467467    /*
    468      * If any options were changed to 'yes', delete their individual caches, and delete 'alloptions' cache so that it
     468     * If any options were changed to 'on', delete their individual caches, and delete 'alloptions' cache so that it
    469469     * is refreshed as needed.
    470      * If no options were changed to 'yes' but any options were changed to 'no', delete them from the 'alloptions'
    471      * cache. This is not necessary when options were changed to 'yes', since in that situation the entire cache is
     470     * If no options were changed to 'on' but any options were changed to 'no', delete them from the 'alloptions'
     471     * cache. This is not necessary when options were changed to 'on', since in that situation the entire cache is
    472472     * deleted anyway.
    473473     */
    474     if ( $grouped_options['yes'] ) {
    475         wp_cache_delete_multiple( $grouped_options['yes'], 'options' );
     474    if ( $grouped_options['on'] ) {
     475        wp_cache_delete_multiple( $grouped_options['on'], 'options' );
    476476        wp_cache_delete( 'alloptions', 'options' );
    477     } elseif ( $grouped_options['no'] ) {
     477    } elseif ( $grouped_options['off'] ) {
    478478        $alloptions = wp_load_alloptions( true );
    479479
    480         foreach ( $grouped_options['no'] as $option ) {
     480        foreach ( $grouped_options['off'] as $option ) {
    481481            if ( isset( $alloptions[ $option ] ) ) {
    482482                unset( $alloptions[ $option ] );
     
    607607    if ( ! $alloptions ) {
    608608        $suppress      = $wpdb->suppress_errors();
    609         $alloptions_db = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE autoload = 'yes'" );
     609        $alloptions_db = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE autoload IN ( '" . implode( "', '", wp_autoload_values_to_autoload() ) . "' )" );
     610
    610611        if ( ! $alloptions_db ) {
    611612            $alloptions_db = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options" );
     
    706707 * @global wpdb $wpdb WordPress database abstraction object.
    707708 *
    708  * @param string      $option   Name of the option to update. Expected to not be SQL-escaped.
    709  * @param mixed       $value    Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
    710  * @param string|bool $autoload Optional. Whether to load the option when WordPress starts up. For existing options,
    711  *                              `$autoload` can only be updated using `update_option()` if `$value` is also changed.
    712  *                              Accepts 'yes'|true to enable or 'no'|false to disable.
    713  *                              Autoloading too many options can lead to performance problems, especially if the
    714  *                              options are not frequently used. For options which are accessed across several places
    715  *                              in the frontend, it is recommended to autoload them, by using 'yes'|true.
    716  *                              For options which are accessed only on few specific URLs, it is recommended
    717  *                              to not autoload them, by using 'no'|false. For non-existent options, the default value
    718  *                              is 'yes'. Default null.
     709 * @param string    $option   Name of the option to update. Expected to not be SQL-escaped.
     710 * @param mixed     $value    Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
     711 * @param bool|null $autoload Optional. Whether to load the option when WordPress starts up.
     712 *                            Accepts a boolean, or `null` to stick with the initial value or, if no initial value is set,
     713 *                            to leave the decision up to default heuristics in WordPress.
     714 *                            For existing options,
     715 *                            `$autoload` can only be updated using `update_option()` if `$value` is also changed.
     716 *                            For backward compatibility 'yes' and 'no' are also accepted.
     717 *                            Autoloading too many options can lead to performance problems, especially if the
     718 *                            options are not frequently used. For options which are accessed across several places
     719 *                            in the frontend, it is recommended to autoload them, by using true.
     720 *                            For options which are accessed only on few specific URLs, it is recommended
     721 *                            to not autoload them, by using false.
     722 *                            For non-existent options, the default is null, which means WordPress will determine
     723 *                            the autoload value.
    719724 * @return bool True if the value was updated, false otherwise.
    720725 */
     
    802807    /** This filter is documented in wp-includes/option.php */
    803808    if ( apply_filters( "default_option_{$option}", false, $option, false ) === $old_value ) {
    804         // Default setting for new options is 'yes'.
    805         if ( null === $autoload ) {
    806             $autoload = 'yes';
    807         }
    808 
    809809        return add_option( $option, $value, '', $autoload );
    810810    }
     
    828828
    829829    if ( null !== $autoload ) {
    830         $update_args['autoload'] = ( 'no' === $autoload || false === $autoload ) ? 'no' : 'yes';
     830        $update_args['autoload'] = wp_determine_option_autoload_value( $option, $value, $serialized_value, $autoload );
     831    } else {
     832        // Retrieve the current autoload value to reevaluate it in case it was set automatically.
     833        $raw_autoload = $wpdb->get_var( $wpdb->prepare( "SELECT autoload FROM $wpdb->options WHERE option_name = %s LIMIT 1", $option ) );
     834        $allow_values = array( 'auto-on', 'auto-off', 'auto' );
     835        if ( in_array( $raw_autoload, $allow_values, true ) ) {
     836            $autoload = wp_determine_option_autoload_value( $option, $value, $serialized_value, $autoload );
     837            if ( $autoload !== $raw_autoload ) {
     838                $update_args['autoload'] = $autoload;
     839            }
     840        }
    831841    }
    832842
     
    854864                wp_cache_set( $option, $serialized_value, 'options' );
    855865            }
    856         } elseif ( 'yes' === $update_args['autoload'] ) {
     866        } elseif ( in_array( $update_args['autoload'], wp_autoload_values_to_autoload(), true ) ) {
    857867            // Delete the individual cache, then set in alloptions cache.
    858868            wp_cache_delete( $option, 'options' );
     
    916926 *
    917927 * @since 1.0.0
     928 * @since 6.6.0 The $autoload parameter's default value was changed to null.
    918929 *
    919930 * @global wpdb $wpdb WordPress database abstraction object.
    920931 *
    921  * @param string      $option     Name of the option to add. Expected to not be SQL-escaped.
    922  * @param mixed       $value      Optional. Option value. Must be serializable if non-scalar.
    923  *                                Expected to not be SQL-escaped.
    924  * @param string      $deprecated Optional. Description. Not used anymore.
    925  * @param string|bool $autoload   Optional. Whether to load the option when WordPress starts up.
    926  *                                Accepts 'yes'|true to enable or 'no'|false to disable.
    927  *                                Autoloading too many options can lead to performance problems, especially if the
    928  *                                options are not frequently used. For options which are accessed across several places
    929  *                                in the frontend, it is recommended to autoload them, by using 'yes'|true.
    930  *                                For options which are accessed only on few specific URLs, it is recommended
    931  *                                to not autoload them, by using 'no'|false. Default 'yes'.
     932 * @param string    $option     Name of the option to add. Expected to not be SQL-escaped.
     933 * @param mixed     $value      Optional. Option value. Must be serializable if non-scalar.
     934 *                              Expected to not be SQL-escaped.
     935 * @param string    $deprecated Optional. Description. Not used anymore.
     936 * @param bool|null $autoload   Optional. Whether to load the option when WordPress starts up.
     937 *                              Accepts a boolean, or `null` to leave the decision up to default heuristics in WordPress.
     938 *                              For backward compatibility 'yes' and 'no' are also accepted.
     939 *                              Autoloading too many options can lead to performance problems, especially if the
     940 *                              options are not frequently used. For options which are accessed across several places
     941 *                              in the frontend, it is recommended to autoload them, by using 'yes'|true.
     942 *                              For options which are accessed only on few specific URLs, it is recommended
     943 *                              to not autoload them, by using false.
     944 *                              Default is null, which means WordPress will determine the autoload value.
    932945 * @return bool True if the option was added, false otherwise.
    933946 */
    934 function add_option( $option, $value = '', $deprecated = '', $autoload = 'yes' ) {
     947function add_option( $option, $value = '', $deprecated = '', $autoload = null ) {
    935948    global $wpdb;
    936949
     
    9921005
    9931006    $serialized_value = maybe_serialize( $value );
    994     $autoload         = ( 'no' === $autoload || false === $autoload ) ? 'no' : 'yes';
     1007
     1008    $autoload = wp_determine_option_autoload_value( $option, $value, $serialized_value, $autoload );
    9951009
    9961010    /**
     
    10101024
    10111025    if ( ! wp_installing() ) {
    1012         if ( 'yes' === $autoload ) {
     1026        if ( in_array( $autoload, wp_autoload_values_to_autoload(), true ) ) {
    10131027            $alloptions            = wp_load_alloptions( true );
    10141028            $alloptions[ $option ] = $serialized_value;
     
    10941108
    10951109    if ( ! wp_installing() ) {
    1096         if ( 'yes' === $row->autoload ) {
     1110        if ( in_array( $row->autoload, wp_autoload_values_to_autoload(), true ) ) {
    10971111            $alloptions = wp_load_alloptions( true );
    10981112
     
    11321146
    11331147    return false;
     1148}
     1149
     1150/**
     1151 *  Determines the appropriate autoload value for an option based on input.
     1152 *
     1153 *  This function checks the provided autoload value and returns a standardized value
     1154 *  ('on', 'off', 'auto-on', 'auto-off', or 'auto') based on specific conditions.
     1155 *
     1156 * If no explicit autoload value is provided, the function will check for certain heuristics around the given option.
     1157 * It will return `auto-on` to indicate autoloading, `auto-off` to indicate not autoloading, or `auto` if no clear
     1158 * decision could be made.
     1159 *
     1160 * @since 6.6.0
     1161 * @access private
     1162 *
     1163 * @param string $option          The name of the option.
     1164 * @param mixed $value            The value of the option to check its autoload value.
     1165 * @param mixed $serialized_value The serialized value of the option to check its autoload value.
     1166 * @param bool|null $autoload     The autoload value to check.
     1167 *                                Accepts 'on'|true to enable or 'off'|false to disable, or
     1168 *                                'auto-on', 'auto-off', or 'auto' for internal purposes.
     1169 *                                Any other autoload value will be forced to either 'auto-on',
     1170 *                                'auto-off', or 'auto'.
     1171 *                                'yes' and 'no' are supported for backward compatibility.
     1172 * @return string Returns the original $autoload value if explicit, or 'auto-on', 'auto-off',
     1173 *                or 'auto' depending on default heuristics.
     1174 */
     1175function wp_determine_option_autoload_value( $option, $value, $serialized_value, $autoload ) {
     1176
     1177    // Check if autoload is a boolean.
     1178    if ( is_bool( $autoload ) ) {
     1179        return $autoload ? 'on' : 'off';
     1180    }
     1181
     1182    switch ( $autoload ) {
     1183        case 'on':
     1184        case 'yes':
     1185            return 'on';
     1186        case 'off':
     1187        case 'no':
     1188            return 'off';
     1189    }
     1190
     1191    /**
     1192     * Allows to determine the default autoload value for an option where no explicit value is passed.
     1193     *
     1194     * @since 6.6.0
     1195     *
     1196     * @param bool|null $autoload The default autoload value to set. Returning true will be set as 'auto-on' in the
     1197     *                            database, false will be set as 'auto-off', and null will be set as 'auto'.
     1198     * @param string    $option   The passed option name.
     1199     * @param mixed     $value    The passed option value to be saved.
     1200     */
     1201    $autoload = apply_filters( 'wp_default_autoload_value', null, $option, $value, $serialized_value );
     1202    if ( is_bool( $autoload ) ) {
     1203        return $autoload ? 'auto-on' : 'auto-off';
     1204    }
     1205
     1206    return 'auto';
     1207}
     1208
     1209/**
     1210 * Filters the default autoload value to disable autoloading if the option value is too large.
     1211 *
     1212 * @since 6.6.0
     1213 * @access private
     1214 *
     1215 * @param bool|null $autoload         The default autoload value to set.
     1216 * @param string    $option           The passed option name.
     1217 * @param mixed     $value            The passed option value to be saved.
     1218 * @param mixed     $serialized_value The passed option value to be saved, in serialized form.
     1219 * @return bool|null Potentially modified $default.
     1220 */
     1221function wp_filter_default_autoload_value_via_option_size( $autoload, $option, $value, $serialized_value ) {
     1222    /**
     1223     * Filters the maximum size of option value in bytes.
     1224     *
     1225     * @since 6.6.0
     1226     *
     1227     * @param int    $max_option_size The option-size threshold, in bytes. Default 150000.
     1228     * @param string $option          The name of the option.
     1229     */
     1230    $max_option_size = (int) apply_filters( 'wp_max_autoloaded_option_size', 150000, $option );
     1231    $size            = ! empty( $serialized_value ) ? strlen( $serialized_value ) : 0;
     1232
     1233    if ( $size > $max_option_size ) {
     1234        return false;
     1235    }
     1236
     1237    return $autoload;
    11341238}
    11351239
     
    29253029    return $registered[ $option ]['default'];
    29263030}
     3031
     3032/**
     3033 * Returns the values that trigger autoloading from the options table.
     3034 *
     3035 * @since 6.6.0
     3036 *
     3037 * @return array The values that trigger autoloading.
     3038 */
     3039function wp_autoload_values_to_autoload() {
     3040    $autoload_values = array( 'yes', 'on', 'auto-on', 'auto' );
     3041
     3042    /**
     3043     * Filters the autoload values that should be considered for autoloading from the options table.
     3044     *
     3045     * The filter can only be used to remove autoload values from the default list.
     3046     *
     3047     * @since 6.6.0
     3048     *
     3049     * @param array $autoload_values Autoload values used to autoload option.
     3050     *                               Default list contains 'yes', 'on', 'auto-on', and 'auto'.
     3051     */
     3052    $filtered_values = apply_filters( 'wp_autoload_values_to_autoload', $autoload_values );
     3053
     3054    return array_intersect( $filtered_values, $autoload_values );
     3055}
Note: See TracChangeset for help on using the changeset viewer.