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();
 	}