Commit 4a340aa901 for woocommerce
commit 4a340aa901f086c0ef84f8c54da2a593a8954988
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date: Tue Feb 10 10:33:40 2026 +0100
[Performance] Orders: detach legacy reports transients invalidation from order persistence (async) (#63118)
Reduces the number of SQLs (-19%, when object cache is not enabled) on the checkout page by deferring the transient deletion of legacy reports (part of the order cache clean) for 1 minute and ensuring only a single event is scheduled at a time.
diff --git a/plugins/woocommerce/changelog/performance-63110-clean-legacy-reporting-transients-async b/plugins/woocommerce/changelog/performance-63110-clean-legacy-reporting-transients-async
new file mode 100644
index 0000000000..ef415b8e2a
--- /dev/null
+++ b/plugins/woocommerce/changelog/performance-63110-clean-legacy-reporting-transients-async
@@ -0,0 +1,4 @@
+Significance: minor
+Type: performance
+
+Performance: separate the transient deletion of legacy reports from the order cache refresh and execute these operations asynchronously.
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-reports.php b/plugins/woocommerce/includes/admin/class-wc-admin-reports.php
index a1d396558c..b926a3bdd9 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-reports.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-reports.php
@@ -24,13 +24,57 @@ if ( class_exists( 'WC_Admin_Reports', false ) ) {
class WC_Admin_Reports {
/**
- * Register the proper hook handlers.
+ * Register the hook handlers for integrating with admin.
*/
public static function register_hook_handlers() {
add_filter( 'woocommerce_after_dashboard_status_widget_parameter', array( __CLASS__, 'get_report_instance' ) );
add_filter( 'woocommerce_dashboard_status_widget_reports', array( __CLASS__, 'replace_dashboard_status_widget_reports' ) );
}
+ /**
+ * Register the hook handlers for integrating with orders.
+ *
+ * @internal
+ * @since 10.6.0
+ */
+ public static function register_orders_hook_handlers(): void {
+ add_action( 'woocommerce_delete_shop_order_transients', array( __CLASS__, 'delete_legacy_reports_transients' ), 10, 1 );
+ add_action( 'woocommerce_delete_legacy_report_transients', array( __CLASS__, 'delete_legacy_reports_transients' ), 10, 2 );
+ }
+
+ /**
+ * Execute legacy reports transient deletion (sync or async depending on the context)
+ *
+ * @internal
+ * @since 10.6.0
+ *
+ * @param int $order_id Order ID (unused, exists for compatibility between the hooks we are integrating with).
+ * @param bool $defer Whether to defer the deletion or execute.
+ * @return void
+ */
+ public static function delete_legacy_reports_transients( int $order_id, bool $defer = true ): void {
+ // Deferring is only making sense on sites without object cache enabled (if enabled, no SQLs being executed).
+ if ( $defer && ! wp_using_ext_object_cache() ) {
+ static $skip_consequent;
+
+ // Schedule the deletion, cap the execution to single pending event at any given time.
+ $schedule = ! $skip_consequent && ! as_has_scheduled_action( 'woocommerce_delete_legacy_report_transients', null, 'woocommerce' );
+ if ( $schedule ) {
+ as_schedule_single_action( time() + MINUTE_IN_SECONDS, 'woocommerce_delete_legacy_report_transients', array( $order_id, false ), 'woocommerce' );
+ }
+ $skip_consequent = true;
+
+ return;
+ }
+
+ delete_transient( 'wc_admin_report' );
+ foreach ( self::get_reports() as $report_group ) {
+ foreach ( $report_group['reports'] as $report_key => $report ) {
+ delete_transient( 'wc_report_' . $report_key );
+ }
+ }
+ }
+
/**
* Get an instance of WC_Admin_Report.
*
diff --git a/plugins/woocommerce/includes/class-woocommerce.php b/plugins/woocommerce/includes/class-woocommerce.php
index c81a67655e..4f104cd65c 100644
--- a/plugins/woocommerce/includes/class-woocommerce.php
+++ b/plugins/woocommerce/includes/class-woocommerce.php
@@ -399,6 +399,9 @@ final class WooCommerce {
$container->get( Automattic\WooCommerce\Internal\ProductFilters\MainQueryController::class )->register();
$container->get( Automattic\WooCommerce\Internal\ProductFilters\CacheController::class )->register();
+
+ // Integration point between legacy reports and orders APIs (the reports caches invalidation focused).
+ \WC_Admin_Reports::register_orders_hook_handlers();
}
/**
diff --git a/plugins/woocommerce/includes/wc-order-functions.php b/plugins/woocommerce/includes/wc-order-functions.php
index 792fc19326..9a2dcaae20 100644
--- a/plugins/woocommerce/includes/wc-order-functions.php
+++ b/plugins/woocommerce/includes/wc-order-functions.php
@@ -502,29 +502,15 @@ function wc_delete_shop_order_transients( $order = 0 ) {
if ( is_numeric( $order ) ) {
$order = wc_get_order( $order );
}
- $reports = WC_Admin_Reports::get_reports();
- $transients_to_clear = array(
- 'wc_admin_report',
- );
-
- foreach ( $reports as $report_group ) {
- foreach ( $report_group['reports'] as $report_key => $report ) {
- $transients_to_clear[] = 'wc_report_' . $report_key;
- }
- }
-
- foreach ( $transients_to_clear as $transient ) {
- delete_transient( $transient );
- }
// Clear customer's order related caches.
+ $order_id = 0;
if ( is_a( $order, 'WC_Order' ) ) {
- $order_id = $order->get_id();
- Users::delete_site_user_meta( $order->get_customer_id(), 'wc_money_spent' );
- Users::delete_site_user_meta( $order->get_customer_id(), 'wc_order_count' );
- Users::delete_site_user_meta( $order->get_customer_id(), 'wc_last_order' );
- } else {
- $order_id = 0;
+ $order_id = $order->get_id();
+ $customer_id = $order->get_customer_id();
+ Users::delete_site_user_meta( $customer_id, 'wc_money_spent' );
+ Users::delete_site_user_meta( $customer_id, 'wc_order_count' );
+ Users::delete_site_user_meta( $customer_id, 'wc_last_order' );
}
// Increments the transient version to invalidate cache.
diff --git a/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-reports-test.php b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-reports-test.php
new file mode 100644
index 0000000000..ec90423d76
--- /dev/null
+++ b/plugins/woocommerce/tests/php/includes/admin/class-wc-admin-reports-test.php
@@ -0,0 +1,28 @@
+<?php
+
+declare( strict_types = 1);
+
+/**
+ * Tests for `WC_Admin_Reports` class.
+ */
+final class WC_Admin_Reports_Test extends WC_Unit_Test_Case {
+ /**
+ * Verify the workflows execution in `delete_legacy_reports_transients`.
+ */
+ public function test_delete_legacy_reports_transients(): void {
+ // Verify the integration point invocation.
+ $this->assertSame( 10, has_action( 'woocommerce_delete_shop_order_transients', array( \WC_Admin_Reports::class, 'delete_legacy_reports_transients' ) ) );
+ $this->assertTrue( has_action( 'woocommerce_delete_legacy_report_transients' ) );
+
+ // Verify the defer-workflow: nov verifying for pending AS action as other tests already triggered the deferred workflow.
+ // Accordingly, we can only verify that we entered into defer-workflow + rely on manual testing for this PR.
+ set_transient( 'wc_admin_report', 'Verify defer' );
+ \WC_Admin_Reports::delete_legacy_reports_transients( 0, true );
+ $this->assertSame( 'Verify defer', get_transient( 'wc_admin_report' ) );
+
+ // Verify the purge-workflow.
+ set_transient( 'wc_admin_report', 'Verify deletion' );
+ do_action( 'woocommerce_delete_legacy_report_transients', 0, false ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
+ $this->assertFalse( get_transient( 'wc_admin_report' ) );
+ }
+}