Okay
  Public Ticket #4556259
Bug Report: Excessive Transient Creation and Ineffective Caching in B2BKing
Closed

Comments

  •  5
    Timothy started the conversation

    Hello B2BKing Support Team,

    I am reaching out to report a technical issue identified while running B2BKing in combination with WooCommerce 9.3.4. The problem results in excessive transient creation in the wp_options table and leads to significant database growth and performance degradation.

    After analyzing both the plugin code and production logs, I have identified several underlying issues.

    First, the price filter (woocommerce_get_price_html) is applied globally whenever the “show B2C price” setting is enabled. This can be seen in:

    b2bking/public/class-b2bking-public.php
    b2bking/includes/class-b2bking.php
    

    Code:

    if (intval(get_option('b2bking_show_b2c_price_setting', 0)) === 1){    add_filter('woocommerce_get_price_html', array($this, 'b2bking_show_both_prices'), 99995, 2);
    }
    

    This means the function b2bking_show_both_prices() runs on all price renders (archives, single products, related products, widgets), not only for B2B users.

    Second, transient keys are generated using get_current_user_id():

    get_current_user_id()
    

    Resulting in keys such as:

    b2bking_display_price_both{product_id}_0
    

    For guest users (user ID = 0). This causes transients to be created for anonymous visitors, which represents the majority of traffic.

    Third, transients are written even when the current user is not a B2B user. The logic includes:

    $is_b2b_user = get_user_meta(get_current_user_id(),'b2bking_b2buser', true);
    if ($is_b2b_user === 'yes'){    // B2B logic
    }
    // Later in the function:
    set_transient('b2bking_display_price_both'.$product_id.'_'.get_current_user_id(), $price);
    

    The set_transient() call executes regardless of whether the user is B2B, meaning guests and B2C users also trigger writes.

    Fourth, and most critically, the caching logic is effectively disabled by default:

    if (    !get_transient('b2bking_display_price_both'.$product_id.'_'.get_current_user_id())     || apply_filters('b2bking_display_both_cache_disable', true)
    )
    

    Because the filter default is true, this condition always evaluates to true. As a result:

    • the cache is never reused
    • the price is recalculated on every request
    • set_transient() is executed on every price render

    This defeats the purpose of caching entirely.

    Fifth, due to this behavior, every call to:

    woocommerce_get_price_html
    

    results in a database write. On archive pages displaying multiple products, this leads to dozens of writes per request. Under crawler or bot traffic, this scales significantly.

    Additionally, the plugin maintains a global transient that is updated on shutdown:

    b2bking/includes/class-b2bking-global-helper.php
    b2bking/includes/class-b2bking.php
    

    Code:

    add_action('shutdown', function(){    b2bking()->set_global_data_update();
    });
    

    And:

    set_transient('b2bking_global_data', $b2bking_data, 30 * DAY_IN_SECONDS);
    

    This can result in repeated writes of large serialized datasets, even when no meaningful changes have occurred.

    In production, this behavior has resulted in:

    • tens of thousands of _transient_* rows
    • a wp_options table exceeding several hundred megabytes
    • continuous INSERT and UPDATE operations
    • performance degradation under load

    From an expected behavior standpoint:

    • the price filter should only execute for relevant B2B users
    • transients should not be created for guest or B2C users
    • caching should be enabled by default and reused properly
    • global transients should only be updated when underlying data changes

    Based on this analysis, I recommend the following improvements:

    • Restrict execution of the price filter:
    if (!is_user_logged_in() || get_user_meta(get_current_user_id(),'b2bking_b2buser', true) !== 'yes') {    return $price;
    }
    
    • Prevent transient creation for guests:
    if (get_current_user_id() === 0) {    return $price;
    }
    
    • Fix cache logic default:
    apply_filters('b2bking_display_both_cache_disable', false)
    
    • Only call set_transient() when necessary (B2B users and when value changes)
    • Avoid unnecessary global transient updates by checking if data actually changed before writing

    The current implementation effectively causes unbounded transient creation due to global execution combined with ineffective caching logic. This leads to database bloat and performance issues, especially on larger WooCommerce installations.

    Kind regards, Timothy

  •  5
    Timothy replied

    Hello B2BKing Support Team,

    I am reaching out to report a technical issue identified while running B2BKing in combination with WooCommerce 9.3.4. The problem results in excessive transient creation in the wp_options table and leads to significant database growth and performance degradation.

    After analyzing both the plugin code and production logs, I have identified several underlying issues.

    First, the price filter (woocommerce_get_price_html) is applied globally whenever the “show B2C price” setting is enabled. This can be seen in:

    b2bking/public/class-b2bking-public.php
    b2bking/includes/class-b2bking.php

    Code:

    if (intval(get_option('b2bking_show_b2c_price_setting', 0)) === 1){    add_filter('woocommerce_get_price_html', array($this, 'b2bking_show_both_prices'), 99995, 2);
    }

    This means the function b2bking_show_both_prices() runs on all price renders (archives, single products, related products, widgets), not only for B2B users.

    Second, transient keys are generated using get_current_user_id():

    get_current_user_id()

    Resulting in keys such as:

    b2bking_display_price_both{product_id}_0

    For guest users (user ID = 0). This causes transients to be created for anonymous visitors, which represents the majority of traffic.

    Third, transients are written even when the current user is not a B2B user. The logic includes:

    $is_b2b_user = get_user_meta(get_current_user_id(),'b2bking_b2buser', true);
    if ($is_b2b_user === 'yes'){    // B2B logic
    }
    // Later in the function:
    set_transient('b2bking_display_price_both'.$product_id.'_'.get_current_user_id(), $price);

    The set_transient() call executes regardless of whether the user is B2B, meaning guests and B2C users also trigger writes.

    Fourth, and most critically, the caching logic is effectively disabled by default:

    if (    !get_transient('b2bking_display_price_both'.$product_id.'_'.get_current_user_id())     || apply_filters('b2bking_display_both_cache_disable', true)
    )

    Because the filter default is true, this condition always evaluates to true. As a result:

        the cache is never reused
        the price is recalculated on every request
        set_transient() is executed on every price render

    This defeats the purpose of caching entirely.

    Fifth, due to this behavior, every call to:

    woocommerce_get_price_html

    results in a database write. On archive pages displaying multiple products, this leads to dozens of writes per request. Under crawler or bot traffic, this scales significantly.

    Additionally, the plugin maintains a global transient that is updated on shutdown:

    b2bking/includes/class-b2bking-global-helper.php
    b2bking/includes/class-b2bking.php

    Code:

    add_action('shutdown', function(){    b2bking()->set_global_data_update();
    });

    And:

    set_transient('b2bking_global_data', $b2bking_data, 30 * DAY_IN_SECONDS);

    This can result in repeated writes of large serialized datasets, even when no meaningful changes have occurred.

    In production, this behavior has resulted in:

        tens of thousands of _transient_* rows
        a wp_options table exceeding several hundred megabytes
        continuous INSERT and UPDATE operations
        performance degradation under load

    From an expected behavior standpoint:

        the price filter should only execute for relevant B2B users
        transients should not be created for guest or B2C users
        caching should be enabled by default and reused properly
        global transients should only be updated when underlying data changes

    Based on this analysis, I recommend the following improvements:

        Restrict execution of the price filter:

    if (!is_user_logged_in() || get_user_meta(get_current_user_id(),'b2bking_b2buser', true) !== 'yes') {    return $price;
    }

        Prevent transient creation for guests:

    if (get_current_user_id() === 0) {    return $price;
    }

        Fix cache logic default:

    apply_filters('b2bking_display_both_cache_disable', false)

        Only call set_transient() when necessary (B2B users and when value changes)
        Avoid unnecessary global transient updates by checking if data actually changed before writing

    The current implementation effectively causes unbounded transient creation due to global execution combined with ineffective caching logic. This leads to database bloat and performance issues, especially on larger WooCommerce installations.

    Kind regards, Timothy

  •   WebWizards replied privately
  •  5
    Timothy replied

    Hi Stefan,

    Thank you for the update and the detailed explanation — much appreciated.

    I’m going to test the updated B2BKing Pro build on our staging/production environment and monitor the impact on transient usage and overall database behavior.

    From a first analysis, the changes around the display price logic and reduction of unnecessary transient writes look like a solid improvement, especially for guests and non-B2B users.

    However, during our debugging we identified two additional areas that are still relevant in our setup, and I would like to get your feedback on these:

    1. Global data transient (b2bking_global_data)

    You mentioned that this only updates when $b2bking_data_is_dirty === true, which is intended to improve performance.

    In our case, we observed that:

    • set_global_data() is triggered quite frequently
    • this sets the “dirty” flag to true relatively easily
    • which results in repeated writes to the b2bking_global_data transient

    On high-traffic WooCommerce setups (especially with many product/archive requests), this can still lead to:

    • frequent writes to wp_options
    • unnecessary database load
    • increased I/O under load

    Our concern is that this behaves more like a “write-on-access” pattern rather than a strict “write-only-on-change” (i.e. comparing with previous data before persisting).

    Question: Is there any plan (or recommended approach) to make this mechanism more strict, e.g. only persisting when the actual data has changed compared to the previous stored value?

    2. Runtime flush_rewrite_rules()

    We also noticed that B2BKing still hooks into runtime requests and may trigger rewrite flushing (via the b2bking_flush_permalinks logic).

    In production environments, this is quite problematic because:

    • flush_rewrite_rules() is an expensive operation
    • it writes to the database (rewrite_rules option)
    • if triggered during normal frontend requests, it can significantly impact performance
    • under load, this can become a bottleneck or even cause spikes in database usage

    To prevent this, we currently disable it via:

    add_filter( 'b2bking_flush_permalinks', '__return_false', 999 );
    

    Question: Is there a reason this still runs during normal requests, and would it be possible to limit this behavior to:

    • plugin activation
    • settings updates
    • or explicit admin-triggered events only?

    Overall, the new build is definitely a step in the right direction, and we’re happy to continue testing and providing feedback.

    Thanks again for your work on this.

    Kind regards, 

    Timothy

  •  2,724
    WebWizards replied

    Hi Timothy,

    Thank you for the feedback,

     

    (1) b2bking_global_data

    In practical tests, we found this approach to be faster than individually changing only specific transients, which was the way it worked previously. Ultimately this amounts to 1 single call on init/shutdown vs. otherwise making 20-30 calls to individual transients that change.

     

    (2) It is fine to disable it via

    add_filter( 'b2bking_flush_permalinks', '__return_false', 999 );

    The problem and reason we are calling it on every load by default is that otherwise too many sites have problems with permalinks (e.g. 404 errors on my account page).

    Simply calling it on plugin activation or settings saves sounds good in theory, but in practice it doesn't work in our experience.

    As B2BKing is running on 10,000+ stores unfortunately sometimes we have to make decisions that are not necessarily optimal for each individual site, but work better as a whole for most sites.

    I suggest you also take a look at https://woocommerce-b2b-plugin.com/docs/speed-performance-issues-optimization-guide/ - code snippets there such as:

    // Disable permalink flushing on every page load
    add_filter('b2bking_flush_permalinks', '__return_false');
    
    // Disable automatic cache flushing
    add_filter('b2bking_flush_cache_wp', '__return_false');
    
    // Disable dashboard data calculations
    add_filter('b2bking_enable_dashboard_data', '__return_false');
    
    // Extend cache time 
    add_filter('b2bking_cache_time_setting', function($val){
        return 86400000;
    }, 10, 1);

    can be useful in improving site speed.

     

    Your feedback is very welcome. I am not sure to what degree you are analysing the code yourself and to what degree you are using AI tools like Claude Code or Codex. These AIs are very smart and powerful, and we have been using them ourselves in developing and analysing the plugin for the past year.

    That said, please note the AIs are very good at theory but do not always have a practical understanding of the realities of running a commercial plugin.

     

    Kind regards,

    Stefan

  •  5
    Timothy replied

    Hi Stefan,

    Thank you for your detailed explanation and for providing the updated version — much appreciated.

    Your reasoning regarding the b2bking_global_data approach and the permalink flushing makes sense from a broader plugin perspective, especially considering the need to support a wide range of store configurations at scale. I understand the trade-offs involved.

    On our side, we are working with a relatively large WooCommerce setup where we’ve been experiencing intermittent database growth and write spikes, particularly in the wp_options table. Because of that, we’ve taken a more diagnostic and controlled approach:

    • We are actively logging and tracing option writes (including backtraces and request context) to identify the exact source of sudden spikes.
    • We aim to minimize unnecessary write operations during normal frontend requests, especially those triggered by non-authenticated traffic (bots, crawlers, etc.).
    • Stability and predictability of database behavior is critical in our case, as we’ve observed situations where the database grows significantly within a short time window.

    That is the main reason we are selectively disabling or adjusting certain default behaviors (such as permalink flushing and cache-related actions), while monitoring the effects closely in production.

    We will proceed with testing the updated version you provided and evaluate how it impacts the write patterns and overall database stability.

    Thanks again for your support and transparency — it’s very helpful in diagnosing these kinds of issues.

    Kind regards, 

    Timothy

  •  5
    Timothy replied

    Hi Stefan,

    Thank you again for your earlier explanations and the updated builds you provided. The previous changes around the b2bking_display_price_both transients have helped: in our latest logs we no longer see the earlier guest/user ID 0 transient buildup, and the major wp_options explosions appear to have stopped.

    We have continued monitoring the site with detailed option-write logging, including request context and backtraces. The remaining B2BKing-related issue we still see is around:

    _transient_b2bking_global_data
    _transient_timeout_b2bking_global_data
    

    In the logs, these are still among the largest remaining wp_options write sources. The traces point to:

    B2bking_Globalhelper::set_global_data_update()
    → set_transient( 'b2bking_global_data', ... )
    

    From what we can see, the global data is marked as dirty during runtime and then saved again on shutdown. In practice, this still results in repeated writes of the full b2bking_global_data transient on normal public requests, including guest/bot/product/category traffic.

    The transient is often several hundred KB, sometimes close to or above 1 MB. Even if this is not causing the same database explosion as before, it still creates significant wp_options write churn and I/O pressure on larger stores.

    The specific question is whether the current dirty-check could be made stricter. For example:

    Only mark the global data as dirty if the new value is actually different from the existing value.
    Only call set_transient() if the serialized/hash value has changed.
    Avoid refreshing the timeout when the stored data has not changed.
    

    We understand your previous explanation that one global transient can be faster than many individual transient writes. That makes sense. The issue we are seeing now is not the use of a global transient itself, but that it still appears to be rewritten very frequently during public runtime requests.

    Could you please review whether set_global_data() / set_global_data_update() can be optimized further to avoid rewriting b2bking_global_data when the actual data has not changed?

    Kind regards, 

    Timothy

  •  2,724
    WebWizards replied

    Hi Timothy,

    Thanks for the follow-up,

    That's a fair point, but I think fundamentally that data is going to change very frequently by design. Every user can have different prices or rules on each product, and whenever a different user loads a different category page with new products, that data will contain something new. So even if we make the dirty-check stricter (which is tricky because comparing large datasets to determine if they actually differ is itself expensive and may not net a real performance gain), you would still see a lot of writes in practice.

    One theoretical option would be a preloader in B2BKing that tries to compute all combinations for all users in advance, but that is very complex and not something we could realistically add quickly.

     

    What I think we can do relatively easily is split the existing global data transient into individual transients. That way you would no longer get one big write, each data point would be stored separately. If you'd like to try that approach, I can implement a filter for it and send you a build to test.

    Let me know if you'd like to go that route,

    Kind regards,
    Stefan

  •  5
    Timothy replied

    Hi Stefan,

    Thank you for the quick and clear explanation.

    Yes, I understand your point that the global data can legitimately change often depending on the user, products, category pages and applicable rules. I also understand that doing a deep comparison of a large dataset may not necessarily be cheaper than writing it.

    In that case, I think your proposed approach is worth testing.

    Splitting the current b2bking_global_data transient into smaller individual transients would likely reduce the impact we are seeing in the logs, because the current issue is mainly that one large transient, often several hundred KB up to around 1 MB, is rewritten repeatedly.

    If you can prepare a build with a filter to enable this behavior, we would be happy to test it.

    A few points that would be important for our setup:

    - the new behavior should be controlled by a filter, so we can enable/disable it easily
    - the new transients should not autoload
    - the transient keys should use a clear B2BKing prefix so we can monitor them
    - the split should preferably be per logical data point/group, not too granular per request
    - if possible, avoid refreshing an individual transient when that specific value has not changed
    

    The goal for us is not necessarily to reduce the number of writes to zero, but to avoid repeatedly writing one large serialized transient during normal public requests + just to optimise the database writes.

    Is this something you are able to do without to much work?

    Would this be beneficial for all users? 

    When this would be ready we will monitor the impact on:

    _transient_b2bking_global_data
    _transient_timeout_b2bking_global_data
    wp_options write volume
    overall database stability
    

    Thanks again for looking into this.

    Kind regards, 

    Timothy

  •  2,724
    WebWizards replied

    Hi again,

    Thanks for the patience on this, I've prepared an update for you.

    Please update to the attached version of B2BKing Pro, then add this code snippet to your theme's functions.php or any code snippets plugin:

    add_filter('b2bking_split_data_transients', '__return_true');

    With this enabled, the global data will be split into multiple smaller transients instead of one large one. This may work better for your site, though to be honest we are not entirely sure which approach is best in your case. Technically I think they're roughly equal, so it's more of a performance matter that depends on each site and server setup.

    Let us know how it goes and what your results look like,

    Kind regards,
    Stefan

  •   WebWizards replied privately
  •  5
    Timothy replied

    Hi Stefan,

    Thank you for preparing the updated build and adding the filter.

    I have reviewed the approach and this looks like the right next test for our setup. We will install the provided B2BKing Pro build and enable:

    add_filter( 'b2bking_split_data_transients', '__return_true' );

    We understand that this may not reduce the total number of writes to zero, but the goal for us is mainly to avoid repeatedly writing one large serialized b2bking_global_data transient during normal public requests.

    After enabling it, we will monitor:

    _transient_b2bking_global_data
    _transient_timeout_b2bking_global_data
    _transient_b2bking_global_data_*
    _transient_timeout_b2bking_global_data_*
    wp_options write volume
    average and maximum option value size
    overall database stability

    We will test this together with the other performance filters we already use, and I will report back with the results.

    Thanks again for preparing this so quickly.

    Kind regards, 

    Timothy

  •  5
    Timothy replied

    Hi Stefan,

    Thank you again for the test build with:

    add_filter( 'b2bking_split_data_transients', '__return_true' ); 

    We installed the build and monitored the site with detailed wp_options logging, including option names, request context and traces.

    The good news is that the split-transient approach is working technically. The old monolithic keys are no longer the main write source:

    _transient_b2bking_global_data 
    _transient_timeout_b2bking_global_data 

    Instead, the data is now split into component-specific transients, for example:

    _transient_b2bking_global_data_b2bking_required_multiple 
    _transient_b2bking_global_data_b2bking_required_multiple_rules_apply 
    _transient_b2bking_global_data_b2bking_minmax 
    _transient_b2bking_global_data_b2bking_quotes_products_user_applicable_rules 
    _transient_b2bking_global_data_b2bking_tiered_price_user_applicable_rules 

    This is an improvement because we no longer repeatedly write one large serialized b2bking_global_data transient. However, the logs now show that some individual split components are still written very frequently.

    In the latest monitored period, B2BKing split-transients accounted for the majority of wp_options writes. The most active keys were:

    _transient_timeout_b2bking_global_data_b2bking_quotes_products_user_applicable_rules 
    _transient_timeout_b2bking_global_data_b2bking_tiered_price_user_applicable_rules 
    _transient_timeout_b2bking_global_data_b2bking_required_multiple 
    _transient_timeout_b2bking_global_data_b2bking_minmax 
    _transient_timeout_b2bking_global_data_b2bking_required_multiple_rules_apply 
    _transient_b2bking_global_data_b2bking_required_multiple 
    _transient_b2bking_global_data_b2bking_required_multiple_rules_apply 

    The largest value payloads are now mainly:

    _transient_b2bking_global_data_b2bking_required_multiple 
    _transient_b2bking_global_data_b2bking_required_multiple_rules_apply 

    These are much smaller than the old full global transient, but they are still updated very often.

    What is important in our setup: we currently only have one active Dynamic Rule, and that is a Tax Exemption rule for B2B users. We do not use Required Multiple rules, Minimum/Maximum Order rules, Free Shipping rules or Quote Product rules.

    Despite that, the logs show that required_multiple, minmax, quotes_products_user_applicable_rules and tiered_price_user_applicable_rules are still being triggered by normal public traffic and bots on product/category/filter pages.

    Looking at the code, this seems related to logic such as:

    get_option( 'b2bking_have_minmax_rules', 'yes' ) === 'yes' 
    || apply_filters( 'b2bking_auto_activate_minmaxstep_rules_meta', true ) 

    and similarly for required multiple:

    get_option( 'b2bking_have_required_multiple_rules', 'yes' ) === 'yes' 
    || apply_filters( 'b2bking_auto_activate_minmaxstep_rules_meta', true ) 

    Because b2bking_auto_activate_minmaxstep_rules_meta defaults to true, it appears that the min/max and required-multiple logic can still be activated even when no dynamic rules of those types are configured.

    That means bots and public product/filter traffic can still trigger these components and cause split-transient writes, even though the store does not actively use those rule types.

    Our current conclusion:

    - The split-transient build works and is an improvement. 
    - It reduces the impact of one large global transient. 
    - However, B2BKing still builds/persists data for some unused components. 
    - The biggest remaining examples are required_multiple and minmax. 

    Would it be possible to further guard these components so they only run when that specific rule type is actually active?

    For example:

    - do not hook required_multiple logic unless required_multiple rules exist 
    - do not hook minmax logic unless minimum/maximum order rules exist 
    - do not persist split-transient components for unused rule types 
    - avoid refreshing split-transient timeouts when the component value has not changed 

    In our case, the only dynamic rule that should remain active is Tax Exemption. So ideally B2BKing should not build or persist required_multiple, minmax or quote-product rule caches during normal public requests.

    As a next test on our side, we are planning to enable:

    Disable dynamic rule required multiple 
    Disable dynamic rule minimum and maximum order 

    because we do not use those features. We will then monitor whether the corresponding split-transient writes disappear.

    Thanks again for preparing the split-transient build. It definitely helped us isolate the next layer of the issue.

    Kind regards,  
    Timothy

  •  2,724
    WebWizards replied

    Hi Timothy,

    Thanks for the detailed report,

    The reason `required_multiple` and `minmax` logic still run is that they're tied directly to the product page (for min/max/step quantity controls), not just to dynamic rules. That's why `b2bking_auto_activate_minmaxstep_rules_meta` defaults to true.

    For your setup, if you don't use min/max/step on the product page, you can safely disable that with:

    add_filter( 'b2bking_auto_activate_minmaxstep_rules_meta', '__return_false' );

    For reference, here's the doc on the feature this controls: https://woocommerce-b2b-plugin.com/docs/quantity-rules-min-max-step-on-product-page/

     

    Beyond that, I also recommend going to B2BKing → Settings → Other → Components and disabling any rule types you don't use (Required Multiple, Minimum/Maximum Order). I believe that should help further.

    Please let me know what your results are after these changes,

    Kind regards,
    Stefan

  •  5
    Timothy replied

    Hi Stefan,

    Thank you again for the previous explanation and the test build with:

    add_filter( 'b2bking_split_data_transients', '__return_true' );
    

    We have now continued monitoring the site after applying your recommendations.

    First of all, your suggestion regarding required_multiple and minmax was correct. After enabling the following settings:

    Disable dynamic rule required multiple
    Disable dynamic rule minimum and maximum order

    and with the split-transient build active, the related writes have effectively disappeared from our logs.

    Previously, we saw frequent writes to:

    _transient_b2bking_global_data_b2bking_required_multiple
    _transient_timeout_b2bking_global_data_b2bking_required_multiple
    _transient_b2bking_global_data_b2bking_required_multiple_rules_apply
    _transient_timeout_b2bking_global_data_b2bking_required_multiple_rules_apply
    _transient_b2bking_global_data_b2bking_minmax
    _transient_timeout_b2bking_global_data_b2bking_minmax

    After disabling those components, these are now gone or negligible. That is a clear improvement.

    The remaining B2BKing-related writes are now mostly split-transient timeout rows, especially:

    _transient_timeout_b2bking_global_data_b2bking_tiered_price_user_applicable_rules
    _transient_timeout_b2bking_global_data_b2bking_quotes_products_user_applicable_rules
    _transient_timeout_b2bking_global_data_b2bking_info_table_user_applicable_rules

    The important detail is that the related split values themselves barely change anymore. In our latest detail logs, the value hashes for the corresponding split values were usually identical, but the timeout rows were still refreshed thousands of times.

    So the current situation is:

    - the split-transient build works
    - required_multiple/minmax are now resolved
    - the large value writes are mostly gone
    - but split-transient timeout rows are still refreshed very frequently

    Would it be possible to avoid refreshing the split-transient timeout row when the component value has not changed?

    In other words: if the split component value is identical to the stored value, could B2BKing skip refreshing the matching _transient_timeout_b2bking_global_data_* option as well?

    We also noticed a separate warning in the PHP error logs:

    Get cart should not be called before the wp_loaded action.

    The trace points to this path:

    init
    → B2bking_Dynamic_Rules::b2bking_dynamic_rule_tax_exemption
    → WC_Cart->get_cart_hash
    → WC_Cart_Session->get_cart_for_session
    → WC_Product_Variation->is_purchasable
    → B2bking->b2bking_make_quote_product_purchasable
    → B2bking_Globalhelper::user_has_p_in_cart
    → WC_Cart->get_cart

    Our only active Dynamic Rule is still Tax Exemption. However, during normal frontend requests, B2BKing appears to reach quote/cart-related logic and calls WC()->cart / WC_Cart->get_cart() during init, before wp_loaded.

    WooCommerce reports this as a doing_it_wrong warning.

    Could this logic be moved to wp_loaded, or guarded so it does not run during normal public requests unless cart context is actually needed?

    To summarize the current findings:

    - Required multiple/minmax issue: resolved after your recommendation
    - Split transient value writes: strongly improved
    - Remaining B2BKing database churn: mainly timeout refreshes
    - Remaining runtime warning: WC cart is accessed too early via tax exemption / quote-product logic

    Thanks again for the help. The changes have definitely improved the situation, and we are now down to these remaining optimization points.

    Kind regards,
    Timothy

  •  2,724
    WebWizards replied

    Hi Timothy,

    Thanks for the follow-up,

    1. The `doing_it_wrong` warning
    That one is actually caused by our latest update which added a new cache to dynamic rules. I've prepared a fix and attached an updated build here - you can either update to the attached version now, or wait for the next official release.

     

    2. Skipping timeout refreshes when split values are unchanged
    This is a fair question. The concern on our side is that comparing large arrays via `===` is itself fairly expensive, so doing that comparison on every shutdown might add more load than just writing the transient. So we'd prefer not to bake that into the plugin by default.

    That said, I think you can test this approach on your side with a small snippet.

    You can add this to your theme's functions.php or any code snippets plugin:

    // Requires b2bking_split_data_transients filter to be enabled
    add_action('shutdown', function () {
        global $b2bking_data, $b2bking_dirty_keys;
    
        if (!is_array($b2bking_dirty_keys) || empty($b2bking_dirty_keys) || !is_array($b2bking_data)) {
            return;
        }
    
        foreach (array_keys($b2bking_dirty_keys) as $key) {
            if (!array_key_exists($key, $b2bking_data)) {
                continue;
            }
            if (get_transient('b2bking_global_data_' . $key) === $b2bking_data[$key]) {
                unset($b2bking_dirty_keys[$key]);
            }
        }
    }, 9); // priority 9 runs before B2BKing's default-priority (10) shutdown writer
    

    Kind regards,
    Stefan

  •   WebWizards replied privately
  •   Timothy replied privately
  •   Timothy replied privately
  •  2,724
    WebWizards replied

    Hi Timothy,

    That's great to hear, I appreciate the update,

    If we can help with anything else, please reach out anytime!

    Kind regards,
    Stefan