• Resolved kartiksadadiya

    (@kartiksadadiya)


    Hi WebToffee Support Team,

    I am running WT Smart Coupons on a high-traffic WooCommerce store (40,000+ customers, 350 orders/day) and have identified a critical performance issue that is causing severe CPU spikes and slow page loads across the entire site.

    The Problem

    On every single page load — including homepage, product pages, blog posts, and all non-cart pages — the plugin fires maybe_auto_apply_on_wp_loaded() which calls get_available_auto_coupons() which runs is_coupon_valid() for every single auto-apply coupon we have.

    We have 171 auto-apply coupons. Each validation call triggers validate_coupon_usage_limit() which internally calls wc_get_orders() — a heavy database query. This means:

    171 coupons × wc_get_orders() = 171+ heavy DB queries per page load
    Total coupon-related queries per page = 223

    We confirmed this via PHP slow log analysis and SAVEQUERIES debug output.

    PHP Slow Log Evidence

    mysqli_query()
    wc_get_orders()
    validate_coupon_usage_limit()
    is_coupon_valid()
    get_available_auto_coupons()  ← line 219
    auto_apply_coupons()          ← line 532
    maybe_auto_apply_on_wp_loaded() ← line 718

    This stack appears 3,439 times in a single day’s slow log.

    The Root Cause

    maybe_auto_apply_on_wp_loaded() currently runs on every page load with no restriction to cart or checkout pages. For stores with many auto-apply coupons this causes massive unnecessary DB load on every page — even pages where cart functionality is completely irrelevant. It should fire relevant places like add to cart happens in ajax on single product page or cart update happens etc.

    Why We Cannot Fix It Ourselves

    We attempted restricting maybe_auto_apply_on_wp_loaded() to cart/checkout pages via a filter on wt_sc_auto_coupons_list returning [] on non-cart pages. This caused remove_unmatched_autocoupons() to incorrectly remove BOGO coupons from active carts because they were not in the empty valid list.

    We also attempted editing the plugin file directly to add an is_cart() || is_checkout() check — this broke cart removal functionality because remove_unmatched_autocoupons() was no longer running on non-cart pages.

    The fix needs to be done at the plugin level with full awareness of how remove_unmatched_autocoupons() and BOGO interact.

    Our Setup

    • WooCommerce 10.2.2
    • WT Smart Coupons v2.2.2 (also tested v2.2.9 — same issue)
    • 171 auto-apply coupons (all active, all in use)
    • 0 coupons with usage limits
    • Cloudways server, Object Cache Pro / Redis active
    • ~150,000 page requests per day

    Impact

    • PHP slow log shows 3,439+ coupon-related slow frames per day
    • CPU spikes during US peak hours causing site slowdowns
    • Every visitor on every page triggers 223 DB queries just for coupon checking

    This is a significant scalability issue for any store with a large number of auto-apply coupons. We would greatly appreciate a fix or official guidance in your next release.

    Thank you for your time.

