Tutorial

Loading the Optimal WordPress Object Cache Implementation

Out of the box, WordPress has pretty great support for external object-cache implementations. Even its built-in object-caching helps WordPress be more performant and avoids redundant non-performant function calls and DB lookups.

One of the challenges we have faced at WebDevStudios is the way WordPress handles including/loading an external object cache and determining whether the default implementation is used. I won’t go into all the details, but the part we have had issues with is the fact that if there is an object-cache.php file in the wp-content directory, WordPress assumes an external object cache exists and it should not include or perform its built-in object-caching implementation.

The issues we have had are outlined below:

  1. We keep the code to be deployed to production in version control. This means we want/need to keep the object-cache.php file in the wp-content directory.
  2. WordPress object-cache plugins generally expect the user to transfer the object-cache.php file from the plugin’s directory directly to the wp-content parent directory.
  3. We prefer to keep the plugin’s object-cache.php file inside the plugin’s directory, NOT in the wp-content parent directory. This is because A) we often use the object-cache plugin as a Git submodule, and B) we want to check if our object cache backend (in this case, Redis) exists in the system before actually including the file.
  4. In order to accomplish #3, we place a custom object-cache.php file in wp-content which simply checks if the Redis backend exists, and if so, loads the ACTUAL object-cache.php from the plugin directory.
  5. WordPress assumption takes place, and our local and/or development sites’ performance is severely impacted (as they do not always have the external object-cache backend).

As the WordPress codebase doesn’t offer a direct way to change this behavior, we’ve developed a sort of wp-content/object-cache.php “hack” file to make it all happen.

<?php

// If Redis exists and redis server is configured,
if ( class_exists( 'Redis' ) && isset( $GLOBALS['redis_server'] ) ) {

    // Then load our object cache plugin.
    require_once 'plugins/wp-redis/object-cache.php';
}

/*
 * If we can't use the object-cache, we need some trickery
 * for WP to believe we aren't actually using an object-cache
 * (which it assumes since we have this file)
 */
else {

    // Helper/callback.
    function set_wp_using_ext_object_cache_to_false() {
        wp_using_ext_object_cache( false );
    }

    /*
     * Set to false now.
     * (After this file loads, WP resets to true.)
     */
    set_wp_using_ext_object_cache_to_false();

    /*
     * Loads and caches certain often requested site options. Need
     * to do manually now because it will not run later when
     * wp_load_core_site_options is set to false.
     */
    wp_load_core_site_options();

    // Include the built-in WP object-caching.
    require_once( ABSPATH . WPINC . '/cache.php' );

    // Hook in to reset to false,
    add_action(
        'muplugins_loaded', // To the earliest hook,
        'set_wp_using_ext_object_cache_to_false',
        -9999 // At a super early priority.
    );
}

What this file does is:

  1. Check if A) the object-cache backend exists, and B) the object-cache configuration exists (line 4).
  2. If both conditions are met, load the actual object-cache.php (line 7) from the plugin’s directory. So far, this is what we’ve already discussed in #4 in the outline above.
  3. If one of the conditions listed above fail, then we proceed to our “hack” (line 15).
    1. Tell WordPress that we are NOT using an external object cache (line 26).
    2. Call wp_load_core_site_options() to ensure certain often requested site options are cached while WordPress thinks we do not have an object-cache (line 33).
    3. Include WordPress’ built-in object-caching implementation (line 36).
    4. Hook in to the earliest possible hook, 'muplugins_loaded', at the earliest (well, within reason) priority order (line 39-43).
    5. The hook callback will (again) tell WordPress that we are NOT using an external object cache.

Even though we told WordPress that we are NOT using an external object-cache, WordPress resets it to true as soon as the wp-content/object-cache.php file is included. Why? because A) it found that the wp-content/object-cache.php file existed, and B) that the `wp_cache_init` function existed (because we included ABSPATH . WPINC . '/cache.php'). So, even though we included the built-in object-caching functionality, without the final bit (the early hook/callback), WordPress will not actually perform its built-in object-caching duties as it would still believe we are taking care of it with an external object-cache solution. Whacky, right?

With the above solution, if our conditions fail and we cannot use our external object cache, we’re essentially telling WordPress, “No…no, we are NOT using an external object cache. Get back to work.”

More resources for learning about WordPress object cache:

CREAM! Cache rules everything around me! #beardandshirt @webdevstudios #webdevstudios #wordpress #webdev #webdesign #webapps

A post shared by Brian Messenlehner (@bmess_wordpress) on

9 thoughts on “Loading the Optimal WordPress Object Cache Implementation

  1. Very well done, and thank you so much for sharing!

    We are now using this code with the following modifications:
    1. We load the plugins from the defined plugins directory (set by WP_PLUGIN_DIR) with a fallback to the default WP_CONTENT_DIR . '/plugins': https://gist.github.com/bjornjohansen/eefbe3bfa4f5f0270a3e#file-object-cache-php-L8
    2. We check that the file actually exists before including it:
    https://gist.github.com/bjornjohansen/eefbe3bfa4f5f0270a3e#file-object-cache-php-L14
    3. We have a fallback to Memcached if Redis is unavailable or not configured:
    https://gist.github.com/bjornjohansen/eefbe3bfa4f5f0270a3e#file-object-cache-php-L21

  2. I could be wrong, but I don’t think you need to hack wp_using_ext_object_cache() the way you’re hacking it. If your object-cache.php implementation doesn’t define wp_cache_init(), core should fall back to including its own cache.php as if the object-cache.php file was never there in the first place. See wp_start_object_cache() for more details.

    1. The problem is that the $_wp_using_ext_object_cache global gets set to true, and there are several places throughout core which wp_using_ext_object_cache() (which is what sets/gets the $_wp_using_ext_object_cache global) is checked: https://gist.github.com/jtsternberg/69a700ce5d1469cb6aa5

      So the hack is necessary to reset that global to false, so that WP operates as it would had we never added an object-cache.php file.

      1. The problem is that the $_wp_using_ext_object_cache global gets set to true

        Right. But it gets set to true only if wp_cache_init() is defined in the object-cache.php drop-in, so the solution is to not define it if Redis/Memcached is unavailable or not configured 🙂

      2. In my testing, there is no scenario where that global is NOT set to true when you have an object-cache.php file. If you figure out or know how, I would love to hear it.

      3. I didn’t test it before I commented, I just assumed, because it’s what wp_start_object_cache() in load.php says, and I was right. Put this in object-cache.php:

        $GLOBALS[‘wp_filter’][‘init’][10][‘testing-object-cache’] = array( ‘function’ => function() {
        var_dump( $GLOBALS[‘_wp_using_ext_object_cache’] );
        die();
        }, ‘accepted_args’ => 1 );

        You’ll see NULL. But the second you activate Multisite, you get a true, because for some (probably ancient) reason ms-settings.php thought it was a good idea to call wp_start_object_cache() twice. Then I found this ticket.

      4. That makes sense, as all my testing was on a multisite installation. I’ll do some testing and update this post with the best way to handle this in a single site install.

Have a comment?

Your email address will not be published. Required fields are marked *

accessibilityadminaggregationanchorbackupsbookmarksbuddypresscachingcalendarcaret-downcartunifiedcrediblecustommigrationdesigndevecomfriendsgoodgroupsgrowthhostingideasinternationalizationiphoneloyaltymailhealthmessagingArtboard 1migrationsmultiple-sourcesmultisitenotificationsperformancephoneprofilesresearchscalablescrapingsecuresharearrowarrowsourcestreamsupportunifiedupdatesvaultwebsitewordpress