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:
- 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.
- 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.
- 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.
- 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.
- 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:
- Check if A) the object-cache backend exists, and B) the object-cache configuration exists (line 4).
- 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.
- If one of the conditions listed above fail, then we proceed to our “hack” (line 15).
- Tell WordPress that we are NOT using an external object cache (line 26).
- 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). - Include WordPress’ built-in object-caching implementation (line 36).
- Hook in to the earliest possible hook,
'muplugins_loaded'
, at the earliest (well, within reason) priority order (line 39-43). - 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:
- Grokking the WordPress Object Cache by Zack Tollman
- Persistent Object Caching by Ryan Hellyer on wptavern
- Core Caching Concepts in WordPress by Zack Tollman
- WP Redis – object caching plugin which uses Redis as the cache store
- WordPress built-in object caching