Hosting: gotcha with multiple WP installs, one Redis
If you're using one Redis instance to keep multiple WordPress sites' data, you need to make sure different sites don't bleed over to others. Read how and why.
Redis here can be any other key-value store used for object caching; Memcached, for example.
Okay, so the setup is that you have at least two sites with their default table prefixes (both use wp_
), and one Redis. In my local development environment, I use Laravel Valet, and have Redis installed via homebrew, and configured so everything Just Works™. You can read about how I set this up here.
The problem is that unless you do additional work, the two sites are going to store their data in the same bucket in the same Redis instance, which is obviously bad. Here’s an example:
$ pwd
/Users/javorszky/Sites/dev2
$ wp option get home
https://dev2.test
$ cd ../dev
$ pwd
/Users/javorszky/Sites/dev
$ wp option get home
https://dev2.test
You should get different values for home
from the two different directories, because wp
will use the install with the wp-config.php
next to it.
If I open the two sites, here’s what I see:
Interestingly the post content seems to be correct.
The reason for this is that WordPress autoloads options (that are to be autoloaded, i.e. most of the core important ones) during its startup with a call to wp_load_alloptions()
function that looks like this:
function wp_load_alloptions() {
global $wpdb;
if ( ! wp_installing() || ! is_multisite() ) {
$alloptions = wp_cache_get( 'alloptions', 'options' );
...
}
That wp_cache_get
is what causes these options from site 1 to bleed over to site 2.
wp_cache_get
is defined in our Redis object cache implementation, which is this:
function wp_cache_get($key, $group = '', $force = false, &$found = null)
{
global $wp_object_cache;
return $wp_object_cache->get($key, $group, $force, $found);
}
The global $wp_object_cache
is set in the same file. The class is declared, and initialised like so:
// # wp-content/plugins/redis-cache/includes/object-cache.php
function wp_cache_init()
{
global $wp_object_cache;
if (! ($wp_object_cache instanceof WP_Object_Cache)) {
$fail_gracefully = ! defined('WP_REDIS_GRACEFUL') || WP_REDIS_GRACEFUL;
$wp_object_cache = new WP_Object_Cache($fail_gracefully);
}
}
class WP_Object_Cache
{
...
}
Here’s the get
method on the class:
public function get($key, $group = 'default', $force = false, &$found = null)
{
$derived_key = $this->build_key($key, $group);
if (isset($this->cache[$derived_key]) && ! $force) {
$found = true;
$this->cache_hits++;
return is_object($this->cache[$derived_key]) ? clone $this->cache[$derived_key] : $this->cache[$derived_key];
...
}
Essentially it builds an internal redis-specific key from whatever option we want, and then grabs that from the object cache. Going further, build_key
is this:
public function build_key($key, $group = 'default')
{
if (empty($group)) {
$group = 'default';
}
$salt = defined('WP_CACHE_KEY_SALT') ? trim(WP_CACHE_KEY_SALT) : '';
$prefix = in_array($group, $this->global_groups) ? $this->global_prefix : $this->blog_prefix;
return "{$salt}{$prefix}:{$group}:{$key}";
}
The final key is made up of the following bits when loading the alloptions
cache:
- group is
options
, as that’s the second argument towp_cache_get
- salt is emptystring, because we do not have
WP_CACHE_KEY_SALT
set $prefix
iswp_
in both cases$key
isalloptions
in both cases.
Which means that no matter which site you’re querying alloptions
, the interal redis key ends up being wp_:options:alloptions
. Whichever site re-set that cache after it’s been cleared, wins!
Solution
Add this to your wp-config.php
file:
define( 'WP_CACHE_KEY_SALT', 'GiB16F?*EJdJBRT*2-L(|Iya)m77|5g[Nre>ADUR1`K<y7EM(I!Q8UPobSO/j,>>' );
Change the salt to your specific 32 character random string.
Addendum
It would be nice if the salt generator on wordpress.org gave us one of these too without having to reload and use another one as the cache key salt.
Until they do, you can use the one I wrote. That one gives you a salt for the cache salt too :).
- https://wpsaltdotenv.herokuapp.com/ for copying into the
wp-config.php
file, and - https://wpsaltdotenv.herokuapp.com/env for copying into your
.env
file
Photo by Chris Sabor on Unsplash