Skip to content

Conversation

@devbodaghe
Copy link
Contributor

Description

This PR fixes a severe performance issue where an expensive SQL query was running on every frontend page load, causing 1.5+ second delays and timeout errors.

The Problem:
The query SELECT option_value FROM wp_options WHERE option_name LIKE 'wc_facebook_background_product_sync_job_%' AND (option_value LIKE '%"status":"processing"%') was executing on every request because:

  1. Double LIKE clauses cannot use indexes efficiently, causing full table scans
  2. The query ran on frontend requests where it's not needed
  3. Results were never cached

The Solution:

  1. Frontend Guards - is_queue_empty() and get_jobs() now return early on frontend requests (when not in admin, AJAX, or cron context)
  2. Transient Caching - Query results are cached indefinitely until explicitly invalidated
  3. Smart Cache Invalidation - Cache is cleared when jobs are created, completed, failed, or deleted

Impact: This single query accounted for ~25% of total page load time on affected stores.

Type of change

  • Fix (non-breaking change which fixes an issue)

Checklist

  • I have commented my code, particularly in hard-to-understand areas, if any.
  • I have confirmed that my changes do not introduce any new PHPCS warnings or errors.
  • I have checked plugin debug logs that my changes do not introduce any new PHP warnings or FATAL errors.
  • I followed general Pull Request best practices. Meta employees to follow this wiki.
  • I have added tests (if necessary) and all the new and existing unit tests pass locally with my changes.
  • I have completed dogfooding and QA testing, or I have conducted thorough due diligence to ensure that it does not break existing functionality.
  • I have updated or requested update to plugin documentations (if necessary). Meta employees to follow this wiki.

Changelog entry

Fix: Resolved performance issue where background sync job queries caused slow page loads on frontend requests.

Test Plan

Automated Tests

Run the new unit tests:
./vendor/bin/phpunit --filter BackgroundJobHandlerTest
./vendor/bin/phpunit --filter SyncTest
./vendor/bin/phpunit --filter DebugToolsTestResults: 46 tests, 98 assertions - all passing

Manual Testing

  1. Frontend Performance Test:

    • Install Query Monitor plugin
    • Visit any frontend page (shop, product, cart)
    • Verify the wc_facebook_background_product_sync_job_% query no longer appears
  2. Admin Functionality Test:

    • Go to WooCommerce > Facebook
    • Trigger a product sync
    • Verify sync status shows correctly (processing → complete)
    • Verify products sync successfully to Facebook catalog
  3. Cache Invalidation Test:

    • Start a product sync
    • Check transient exists: get_transient('wc_facebook_background_product_sync_queue_empty')
    • Complete or cancel the sync
    • Verify transient is cleared
  4. Debug Tools Test:

    • Go to WooCommerce > Status > Tools
    • Run "Delete background sync job options"
    • Verify cache transients are also cleared

Screenshots

Before

Query Monitor showing the expensive query on every frontend request:

  • Query: SELECT option_value FROM wp_options WHERE option_name LIKE 'wc_facebook_background_product_sync_job_%' AND (option_value LIKE '%"status":"processing"%')
  • Execution time: ~1.63 seconds per page load

After

  • Query no longer runs on frontend requests
  • Admin/cron contexts use cached results
  • Cache invalidates automatically when job status changes

@meta-cla meta-cla bot added the CLA Signed label Jan 19, 2026
@github-actions github-actions bot added changelog: fix Took care of something that wasn't working. type: bug The issue is a confirmed bug. labels Jan 19, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 19, 2026

📦 Latest Plugin Build

Built at: 2026-01-26T17:37:13.669Z
Commit: 45ab95d
Size: 1.6M

Download: Click here to download the plugin

To download: Click the link above → Scroll to bottom → Download "facebook-for-woocommerce" artifact

$is_empty = intval( $count ) === 0;

// Cache the result indefinitely - it will be invalidated when job status changes
set_transient( $this->queue_empty_cache_key, $is_empty ? 'empty' : 'not_empty', 0 );
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd put something like 1h for the expiration, just in case.

*/
protected function is_queue_empty() {
// Skip expensive query on frontend - only needed in admin/ajax/cron contexts
if ( ! is_admin() && ! wp_doing_ajax() && ! wp_doing_cron() && ! $this->is_process_request() ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

so the only case where the method should run is:
if it's an admin user, making an ajax call, through cron?

*/
protected function is_queue_empty() {
// Skip expensive query on frontend - only needed in admin/ajax/cron contexts
if ( ! is_admin() && ! wp_doing_ajax() && ! wp_doing_cron() && ! $this->is_process_request() ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the is_process_request method? I cannot find the implementation of it

* Tests focus on the is_sync_in_progress() method and its behavior
* with caching and frontend guards.
*/
class SyncTest extends AbstractWPUnitTestWithOptionIsolationAndSafeFiltering {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you also test the happy-path ( when a sync is in progress ), using an E2E test?

David Evbodaghe added 4 commits January 26, 2026 17:36
…frontend

- Add transient-based caching to is_queue_empty() and get_jobs() methods
- Add frontend guards to prevent expensive queries on frontend requests
- Cache persists until explicitly invalidated (no time-based expiry)
- Invalidate cache when jobs are created, completed, failed, or deleted
- Update DebugTools to clear cache when cleaning up old sync options
- Add comprehensive unit tests for caching and frontend guard logic

This fixes the slow SQL query (SELECT ... LIKE '%status:processing%')
that was running on every frontend request, causing 1.5+ second delays.
The caching for sync status is implemented at the higher level in
is_sync_in_progress() in Sync.php, not in get_jobs() directly.
Updated tests to reflect the actual implementation.
@devbodaghe devbodaghe force-pushed the fix/background-sync-performance branch from f4dfbd1 to 0c295c0 Compare January 26, 2026 17:36
@meta-codesync
Copy link

meta-codesync bot commented Jan 26, 2026

@devbodaghe has imported this pull request. If you are a Meta employee, you can view this in D91487986.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: fix Took care of something that wasn't working. CLA Signed type: bug The issue is a confirmed bug.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants