How 12MB of Autoloaded Options Were Adding 3 Seconds to Every Page Load
· 8 min read
A client's WordPress site had a problem that crept in slowly. Page load times had drifted from under a second to over four seconds over the past year. Nothing obvious had changed -- no new plugins, no traffic spike, no server issues. The hosting was solid, PHP 8.2 on a well-configured VPS with Redis object cache already in place.
The Redis cache was the first clue. Despite having object caching enabled, the alloptions cache key was enormous. I noticed it during a routine check with wp redis info -- the memory usage was far higher than it should have been for a site with around 200 published pages.
The investigation
I SSH'd in and checked the total size of autoloaded data in wp_options:
SELECT ROUND(SUM(LENGTH(option_value)) / 1024 / 1024, 2) AS autoload_mb
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on');
+-------------+
| autoload_mb |
+-------------+
| 12.41 |
+-------------+
12.4MB. On every single page load, WordPress was pulling 12.4MB of data from wp_options into memory before rendering anything. For context, a healthy WordPress site should have under 800KB of autoloaded data. Anything over 2MB is a performance problem.
This matters because WordPress loads every autoloaded option on every request -- front-end, admin, AJAX, REST API, WP-Cron. It's one of the first database queries that runs. On this site, that single query was returning 12MB of data, which then had to be unserialized and stored in PHP memory. With Redis enabled, the data was cached in memory, but the initial cache prime was expensive, and the serialized blob was still consuming a significant chunk of Redis's allocated memory.
Finding the biggest offenders
I ran a query to find the largest autoloaded options:
SELECT option_name,
ROUND(LENGTH(option_value) / 1024, 2) AS size_kb,
autoload
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on')
ORDER BY LENGTH(option_value) DESC
LIMIT 25;
The results painted a clear picture:
+-----------------------------------------------+---------+----------+
| option_name | size_kb | autoload |
+-----------------------------------------------+---------+----------+
| _flavor_settings_backup_20240312 | 3841.22 | yes |
| flavor_css_cache | 2048.67 | yes |
| flavor_google_fonts_cache | 1536.44 | yes |
| flavor_settings | 1024.18 | yes |
| rewrite_rules | 892.31 | yes |
| flavor_custom_css | 645.90 | yes |
| flavor_widget_areas | 512.33 | yes |
| flavor_dynamic_styles | 384.71 | yes |
| flavor_theme_mods | 256.88 | yes |
| wpseo_taxonomy_meta | 187.42 | yes |
| flavor_sidebar_config | 128.55 | yes |
| flavor_shortcode_cache | 96.44 | yes |
+-----------------------------------------------+---------+----------+
The top eight entries were all from "Flavor" -- a page builder theme that had been replaced over a year ago. The theme was no longer active, but its data was still sitting in wp_options with autoload=yes, being loaded into memory on every request. The settings backup alone was 3.8MB.
This is one of the most common problems I see on WordPress sites that have been through a theme or plugin change. When you deactivate a theme or plugin, WordPress doesn't clean up its options. The data stays in wp_options, and if those options were created with autoload=yes, they continue to be loaded on every page view forever.
The fix
The fix is straightforward, but you need to be careful. You don't want to delete options that are still needed, and you don't want to accidentally break something by flipping autoload on an option that genuinely needs it.
Step 1: Disable autoload on abandoned options
For options from deactivated plugins and themes, I switched autoload to no rather than deleting them outright. This way the data is still recoverable if needed:
UPDATE wp_options
SET autoload = 'no'
WHERE option_name LIKE 'flavor_%'
OR option_name LIKE '_flavor_%';
I verified the affected rows first with a SELECT COUNT(*) using the same WHERE clause -- 23 rows matched, all from the deactivated theme.
Step 2: Clean up expired transients
Transients should expire and self-clean, but on sites where WP-Cron isn't running reliably, expired transients accumulate in wp_options with autoload enabled:
wp transient delete --expired --network
This removed another 340 expired transients, freeing up about 1.2MB.
Step 3: Trim the rewrite_rules option
The rewrite_rules option was 892KB, which is unusually large. This happens when plugins register hundreds of custom post types or REST API endpoints and never clean them up. After confirming the deactivated plugins were responsible for the extra rules, I flushed and regenerated:
wp rewrite flush
This brought rewrite_rules down from 892KB to 86KB.
Step 4: Flush the object cache
After modifying wp_options, you need to flush the Redis cache so it picks up the changes:
wp cache flush
Checking the results
After the cleanup, I re-ran the autoload size query:
SELECT ROUND(SUM(LENGTH(option_value)) / 1024 / 1024, 2) AS autoload_mb
FROM wp_options
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on');
+-------------+
| autoload_mb |
+-------------+
| 0.64 |
+-------------+
From 12.4MB down to 640KB -- well within the healthy range. Page load times dropped from 4.2 seconds to 1.8 seconds on the front end, and the admin dashboard went from sluggish to snappy.
A note on WordPress 6.6+ autoload changes
If you're running WordPress 6.6 or later, the autoload system has changed. The old yes/no values have been expanded to a more granular set: on, off, auto, auto-on, and auto-off. WordPress now attempts to dynamically determine whether new options should be autoloaded, and will automatically disable autoload for options with very large values.
This is a good improvement, but it only applies to newly created or updated options. Existing options from before the upgrade still use the old yes/no values and won't be automatically optimised. If your site has been running for years, you still need to audit manually.
When checking autoloaded data size, make sure your query accounts for all the autoloading values:
WHERE autoload IN ('yes', 'on', 'auto', 'auto-on')
Using just WHERE autoload = 'yes' will miss options created under the new system.
Setting up ongoing monitoring
I added a WP-CLI command to the site's weekly maintenance script to catch autoload bloat before it becomes a problem:
#!/bin/bash
# check-autoload-size.sh
AUTOLOAD_KB=$(wp db query "
SELECT ROUND(SUM(LENGTH(option_value)) / 1024, 0)
FROM wp_options
WHERE autoload IN ('yes','on','auto','auto-on');
" --skip-column-names --path=/var/www/html 2>/dev/null)
if [ "$AUTOLOAD_KB" -gt 1024 ]; then
echo "WARNING: wp_options autoloaded data is ${AUTOLOAD_KB}KB (threshold: 1024KB)"
# Log the top offenders
wp db query "
SELECT option_name, ROUND(LENGTH(option_value)/1024, 1) AS kb
FROM wp_options
WHERE autoload IN ('yes','on','auto','auto-on')
ORDER BY LENGTH(option_value) DESC
LIMIT 10;
" --path=/var/www/html 2>/dev/null
fi
The bigger picture
This kind of bloat is invisible unless you look for it. The site still works, pages still render, no errors appear in the logs. It just gets a little slower every time a plugin is installed, tested, and deactivated without cleanup. Over a year or two, the wp_options table accumulates megabytes of orphaned data that WordPress dutifully loads on every single request.
If you're managing WordPress sites, add an autoload size check to your maintenance routine. It takes 30 seconds to run the query and five minutes to fix when you catch it early. Left unchecked, it can cost you seconds on every page load -- and your visitors won't wait around for that.
Stop Firefighting. Start Maintaining.
I manage 70+ WordPress sites for UK agencies and businesses. Autoload bloat is one of the silent performance killers that routine maintenance catches before your visitors notice. Whether you need ongoing maintenance, emergency support, or a one-off database optimisation -- I can help.
