[wp-trac] [WordPress Trac] #31245: Replace alloptions with a key cache
WordPress Trac
noreply at wordpress.org
Fri Feb 6 05:39:48 UTC 2015
#31245: Replace alloptions with a key cache
--------------------------------+-----------------
Reporter: rmccue | Owner:
Type: enhancement | Status: new
Priority: normal | Milestone: 4.2
Component: Options, Meta APIs | Version:
Severity: normal | Keywords:
Focuses: performance |
--------------------------------+-----------------
Let's talk about `alloptions`. `alloptions` is a particularly fragile
piece of WordPress, since it's exceptionally vulnerable to accidental
concurrency issues due to its design.
A primer on `alloptions`, for those who haven't dug into it: `alloptions`
is a cache key that stores all the options marked with autoload, thereby
reducing the number of external calls out to an object cache from hundreds
down to 1. It does this by running `SELECT * FROM wp_options WHERE
autoload = 'yes'`, then storing that array (map of `option_name =>
option_value`) under the `alloptions` cache key. (Non-autoloaded options
are stored as individual cache items instead, with a key of `option_name`
in the `options` group.)
When you add an option, you can pick the `autoload` flag. This flag is
`'yes'` (`true`) '''by default''', meaning most calls to `add_option` will
set options to autoload. Additionally, if you add an option via
`update_option`, there's no way to set this flag. These combined mean that
the vast majority of options are stored in the `alloptions` cache.
== Why is this an issue? ==
The `alloptions` cache is loaded very early in the WordPress request
lifetime. From this moment until the end of a request, option reads are
taken from this cache rather than the database.
In addition, every time an autoloaded option is updated or deleted,
`alloptions` is similarly updated. However, this means that on '''any'''
autoloaded option being updated, '''every''' autoloaded option has its
value set to the value at load time.
This is a huge concurrency bug, as it's very easy to run into
accidentally. As a proof of concept, try updating two different options in
two different requests at the same time, and you'll see that whichever ran
first will lose the changes. (See #25623)
(We're running into this issue a few times a week, due to certain plugins
running an `update_option` on a high percentage of requests. These plugins
are obviously being dumb in doing so, but they're revealing the core bug
quite nicely.)
== How do we fix this? ==
The biggest issue is that `alloptions` is a single cache item containing
every option. If we reduce this to one cache item per option, the
concurrency problem is then reduced to individual options (and can be
mitigated in the places it actually matters, typically in plugins). Rather
than storing all the options, we can instead cache the keys that matter,
then grab the values for all of those at once.
This means that we may still have minor concurrency issues when adding and
removing options, however these happen much less often than updating
existing ones. In addition, the cache would now only be used to decide
which options to load at the start of the request, so worst case scenario,
you end up with extra object cache `get` calls. This is much better than
the current case, where a concurrency problem can return incorrect data.
This change would require adding a `wp_cache_get_multi` to take full
advantage of the cache, however compatibility could be shimmed here with a
loop and individual calls to `wp_cache_get`. This '''would''' cause worse
performance for object caches that don't support multi-get (or haven't
been updated), but at the benefit of no longer returning potentially bad
data.
There's a few things that need to be done for this:
* Add `wp_cache_multi_get`, and talk to object cache maintainers to update
the popular ones to support it (Memcache, APC)
* Change `wp_load_alloptions` to pull from `alloptionskeys`, then run a
multi-get on those
* Ensure `alloptionskeys` is a protected option name
* Store value caches per-key
--
Ticket URL: <https://core.trac.wordpress.org/ticket/31245>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform
More information about the wp-trac
mailing list