This is a real case. A WooCommerce store, 88,000 products was experiencing chronic 500 errors, high server load, and a database that had grown completely out of control. Two-phase engagement: audit first, then execution.
I’ll walk through everything I found, everything I changed, and why. No fluff.
The Baseline Problem
Before touching anything, you need to understand why a store like this is inherently heavy:
- 88,000 products in WooCommerce. That alone puts wp_postmeta at 4+ million rows that’s normal and there’s nothing wrong with it. Don’t let anyone tell you to “clean postmeta” on a store this size.
- Plugin stack: Elementor + JetWooBuilder + Jetpack + WooCommerce. Each of these is a small application. Together they create a baseline load that leaves very little headroom.
- Bot traffic that was being largely ignored at the infrastructure level.
The 500 errors were not mysterious. They had two distinct causes both traceable to specific plugin bugs and everything else was a load problem compounding those errors.
Phase 1: Audit
I reviewed error logs, access logs, Kinsta analytics and installed Query Monitor on the live site. Here’s what the audit surfaced, in order of severity.
Fatal PHP Error WooCommerce Gateway Affirm
Every 500 error trace pointed to the same line:
Fatal error: Call to a member function get_total() on false
in class-wc-gateway-affirm.php:1752
This is a known bug. The plugin is calling get_total() on an order object that doesn’t exist it’s getting false instead of an order. This happens during real order processing events, not some edge case. The plugin vendor had an open bug report with no fix at the time of the audit.
The only correct action: deactivate the plugin immediately. Don’t wait for a fix. Don’t try to work around it. Deactivate it and find an alternative payment gateway or wait for a patch.
Fatal PHP Error Elementor AI
A secondary source of 500 errors: Elementor AI was calling get_gallery_image_ids() on a product that no longer existed. The AI module was enabled for one admin account while it had already been disabled for everyone else. One missed checkbox, generating crashes on the live site.
Fix: disable Elementor AI functionality in the user profile. Done.
Bot Traffic Hitting WooCommerce Endpoints
This one was generating real database damage. Meta’s crawler (meta-webindexer) was hitting ?add-to-cart= URLs. Every single one of those requests creates a WooCommerce session entry in the database. 136 hits in one reviewed log window. Multiply that across weeks and months.
The woocommerce_sessions table had grown to over 1 million rows.
Bots don’t buy things. They should never be allowed to touch cart endpoints.
Plugin-Level Database Abuse
Several plugins were writing aggressively to the database with no retention limits:
- CartBounty Pro (woo-save-abandoned-carts-pro) no data retention limit configured. It was accumulating abandoned cart data indefinitely.
- WP Activity Log (WSAL) the plugin had been removed from the site, but its tables wsal_metadata and wsal_occurrences were still sitting in the database with 2.2 million combined rows.
- WooCommerce Product Search the wps_hit and wps_uri tables had accumulated 210,000+ rows of search tracking data going back to 2021.
Autoloaded Data Bloat
WordPress autoloads certain options from the database on every single page request. When this gets out of control, every page load carries unnecessary weight.
Before cleanup, the site had 2,213 autoloaded options totaling 870 KB. Notable offenders:
| Option | Size | Problem |
|---|---|---|
| wuoc_logger | 177 KB | WooCommerce Merge Orders logger, loaded on every request |
| wt_pklist_languages_list | 26 KB | PDF invoices plugin, only needed when generating a PDF |
| elementskit-lite__banner_data | 11.5 KB | Plugin already removed from site |
| elementskit-lite__stories_data | 5.5 KB | Same |
| jetpack_static_asset_cdn_files | 82 KB | Jetpack CDN manifest |
Loading 870 KB of options on every page request is avoidable. A lot of it is orphaned data from plugins that aren’t even active anymore.
Orphaned WP-Cron Jobs
Ten scheduled tasks were running on autopilot with nothing to do the plugins that registered them had been removed:
rocket_atf_cleanup
rocket_update_dynamic_lists
rocket_saas_clean_rows_time_event
rocket_preload_clean_rows_time_event
updraftplus_clean_temporary_files
wsal_cleanup
wsal_delete_logins
analytify_email_cron_function
analytify_cleanup_logs
puc_cron_check_updates-advanced-product-search-for-woocommerce
WP Rocket and UpdraftPlus had both been removed or replaced but their cron hooks stayed registered. These fire repeatedly and uselessly. Clean them up.
Debug Logging in Production
Every active payment gateway Stripe, Apple Pay, Google Pay had debug logging enabled. Debug logs are for development. On a live store processing real transactions, they generate enormous log files on every single checkout event. This is a common misconfiguration that nobody notices until the disk fills up or performance degrades.
Disable debug logging for all gateways. Standard error logging for failed payments is separate and should remain active.
Phase 2: Execution
With the audit complete, I went through every item systematically. All work was on the live environment. Full database backup before touching anything.
Plugin Changes
Deactivated WooCommerce Gateway Affirm. Already covered above. Fatal errors stop the moment this plugin is off.
Deactivated CartBounty Pro. Replaced with Abandoned Cart Recovery for WooCommerce by VillaTheme it enforces a 10-day log retention limit by design. CartBounty had no such limit and was bloating the database without bound.
Adjusted YITH Points & Rewards. Two settings changes that reduce unnecessary queries:
- Enabled “Hide points messages to guest users”
- Disabled “Show points in Cart page”
Both settings cause the plugin to fire database queries on every cart page load for users who will never redeem points. Guests don’t have points accounts. There’s no reason to query for points data on every cart page for every anonymous visitor.
Disabled Elementor AI for the remaining admin account. Users > All Users > Administrator > Status. Done.
Disabled WooCommerce usage data collection. WooCommerce > Advanced > WooCommerce.com. It was sending tracking data to WooCommerce servers on every request. This is enabled by default and most store owners don’t know it’s on.
Disabled WooCommerce Analytics. WooCommerce > Advanced > Features. Analytics generates constant background queries. On a store with 88,000 products and heavy traffic, those background queries matter. If you’re not actively using the analytics dashboard, turn it off.
Disabled Jetpack Site Statistics. Jetpack > Settings > Traffic. Removes per-visit tracking overhead.
Disabled debug logging for all payment gateways. Stripe, Apple Pay, Google Pay. See above.
Removed 10 orphaned WP-Cron jobs. All WP Rocket and UpdraftPlus leftovers, plus the analytify and WSAL jobs.
Database Cleanup
This is where the numbers get interesting.
Deleted wsal_metadata and wsal_occurrences.
These tables belonged to WP Activity Log, which was no longer active on the site. Before deletion:
| Table | Rows |
|---|---|
| wsal_metadata | ~1,916,097 |
| wsal_occurrences | ~353,875 |
Combined: over 2.2 million rows of dead data. Backed up to phpMyAdmin before deletion, then dropped both tables. After: both tables show 0 rows (retained as empty structure).
Cleaned wps_hit and wps_uri.
These are search tracking tables from WooCommerce Product Search. The data dated back to 2021. No impact on active search functionality.
Before:
- wps_hit: ~488,068 rows, 147.3 MiB
- wps_uri: ~340,679 rows, 169.8 MiB
After:
- wps_hit: 8,639 rows, 3.2 MiB
- wps_uri: 6,732 rows, 2.0 MiB
wps_index not touched. This table (3.4 million rows) cannot be safely truncated without breaking search. Left alone.
WooCommerce sessions pruned to 1 week.
Expired sessions older than 7 days were removed. The table went from 1M+ rows to 8,002. WP-Cron is now configured to handle ongoing cleanup correctly so it doesn’t accumulate again.
Autoload cleanup. Removed the following from autoload (set autoload = ‘no’ in wp_options):
- wuoc_logger 177 KB, WooCommerce Merge Orders logger
- wt_pklist_languages_list 26 KB, PDF invoice plugin data
- elementskit-lite__banner_data orphaned, plugin removed
- elementskit-lite__stories_data orphaned, plugin removed
Result: autoloaded options dropped from 870 KB to 655 KB. WordPress Site Health now reports autoloaded data as “acceptable.” That’s 215 KB less data being pulled from the database on every single page request.
Cloudflare Bot Protection
This was the highest-leverage change in the entire project.
Rule 1: Block bots from ?add-to-cart= URLs.
A Cloudflare Security Rule was added with the following logic:
- URI Query String contains add-to-cart
- AND User Agent contains SearchBot or Bot or similar bot identifiers
- Action: Block
Result after 24 hours: 37,000 blocked requests from meta-webindexer (Facebook’s crawler) and Claude-SearchBot alone.
This rule has zero impact on real visitors. Real customers don’t have “SearchBot” in their user agent. Checkout functionality is unaffected. Search engine crawling of product pages is unaffected the rule only blocks requests that include the cart action parameter.
Rule 2 (admin-ajax.php rate limit) not implemented.
After the add-to-cart rule went live and robots.txt restrictions were tightened (see below), the remaining bot load on admin-ajax.php dropped to an acceptable level. Adding a rate limit was assessed as unnecessary at that point, and skipping it avoids any risk of rate-limiting legitimate AJAX calls from real users. Sometimes the right call is to not add a rule.
robots.txt Optimization
After the Cloudflare changes, I went back to the server access logs. Two crawlers were still generating significant noise:
- Bingbot was hammering filter pages (/shop/?wlfilter=) and the WordPress REST API (/wp-json/). Filter URLs generate thousands of unique URLs that serve no SEO purpose and should never be indexed.
- Amzn-SearchBot (Amazon’s product crawler) was hitting REST API endpoints.
Added to robots.txt:
# Bingbot crawl rate limit and filter pages
User-agent: bingbot
Crawl-delay: 10
Disallow: /shop/?wlfilter=
Disallow: /wp-json/
# Amazon bot — block REST API access
User-agent: Amzn-SearchBot
Disallow: /wp-json/
The crawl delay on Bingbot is important. Without it, Bing will crawl as fast as your server allows. A 10-second crawl delay doesn’t meaningfully affect indexing but significantly reduces load during crawl bursts.
What Was Reviewed But Left Alone
Not every flagged item gets changed. Here’s what I assessed and decided not to touch:
Product Import Export for WooCommerce the client uses it daily. The load this plugin generates comes from the volume of products being imported/exported, not from the plugin itself. Replacing it with something lighter would accomplish nothing. Load is proportional to data volume, not to the plugin wrapper.
Advanced Database Cleaner after manual cleanup, it was performing correctly. No reason to replace a working tool.
UpsellWP error logging the plugin has no setting to disable error logging. Nothing to configure, no action possible.
Error Log After Optimization
After all changes, the error log was clean. Every other PHP fatal error was gone.
Recommendations Left for the Client
A few items were outside the scope of Phase 2 but worth documenting:
Move old out-of-stock products with no associated orders to Draft status. This reduces the search index size and removes them from storefront queries. Products linked to orders should stay published deleting products that appear on historical orders breaks order records.
Set up a monthly database maintenance routine using Advanced Database Cleaner:
- Clear expired transients
- Clean orphaned postmeta and usermeta records
- Remove spam and trashed comments
- Run OPTIMIZE TABLE to reclaim freed space after large deletions
Without a maintenance routine, all of this bloat comes back. Database hygiene is not a one-time job.
Key Takeaways
If you’re running a large WooCommerce store, here’s what this case reinforces:
Bot traffic is a database problem, not just a bandwidth problem. Every bot that hits a cart URL creates a session record. Over time, this inflates your sessions table into the millions. Block bots from cart endpoints at the CDN level before they ever touch your application.
Dead plugin tables don’t clean themselves. When you remove a plugin, its database tables stay. Go back and audit what’s sitting in your database from plugins you removed a year ago.
Autoload is a silent tax. Every autoloaded option is loaded on every page request, regardless of whether that page needs it. 870 KB on every request is significant. Audit your autoload regularly.
Debug logging in production is always wrong. It generates large files, it has no operational value on a live store, and it silently degrades performance. Every gateway ships with it enabled by default. Disable it on every gateway, every time.
Orphaned cron jobs accumulate. WP-Cron does not automatically clean up scheduled events when plugins are removed. Those events keep firing. Use a cron management tool and review your event list when you remove plugins.
Not everything needs to be changed. The rate limit on admin-ajax.php was skipped because the problem was already solved by upstream changes. Adding rules for the sake of being thorough introduces unnecessary risk. Assess the actual current state before implementing.




Leave a Reply