Commit cc95e1cba3b for woocommerce
commit cc95e1cba3b833dde92b56a839b579c261bb2e5d
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date: Mon Jun 22 07:40:36 2026 +0200
[Performance] Prime options for feature flags and bundled gateways settings (#65883)
diff --git a/.ai/skills/woocommerce-performance/options-cache-priming.md b/.ai/skills/woocommerce-performance/options-cache-priming.md
index d63925216ce..1a3737eaa32 100644
--- a/.ai/skills/woocommerce-performance/options-cache-priming.md
+++ b/.ai/skills/woocommerce-performance/options-cache-priming.md
@@ -77,7 +77,32 @@ Guard with `! empty()` when the list is dynamically built and may be empty. When
---
-### 4. Transient names passed to `wp_prime_option_caches()` — unsafe under persistent object cache
+### 4. Registry / definition-array based priming at init time
+
+**Apply when:** A class maintains a registry (array) of entities whose option key is derivable from the entry — either stored as an explicit `option_key` field or computable from the entry's ID using a known naming convention.
+
+**Correct pattern:**
+
+```php
+// Prime options caches to reduce future queries (for non-existing yet or non-autoloaded options).
+wp_prime_option_caches(
+ array_map(
+ static fn( $id, $def ) => $def['option_key'] ?? "woocommerce_feature_{$id}_enabled",
+ array_keys( $this->registry ),
+ $this->registry
+ )
+);
+```
+
+Place the call at the end of the method that populates the registry, before any code that reads from it. This ensures a single batch query covers all entries regardless of which specific entry triggers the first read.
+
+`wp_prime_option_caches()` skips entries already in `alloptions` (autoloaded options loaded at WordPress boot) — the resulting SQL `WHERE option_name IN (...)` contains only the non-autoloaded or not-yet-existing subset. This is expected and correct: the SQL appearing in query monitors will show a subset of the full registry, not all entries.
+
+**Real-world example:** `FeaturesController::init_feature_definitions()` — after registering all feature definitions, primes all `woocommerce_feature_{id}_enabled` keys (and any custom `option_key` overrides) in one call. Without this, each `feature_is_enabled()` call throughout the request issues its own individual `SELECT`.
+
+---
+
+### 5. Transient names passed to `wp_prime_option_caches()` — unsafe under persistent object cache
**Anti-pattern:** Passing `_transient_*`, `_transient_timeout_*`, `_site_transient_*`, or `_site_transient_timeout_*` option names to `wp_prime_option_caches()`.
@@ -153,13 +178,11 @@ High `get_option()` concentration alone is **not** a signal. These are common fa
All three entity types extend `WC_Settings_API`, which saves settings with `autoload='yes'`. Once saved, these options are already in cache. However, on a fresh install or before settings are first saved, they are absent from `wp_load_alloptions()` — each `get_option()` issues an individual query. Priming is justified here specifically for the existence dimension (batching those misses), particularly when looping over a large number of entities such as email classes.
-The four built-in payment gateways are a negligible count and are skipped.
-
| Location | Pattern | Status |
| --- | --- | --- |
| `includes/class-wc-emails.php` — `init()` | array_map over email class list | ✅ covered — batches miss queries on fresh/unconfigured installs |
| `includes/class-wc-shipping.php` — `get_shipping_method_class_names()` | array_map over method ID list | ✅ covered — same rationale |
-| `includes/class-wc-payment-gateways.php` — `init()` | 4 built-in gateways — negligible count | ✅ verified, skipped |
+| `includes/class-wc-payment-gateways.php` — `init()` | 5 known option keys for 4 built-in gateways | ✅ covered — same rationale |
### Workflow for gap analysis
diff --git a/plugins/woocommerce/changelog/performance-features-and-gateways-options-priming b/plugins/woocommerce/changelog/performance-features-and-gateways-options-priming
new file mode 100644
index 00000000000..5603ef0085e
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-features-and-gateways-options-priming
@@ -0,0 +1,4 @@
+Significance: patch
+Type: performance
+
+Reduced the number of SQL queries on the cart and checkout pages in the store, as well as on admin pages, by adding cache priming.
diff --git a/plugins/woocommerce/includes/class-wc-payment-gateways.php b/plugins/woocommerce/includes/class-wc-payment-gateways.php
index be1925ca5bb..baac33a4d4f 100644
--- a/plugins/woocommerce/includes/class-wc-payment-gateways.php
+++ b/plugins/woocommerce/includes/class-wc-payment-gateways.php
@@ -91,7 +91,16 @@ class WC_Payment_Gateways {
// Filter.
$load_gateways = apply_filters( 'woocommerce_payment_gateways', $load_gateways );
- // No wp_prime_option_caches needed: gateway settings are autoloaded (WC_Settings_API saves with autoload='yes').
+ // Preload option caches to minimize future queries for options that do not yet exist or are not set to autoload.
+ wp_prime_option_caches(
+ array(
+ 'woocommerce_bacs_settings',
+ 'woocommerce_bacs_accounts',
+ 'woocommerce_cheque_settings',
+ 'woocommerce_cod_settings',
+ 'woocommerce_paypal_settings',
+ )
+ );
// Get sort order option.
$ordering = (array) get_option( 'woocommerce_gateway_order' );
diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
index 76cb1a11d4b..fa7b6facd16 100644
--- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php
+++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php
@@ -684,6 +684,15 @@ class FeaturesController {
$this->add_feature_definition( $slug, $definition['name'], $definition );
}
+ // Preload option caches to minimize future queries for options that do not yet exist or are not set to autoload.
+ wp_prime_option_caches(
+ array_map(
+ static fn( $slug, $definition ) => $definition['option_key'] ?? sprintf( 'woocommerce_feature_%s_enabled', $slug ),
+ array_keys( $this->features ),
+ $this->features
+ )
+ );
+
$this->init_compatibility_info_by_feature();
}