Viewing 7 replies - 1 through 7 (of 7 total)
  • Plugin Support WebToffee Support

    (@webtoffeesupport)

    Hi @kartiksadadiya,

    Thank you for this exceptionally detailed report — we truly appreciate the depth of your analysis and the effort you’ve put into identifying the issue.

    The current behavior of maybe_auto_apply_on_wp_loaded() triggering on every page load, along with the associated validation flow, can lead to significant performance overhead in high-scale environments like yours.

    Let me check this case with our dev team for the best approach to address this at the plugin level. We’ll follow up with you soon with an update on it.

    Thanks again for bringing this to our attention — your insights are extremely valuable in helping us improve the plugin.

    Plugin Support WebToffee Support

    (@webtoffeesupport)

    Hi @kartiksadadiya,

    Thanks for your patience.

    We’d like to clarify that we are not currently limiting the auto coupon checking to only the cart and checkout pages, as some third-party plugins enable coupon application on other pages as well.

    Could you please confirm the following from your end:

    • Are coupons intended to be applied only on the cart and checkout pages?
    • Are you using any third-party plugin that applies coupons on product pages or other non-cart pages?

    If no such plugin is being used, we can explore providing a code snippet to restrict auto coupon application to only the cart/checkout pages.

    Additionally, since you’re working with a high number of auto-apply coupons, this can significantly impact performance on large-scale sites. As a possible optimization, you can limit the number of auto-apply coupons by navigating to: Smart Coupons → All Coupons → Auto-apply coupons → Auto-apply coupon settings (as shown here)

    Also, could you please share your WooCommerce System Status Report, which you can find under:
    WooCommerce → Status → System Status → Get system report via this link?

    This information will help us analyze your setup more accurately and suggest the best solution.

    Thread Starter kartiksadadiya

    (@kartiksadadiya)

    Hi,

    Thank you for the quick response. Here are the answers to your questions:

    1. Are coupons intended to be applied only on cart and checkout pages?

    We need auto-apply coupons to be checked on:

    • Cart page
    • Checkout page
    • Any cart-related AJAX requests (add to cart, remove from cart, update cart, apply coupon, remove coupon) regardless of which page they fire from

    We do NOT need them checked on regular page loads of homepage, product pages, blog pages, shop pages etc.

    2. Are you using any third-party plugin that applies coupons on product pages or other non-cart pages?

    No. We are not using any third-party plugin that applies coupons outside of cart/checkout context.

    Based on this, please provide the code snippet to restrict auto coupon checking to cart/checkout pages and cart AJAX requests only.

    Regarding the Auto-apply coupon settings limit:

    We have 171 active auto-apply coupons that are all currently in use and cannot be reduced. The performance issue stems from all 171 coupons being validated on every single page load — not just cart pages.

    Our PHP slow log confirms:

    maybe_auto_apply_on_wp_loaded()
    → auto_apply_coupons()
    → get_available_auto_coupons()
    → is_coupon_valid() × 171 times
    → validate_coupon_usage_limit()
    → wc_get_orders() × 171 times
    = 223 DB queries per page load on every page

    This results in 3,400+ slow log entries per day causing CPU spikes during peak traffic.

    Important — BOGO Ghost Item Issue:

    While attempting to fix this ourselves by returning [] from the wt_sc_auto_coupons_list filter on non-cart pages, we encountered a side effect:

    When a BOGO coupon is applied and the paid item is removed from the cart, the free gift item remains as a ghost item in the cart. It only disappears when the customer visits the cart page.

    We traced this to get_bogo_eligible_qty() with wbte_sc_bogo_apply_once — it always returns the fixed free qty from coupon settings without checking whether eligible paid products still exist in the cart. As a result when the last paid item is removed:

    free_products_qty = 1 (fixed from coupon setting, ignores cart)
    free_prod_qty_in_cart = 1
    1 < 1 = false → reduce_free_product_qty() never fires
    → free item stays as ghost in cart

    This is a pre-existing bug in the plugin that becomes visible when remove_unmatched_autocoupons() is not running on every page load.

    Here is my code that I tried

    add_filter( 'wt_sc_auto_coupons_list', function( $coupons ) {
    if ( is_cart() ) return $coupons;
    if ( is_checkout() ) return $coupons;
    if ( doing_action( 'woocommerce_after_calculate_totals' ) ||
    doing_action( 'woocommerce_cart_updated' ) ) return $coupons;
    if ( ! wp_doing_ajax() && isset( $_POST['add-to-cart'] ) && '' !== $_POST['add-to-cart'] ) return $coupons;
    if ( wp_doing_ajax() ) {
    $wc_ajax = isset( $_GET['wc-ajax'] ) ? sanitize_text_field( $_GET['wc-ajax'] ) : '';
    $action = isset( $_POST['action'] ) ? sanitize_text_field( $_POST['action'] ) : '';
    $get_action = isset( $_GET['action'] ) ? sanitize_text_field( $_GET['action'] ) : '';
    $allowed = [
    'add_to_cart', 'remove_from_cart', 'restore_cart_item',
    'update_cart', 'apply_coupon', 'remove_coupon',
    'woocommerce_add_to_cart', 'kures_update_cart_item_details',
    ];
    if ( in_array( $wc_ajax, $allowed ) ||
    in_array( $action, $allowed ) ||
    in_array( $get_action, $allowed ) ) return $coupons;
    }
    return [];
    } );

    Could you please address both issues:

    1. Code snippet to restrict auto-apply checking to cart/checkout/AJAX only
    2. Fix for the BOGO ghost item bug in get_bogo_eligible_qty() with apply_once

    We will share the WooCommerce System Status Report separately.

    Thank you.

    Thread Starter kartiksadadiya

    (@kartiksadadiya)

    Additional Request — Query Optimization:

    We noticed that the plugin already has a lookup table (wt_sc_auto_coupon_lookup) which stores auto-coupon data including is_auto_coupon, auto_coupon_priority, email_restriction etc. This table is already being used in prepare_auto_coupons_list() to fetch coupon IDs efficiently.

    However the actual validation step in get_available_auto_coupons() still calls is_coupon_valid() individually for each of the 171 coupons which hits the database heavily on every execution.

    Could you explore using the existing lookup table (or extending it) to handle coupon validation more efficiently — rather than running individual DB queries per coupon? This would dramatically reduce the database load especially for stores with a high number of auto-apply coupons like ours.

    Thread Starter kartiksadadiya

    (@kartiksadadiya)

    Hi Team,

    I wanted to share a performance finding that may be useful for improving the plugin for all high-volume stores.

    Issue 1 — maybe_auto_apply_on_wp_loaded() fires on every page load

    The function fires on every single page request including homepage, product pages, blog pages and shop pages — not just cart/checkout. With 171 auto-apply coupons this generates 171+ DB queries on every page load across the entire site.

    We fixed this ourselves by adding an early return in the plugin file:

    public function maybe_auto_apply_on_wp_loaded() {
        if ( ( isset( $_GET['wc-ajax'] ) && 'update_order_review' === ... ) || isset( $_REQUEST['update_cart'] ) ) {
            return;
        }
    
        // Added by us
        if ( ! is_cart() && ! is_checkout() && ! wp_doing_ajax() ) {
            return;
        }
    
        $this->auto_apply_coupons();
    }
    

    We suggest this becomes the default behavior in a future release. Or at list you can provide hook/filter here

    Issue 2 — String IDs passed to WC_Coupon constructor causing unnecessary SQL queries

    In get_available_auto_coupons(), coupon IDs are fetched correctly from the lookup table via prepare_auto_coupons_list(). However $wpdb->get_col() returns IDs as strings not integers.

    When these string IDs are passed to new WC_Coupon( $coupon_id ), the WooCommerce constructor sees a string and calls wc_get_coupon_id_by_code() which runs this SQL query:

    SELECT ID FROM wp_posts 
    WHERE LOWER(post_title) = LOWER('5710315')
    AND post_type = 'shop_coupon'
    AND post_status = 'publish'
    

    This query searches post_title (coupon code) for a numeric ID — it will never return a result. It is completely wasted. And because it returns empty, WooCommerce does not cache the result, meaning this uncacheable query hits the database on every single request.

    This runs 171 times per execution — once per auto-apply coupon.

    Root cause:

    // prepare_auto_coupons_list() returns strings from wpdb->get_col()
    // ["5710315", "5710316" ...] ← strings
    
    // WC_Coupon constructor:
    if ( is_int( $data ) )        // ← FALSE for "5710315" 
        → skips SQL, uses ID directly ✅
    elseif ( is_string( $data ) ) // ← TRUE, enters here
        → calls wc_get_coupon_id_by_code() → useless SQL ❌
    

    Fix — simply cast IDs to integers before the loop:

    $coupon_ids = $this->prepare_auto_coupons_list( $offset, $auto_coupon_check_count );
    $coupon_ids = array_map( 'intval', $coupon_ids ); // ← add this one line
    

    This makes is_int() return true → WooCommerce skips the SQL entirely → coupon is loaded directly by ID.

    Impact:

    Before: 3 DB calls per coupon × 171 = 513 DB calls per execution
    After:  1 DB call per coupon × 171 = 171 DB calls per execution
            (most cached in Redis on subsequent requests)
    

    Combined with Issue 1 fix, total coupon-related slow DB queries will be reduced.

    We have already applied both fixes on our end. We wanted to share this so it can be properly fixed in the plugin for all users. Also correct me if I miss something or some issue in my code.

    Please let us know if you need any additional details.

    Thank you.

    Plugin Support WebToffee Support

    (@webtoffeesupport)

    Hi @kartiksadadiya,

    Thank you for the detailed follow-up and for sharing your findings.

    We’ve already responded to your query via our support channel. Kindly check there, and you can continue the conversation with us directly on that thread.

    Plugin Support WebToffee Support

    (@webtoffeesupport)

    Hi @kartiksadadiya,

    Thank you for the detailed report and analysis.

    We’ve reviewed the issues you raised regarding auto-apply coupons running on all page loads, unnecessary queries due to coupon ID handling, and the BOGO “ghost item” behavior.

    These will be addressed in an upcoming release with performance optimizations and fixes.

    We appreciate your input in helping us improve the plugin.

Viewing 7 replies - 1 through 7 (of 7 total)

You must be logged in to reply to this topic.