Automattic / wp-cache-memcached

Memcached Object Cache for WordPress
6 stars 0 forks source link

Store alloptions in runtime cache even after an add() failure #6

Open WPprodigy opened 1 year ago

WPprodigy commented 1 year ago

The Problem

A) If the memcached server where the alloptions cache entry is stored is under duress, rebooting, or perhaps just has some pages temporarily locked up, it often causes a full scale outage on a site.

B) Similarly, if the alloptions value exceeds 1mb when compressed it will also pretty much always cause a full outage on the site.

This is because WP really really relies on having this particular db query cached, else it will continually try over and over again to both query for fresh results and attempt to add() it back to the cache. This happens every time get_option() and such is called, resulting in 1000s of SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'; (and thousands more of memcached get()s and add()s) on each request. Which is usually more than enough to bork a high traffic site.

Solution

If a memcached add() fails for alloptions, we'll force lookup the key once more to see if it exists or not (is possible another request won the race and successfully did set the value). If the key still isn't there though, then something must be wrong and we'll just save the fresh DB results to the runtime cache anyway.

On a default WP install, this means there will only be 1 alloptions DB query per request rather than over a thousand. And the results being saved to the runtime cache are from a fresh DB query anyway.

The only risk is if the results are slightly stale due to coming from a read replica, but such race conditions exist anyway with the options API and we can't really prevent all those cases as the only real way to do that is to not being doing frontend options writes without some solid locking and concurrency prevention in place.

Part of this risk is already prevented though as core will do a "forced get" of alloptions when there are option writes going on. And that will effectively flush the runtime cache of this value anyway and result in a new DB query and lookup. Core then uses wp_cache_set() to update alloptions with the fresh value that was just added/updated, which also of note will store to runtime cache whether or not the remote set() even succeeds: https://github.com/Automattic/wp-cache-memcached/blob/develop/includes/wp-object-cache.php#L293-L301

So really, we're just making add() work the same as set() for this alloptions use case.