Repository navigation
Features: Load all feature toggles with one query - #1113
Conversation
Loader::initialize_features() asks every feature is_enabled(), which reads
wpai_feature_{id}_enabled with get_option(). The option has no row until the
feature is switched on for the first time, so on sites without a persistent
object cache every never-enabled feature cost one query on every request (21
on a fresh install).
The toggle names are now passed to wp_prime_option_caches() before the loop,
which loads them with a single query. The call is skipped when a persistent
object cache is in use, where core already remembers missing options between
requests.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1113 +/- ##
=============================================
+ Coverage 81.60% 81.61% +0.01%
- Complexity 3082 3083 +1
=============================================
Files 129 129
Lines 12282 12289 +7
=============================================
+ Hits 10023 10030 +7
Misses 2259 2259
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
I'd say handle that in a new PR and I'll merge this one. Thanks! |
What?
Closes #1112
Loads all feature toggles with one query when the features are initialized, instead of one query for each feature that has never been switched on.
Why?
On a site without a persistent object cache,
Loader::initialize_features()runs a separate query on every request for each feature whosewpai_feature_{id}_enabledoption has no row yet. A fresh install has 21 features, so that is 21 queries, a little under half of all queries on a default home page. Numbers and details are in #1112.How?
initialize_features()passes all toggle names towp_prime_option_caches(). Core loads the ones it does not have yet with a singleSELECT ... WHERE option_name IN (...)and remembers the missing ones for the rest of the request. Theget_option()calls inis_enabled()then do not query.is_enabled()still reads the same options throughget_option(), with the same filters. The names come from the registry, so features added throughwpai_register_featuresare covered too.MGETwith 21 keys on every request without the check, none with it.wpai_markdown_feeds_flush_rewriteandwpai_key_encryption_resume_migrationare still looked up one by one on every request. The Loader does not know the options of single experiments, so adding them needs a small design decision. I can add them here or in a follow-up, whichever you prefer.Queries per request on a fresh install with nothing switched on (MariaDB 12.3.3, WordPress 7.1.2, no persistent object cache):
develop/?rest_route=/)wp-login.phpadmin-ajax.phpwp-cron.phpIt is the same 20 fewer queries on SQLite with WordPress 7.1. With three features enabled the home page goes from 44 to 27.
Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code and Codex
Used for: Investigation, implementation, tests, measurements, and PR wording. I reviewed the reasoning and test results, and I take responsibility for the contribution.
Testing Instructions
wp-content/mu-plugins/count-ai-option-queries.php. It writes one line per request to the PHP error log.developthe line shows one lookup for each never-enabled feature, plus 2. With this PR it shows 2.Automated:
npm run test:php -- --filter test_feature_toggles_areruns the two new tests.test_feature_toggles_are_loaded_with_a_single_queryfails ondevelop(21 queries, 1 expected) and passes here. The other one checks that nothing is primed when a persistent object cache is in use.composer lintand PHPStan pass.Changelog Entry