A Story of WP Option Autoload: A wp_options Cleanup

Today I want to dive into a kind of esoteric topic: WP option autoloading. It’s not something a lot of WordPress developers are likely to hit or to need. But vague knowledge of this topic saved me a bunch of time and confusion on a client project, so I want to help you understand how casual use of the update_option function without understanding WordPress’s autoload options feature can cause you pain and heartache.

A Quick Summary of WP Options, Autoload, and Cleanup

Before I dive into my story, I want to offer some definitions to help my story make more sense:

  • wp_options is a table in the WordPress database. In general, WordPress developer will call values stored here “options”, without adding much more detail. These “options” are for a plugin or theme to store user-controllable settings about their functionality. So if you’ve got the ability to store an address of your ecommerce store, that’ll typically stored in a row in the wp_options table.
  • update_option is the WordPress function most often used to set and update options. It’s relatively easy to use: provide a name for your option, and then the value you want WordPress to store for it.
  • “Transients” which are tangential to this story, but a common issue in general goals of WP Options cleanup, are short-lived values in the options table in some WordPress sites. Transients are used when WordPress doesn’t have more complete “object caching” available.
  • “Object caching” is when WordPress stores intermediates in your an in-memory data store–like Redis or Memcache(d)–or in WordPress transients. These improve performance by storing the result, for example, of an expensive database query so WordPress doesn’t have to do a full recalculation every time.
  • WordPress options can be set to “autoload.” As you may surmise from the name, that means that WordPress will always and without exception have loaded this option for you on all page loads. Why and when you might not want this is something we’ll get to in my story.
  • get_option is the WordPress function most often used to fetch options that are stored in wp_options table. The main benefit of this function is that it can use the object-cached value rather than making a new database query, especially for autoloaded options.

With all those facts in our head, time to tell my story. Don’t worry too much if all those facts didn’t fully take, we’ll re-cover important facets of these details as they become relevant.

My Story About WP Option Autoload

So, I have a client (who I will not name here) with a big WordPress site. They’ve had some small performance problems on their site for months, if not years. They get a fairly large amount of traffic, and are pay a lot for web hosting in order to assure their large site with lots of content still performs.

Their WordPress site is quite old, and it’s also rather strange. They are a publication that started life using a custom Python CMS, and then had those Python developers (more or less) build them a WordPress site. A Python developer’s first WordPress site will have made at least a few uncommon choices compared to WordPress development norms.

So they put a whole wallop of functionality in the WordPress theme. (A problem so common I wrote against it years ago as “theme creep.”) The theme is using Laravel’s Blade templating system, and more importantly also has a lot of other functionality for the site: advertisement decisioning choices, much of the AMP support code, and the migration functionality. That migration functionality is code they used to port the site from their old custom thing into WordPress.

How We Found a Problem

This site has never been blazingly fast. It’s an enormous site with thousands and thousands of articles. While it isn’t at the edge of WordPress’s capabilities, it sometimes feels close to it.

And they’d noticed that it had started behaving even worse in the last few months. There were random 502 error, and just general slowness. A teammate reached out to hosting support, who recommended a strange piece of advice: disable object caching. Remember: object caching is the WordPress name for using an in-memory cache layer (typically Redis of Memcached) to store WordPress intermediates, like common database queries and also the WP Options autoload set. It generally makes things faster. But indeed, turning it off seemed to help.

But why? Why would turning off a feature that’s supposed to speed up WordPress make it faster?

What was happening is that the host’s object caching (for reasonable performance reasons) rejects “objects” that are over 1MB in size. And this website had recently crossed the boundary, where a very important object to cache had become 1.1MB in size. For those who don’t speak file sizes, a string of text is usually a few dozen bytes. 1000 (or 1024) bytes is a kilobyte, or KB. 1000 (or 1024) kilobytes is a megabyte, or MB. So there was something that was quite big.

WordPress would build up an object representing all the WP-Options which are set to autoload, then try to push that to the object caching layer. When the object-caching-layer would then reject the object, WordPress would then recalculate this value. The value in question: set of autoloaded options for the whole site. So rather than looking it up once per page load, WordPress looked it up and loaded it twice. And as we discussed, it was also pathologically large.

The Bad Migration Code

The specifics of what was happening was revealed when the support team looked into our wp_options table with a little database query:

SELECT LENGTH(option_value), 
     option_name FROM wp_options 
     WHERE autoload=β€˜yes’ 
     ORDER BY length(option_value) DESC 
     LIMIT 20;

The details of this SQL syntax aren’t particularly important. What it does is finds the longest (largest size) values in your wp_options table which are set to autoload.

What we found was both confusing and obvious. We had on a single autoloaded option in our WordPress database that was nearly 900KB in size. For photo of video files this isn’t huge. For text or simple database values it was confusingly massive.

So I dug into what this single pathologically large value was. I found the code populating the value: a single function in the WordPress theme that was made to do the site’s data migration all those years ago. What’s more, something about this code was causing it to run intermittently, continually growing the size of the wp_option value as the site lived longer. The exact reason for the growth wasn’t clear, and remains unclear. (We’ve been planning for months to retire this historic and “technical debt”-laden theme, so making it make sense is of small business value to us.)

The Solution: Turn off Autoload for this Pathological Option

I dug enough into this code to understand one thing: it wasn’t actually doing anything particularly useful to the site’s normal functioning. What’s more, the option it was updating was just getting filled with extra junk data. That is, rather than storing useful data, all that was being stored was a PHP array that was regularly getting new empty key-value pairs pushed into it. Which is to say: useless junk.

So the solution was pretty simple: just make that row in the WordPress wp_options database table not have the value “yes” for autoload for this hilariously large value.

What this cleanup of the wp_options table did was make it so that we could turn back on object caching for the site, and get the intended performance gains. So it was a win-win: we made the site have less cache problems. And getting rid of those issues meant re-enabling the cache it would also be faster.

Lessons this Taught Me About WP Options Clean-up

To state a thing you may have realized by now, this is not a definitive guide to WP-Options cleanup. If you’re looking for a complete guide to WP Options Cleanup, check out this post from Kinsta.

Rather I’m just sharing a story about how I think WordPress performance and the autoload value in wp_options surprised me, in the hope that some day it may help you.

I’d like to highlight the specific lessons that this taught me about managing WordPress options:

  1. Keep your options small. This one is common practice. But seeing a serialized array in the WordPress database with a few thousand keys really convinced me of the importance of using simple and small data-sets in your WP-Options table.
  2. Think critically about autoload when calling update_option. What this means is that when you’ve got a rarely used option (say something that only actually gets looked up on 1-in-20 pageloads on your WordPress site), don’t make it autoload. You’d do this pretty simply by changing a basic update_option( 'my_option', 'value' ); to be update_option( 'my_option', 'value', 'no' );. (If you don’t specify a value for the third $autoload parameter, update_optiondefaults to 'yes'.)
  3. Don’t put migration plugin in a theme. Adding migration code to a WordPress theme is the very definition of what I meant by “WordPress theme creep.” Put that code in a plugin that you disable and remove as soon as you’ve completed the migration. No need to keep it around, running all the time, when it’s not supposed to be doing anything. (And it’s doing something will surprise and confuse the site administrators in three years time.)

I’m sure one can draw other lesson in this story. That fact that our host’s support knew about and understood the issue is a little frustratingly rare. Which is why it’s always important to keep abreast of the best WordPress hosting.

And that’s how WP Options Autoloading Works

I hope I helped you come to grips with a little more of the weird details of how WP Options handle being autoloading, and a pathological case that may happen when you have too many options being autoloading so you overrun the acceptable size for your object cache.

Like I said, I might have been deeply confused by this report if I wasn’t already a little familiar with the fact that wp_option autoloading can cause unexpected problems.

Good luck out there, WordPress developers πŸ™‚


Add a Comment

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