Skip to content

Conversation

daledupreez
Copy link
Contributor

Changes proposed in this Pull Request:

This PR explores a cache prefetch implementation for a site's payment method configurations, as we are seeing busy sites generate multiple API calls immediately after the cache expires, which means that we have multiple users getting delayed responses every 10 minutes when the payment method configuration cache expires.

The PR handles this as follows:

  • Every time we have a cache hit, we check if the cache key supports prefetching. If so, we call the prefetcher to check whether we should enqueue a prefetch.
  • If the cache expiration is within a specified number of seconds of expiring (aka the prefetch window), we then check if we already have a prefetch enqueued via a specific option.
    • If the option exists, and it represents a timestamp in the future or within a number of seconds (the prefetch window), we don't re-queue a prefetch.
    • If the option doesn't exist, or the timestamp is in the past, we queue up an immediate Action Scheduler action to call a new wc_stripe_database_cache_prefetch_async action that accepts the cache key to prefetch
    • When the hook runs, we check if settings sync is enabled, and then call code that (re)fetches the underlying data.
      • For now, the only combination that we have implemented is the payment_method_configuration key, which calls WC_Stripe_Payment_Method_Configurations::get_upe_enabled_payment_method_ids( true ); to force a refetch of the PMC.
    • After we have completed running the prefetch, we remove the tracking option.
      • Note that we don't do when we pick up the work, as the cache will only be (re)populated after the processing is complete, and we want to consider the (re)fetch complete at that time, not when the fetch is picked up.

Note that we can't completely prevent multiple refetches, but relying on a local database option means the timing window is much smaller than for the case where the cache entry expires and the second and subsequent callers need to hit the site before the PMC API call returns.

Concerns and questions

  • For now, the window for the PMC cache key is 10 seconds, which is pretty generous, as it gives us time to complete the API calls for the refetch before the main entry expires. Should this be smaller?
  • As noted above, we're not going to completely avoid multiple API calls, but we should reduce the window for them considerably. It's possible we need to tweak the logic in the checks a bit further to support that goal.
  • At a broader level, is it worth adding this complexity?
    • In my mind, I think it's worth it for the PMC data, as that can impact a number of callers and slow down their experience pretty regularly.
    • It might be worth it for account data as well, but that expires every 120 minutes (2 hours), so the volume impact is smaller.

Testing instructions

  • Identify a site where Action Scheduler is working well -- my local dev instance was not a good candidate
  • Also ensure that you have two or more products in the store
  • Modify the CONFIGURATION_CACHE_EXPIRATION constant in includes/class-wc-stripe-payment-method-configurations.php from 10 * MINUTE_IN_SECONDS to 2 * MINUTE_IN_SECONDS
  • Run a build from this branch, and get this version running on your site
  • Ensure your site is connected to Stripe and has logging enabled
  • In one tab, navigate to WooCommerce > Status > Logs and open the woocommerce-gateway-stripe file from today.
  • In a second tab, navigate to the payment methods list for Stripe - this should force a refetch of the PMC data
  • In your second tab, navigate to your shop, and navigate between your products as quickly as you can for the next 2-3 minutes.
  • After you know you have spent at least 2 minutes since loading the payment method listing page, reload the Stripe logs
  • Find the initial call to payment_method_configurations
  • Scroll down until ~1m 50s later, and see if you can see a Enqueued cache prefetch entry, followed by a few Cache prefetch already pending entries, and then a successful payment_method_configurations API call and a Successfully prefetched cache key entry.
    • There may be one or more Unable to enqueue cache prefetch: Action Scheduler is not initialized or available entries as well -- in these cases, I think it's OK to simply skip the refetch logic if we can't actually queue up the relevant deferred actions.
Screenshot 2025-09-01 at 16 44 19
  • Covered with tests (or have a good reason not to test in description ☝️)
  • Tested on mobile (or does not apply)

Changelog entry

  • This Pull Request does not require a changelog entry. (Comment required below)
Changelog Entry Comment

Comment

Post merge

@daledupreez daledupreez requested a review from Copilot September 1, 2025 14:47
@daledupreez daledupreez self-assigned this Sep 1, 2025
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements a cache prefetch mechanism for payment method configurations to reduce user delays caused by cache expiration. When the cache expires, multiple users could experience slow responses while the data is refetched from the API.

  • Implements a prefetch window system that triggers cache refresh before expiration
  • Adds tracking mechanisms using WordPress options to prevent duplicate prefetch operations
  • Integrates with Action Scheduler for asynchronous cache repopulation

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
includes/class-wc-stripe-database-cache-prefetch.php New class implementing the core prefetch logic with configurable windows and async handling
includes/class-wc-stripe-database-cache.php Adds prefetch triggering logic and refactors expiry time calculation
includes/class-wc-stripe-payment-method-configurations.php Changes cache key visibility to support prefetch configuration
includes/class-wc-stripe.php Registers the new prefetch class and async action handler
tests/phpunit/WC_Stripe_Database_Cache_Prefetch_Test.php Comprehensive test coverage for prefetch functionality
Comments suppressed due to low confidence (1)

includes/class-wc-stripe-database-cache.php:1

  • Use strict comparison (===) instead of loose comparison (==) for null checks to avoid type coercion issues.
<?php

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@daledupreez daledupreez requested review from a team, diegocurbelo and wjrosa and removed request for a team September 2, 2025 15:57
* @return bool True if the cache key can be prefetched, false otherwise.
*/
public function should_prefetch_cache_key( string $key ): bool {
return isset( self::PREFETCH_CONFIG[ $key ] ) && self::PREFETCH_CONFIG[ $key ] > 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

Optional:

Suggested change
return isset( self::PREFETCH_CONFIG[ $key ] ) && self::PREFETCH_CONFIG[ $key ] > 0;
return ( self::PREFETCH_CONFIG[ $key ] ?? 0 ) > 0;

Copy link
Member

@diegocurbelo diegocurbelo left a comment

Choose a reason for hiding this comment

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

The code looks good, and tests well.

For now, the window for the PMC cache key is 10 seconds, which is pretty generous, as it gives us time to complete the API calls for the refetch before the main entry expires. Should this be smaller?

10 seconds sounds like a reasonable amount.

In my mind, I think it's worth it for the PMC data, as that can impact a number of callers and slow down their experience pretty regularly.

I agree, and we should also consider changing the current 10-minute TTL to a 60- or 120-minute TTL (similar to the account cache).

A merchant enabling/disabling payment methods directly from the Stripe Dashboard is not that frequent.

It might be worth it for account data as well, but that expires every 120 minutes (2 hours), so the volume impact is smaller.

I think we should use it for the account data too.

--

Not a blocker by any means, but I'm wondering if we should add a filter to allow merchants to opt out of prefetching?

The likely candidate is in should_prefetch_cache_key(), but that might give the impression that they can return true in the filter for any key and it will be prefetched.

Another approach would be to use the filter to allow overriding PREFETCH_CONFIG, which has the benefit of also allowing merchants to customize the prefetch window (or set it to 0 to disable).

* @return bool True if the cache key can be prefetched, false otherwise.
*/
public function should_prefetch_cache_key( string $key ): bool {
return isset( self::PREFETCH_CONFIG[ $key ] ) && self::PREFETCH_CONFIG[ $key ] > 0;
Copy link
Member

Choose a reason for hiding this comment

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

We could add a filter here to provide a way for merchants to disable the prefetch for a specific key if they want.

Copy link
Contributor

@wjrosa wjrosa left a comment

Choose a reason for hiding this comment

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

Change looks good to me and works as expected 🚢

@daledupreez
Copy link
Contributor Author

@diegocurbelo, apologies for the delay! Thanks for the excellent feedback and discussion about future options.

For now, I think it would be better to get the core changes in this PR merged (so we don't need a retest), and then ship the following changes in follow-up PRs:

  • Add filter to allow opt-out for supported prefetch keys (but not opt-in for unsupported keys!)
  • Extend the default cache timeout for PMCs
    • I think this needs its own discussion, as we had a forum thread where a merchant discovered a payment method via Optimized Checkout Suite that wasn't supported by the plugin. We would want those sorts of issues to be resolved fairly quickly. 😬
  • Implement prefetch support for account data

@daledupreez daledupreez merged commit 3af29ae into develop Sep 17, 2025
41 of 42 checks passed
@daledupreez daledupreez deleted the try/implement-database-cache-prefetch branch September 17, 2025 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants