Skip to content
This repository was archived by the owner on May 21, 2025. It is now read-only.

Performance - Reduce unnecessary database reads in get_related_orders() when fetching multiple order types #790

Conversation

james-allan
Copy link
Contributor

@james-allan james-allan commented Feb 20, 2025

Part of https://github.com/woocommerce/woocommerce-subscriptions/issues/4795

Description

This PR reduces the number of duplicate queries running on the WooCommerce → Subscriptions list table, decreasing them from 137 to 117 (-20). It focuses on the $subscription->get_related_orders() flow and improvements to the WCS_Related_Order_Store. It therefore has broader impacts to the overall performance - more than just the list table.

Previously, calling $subscription->get_related_orders() with multiple order types would lead to multiple WCS_Related_Order_Store::instance()->get_related_order_ids() calls.

Whenever you fetch a subscription's related order cache, you do a direct read on the database.

So for example, this code snippet would lead to three full subscription metadata reads -- One for each order type (renewal, switch, resubscribe).

$subscription->get_related_orders( 'ids', 'any' );

This PR reduces that to just one read.

To achieve that, this PR makes the following changes:

  1. Adds a new WCS_Related_Order_Store::get_related_order_ids_by_types() function, designed to fetch multiple related order lists from the cache in a single read.
    • It uses static variables to ensure the subscription’s meta is read only once per function execution, storing it in memory for reuse when retrieving subsequent order types.
    • This approach uses a function-scoped cache, meaning the cached data is limited to the function's execution and does not persist across requests, preventing unintended cache pollution.
  2. Refactors WC_Subscription::get_related_order_ids() so it can fetch multiple order types - not just one at a time.
  3. Updates WC_Subscription::get_related_orders() to make use of this new function.

How to test this PR

  1. Install Query Monitor.
  2. Go to WooCommerce → Subscriptions
  3. Open the Query Monitor window and go to Database Queries → Duplicate Queries
  4. Note the number of duplicate queries.
  5. Each subscription in the list table is causing 6 calls to get_related_order_metadata()
  6. Checkout this branch and repeat the steps above.
  7. The number of duplicate queries should drop by roughly 1 x the number of subscriptions you
    have in the list table.
    • eg with 20 subscriptions in the list table this should remove ~20 duplicates.
    • Note: This is because on the list table we count the number of renewal and switch orders for each subscription which is 2 reads. This PR reduces that to 1.
  8. There should be no difference in content or functionality on the list table.
    • Note: this change is specifically related to the last column -- the "Orders" column.

Product impact

  • Added changelog entry (or does not apply)
  • Will this PR affect WooCommerce Subscriptions? yes/no/tbc, add issue ref
  • Will this PR affect WooCommerce Payments? yes/no/tbc, add issue ref
  • Added deprecated functions, hooks or classes to the spreadsheet

@james-allan james-allan changed the title Improve the performance of fetching multiple related order types Performance - Reduce unnecessary database reads in get_related_orders() when fetching multiple order types Feb 20, 2025
This restores the previous format of this returned value. Prior to my earlier commit
the get_related_order_ids() call returned results in the [ 0 => $id ] format.

This commit restores that.
@james-allan
Copy link
Contributor Author

@mattallan after our discussion in Slack, I've just updated this PR (efda0d8) to remove the array_combine().

When working on this PR I noticed that parent orders returned in the format [ $id => $id ] and assumed all $order_type args returned in that format. That is incorrect.

The old version of this function had inconsistent returned formats:

wcs_get_subscription( 143 )->get_related_order_ids( 'parent' ) 
[ [142] => 142 ]

-------
wcs_get_subscription( 143 )->get_related_order_ids( 'renewal' | 'any' )
[ 
   [0] => 158
   [1] => 146
   [2] => 145
   [3] => 144
]

This is because when querying for 'any' or a specific type, the array_merge() overrides the [ $id => $id ] format set up by the parent part of the function.

In any case, this function is protected so we don't have to worry too much about the inconsistent return format. I chose to return in the [ 0 => $id ] format because it's less work and consistant with the majority of calls to this function.

Copy link
Contributor

@mattallan mattallan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @james-allan for working on this!

The $is_batch_processing approach kind of felt a bit messy to me because it requires you to set and unset $batch_processing_related_orders $subscription_meta_cache variables. That said, I attempted to explore another approach like updating get_related_order_ids() to allow an array of relation types to be passed, but that got even messier!!

Coming back and reviewing your changes makes a lot of sense why you decided to go with this approach 😅

Testing performed:

  • Load WooCommerce Subscriptions list table and confirm related orders column is populating
    • Note the reduction in queries performed on the page
  • Deleted related orders cache and refreshed the WooCommerce > Subscriptions page to regenerate related orders.
  • Processed a renewal order & deleted a renewal order to confirm the changes get_related_order_metadata() hasn't impacted regular relation adding/deleting.

LGTM

Copy link
Contributor

@mattallan mattallan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a minor finding I found while testing/reviewing

@james-allan
Copy link
Contributor Author

I attempted to explore another approach like updating get_related_order_ids() to allow an array of relation types to be passed, but that got even messier!!

Coming back and reviewing your changes makes a lot of sense why you decided to go with this approach 😅

Yeah I also started with that but the more I pulled on that string, the more I realised that approach was going to be messy. 😆

I've fixed that issue you raised and made a couple of other minor code improvements - just comment changes and variable usage.

Copy link
Contributor

@mattallan mattallan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @james-allan I went through the latest changes and those all look good.

Confirmed the empty parent array is now being returned by get_related_order_ids(). I performed some generic smoke tests on this PR as well

Happy to approve this one!

@james-allan james-allan merged commit d599390 into trunk Feb 26, 2025
9 checks passed
@james-allan james-allan deleted the issue/4795-improve-performance-of-multiple-get_related_order-types branch February 26, 2025 05:37
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants