There’s a specific category of WooCommerce performance problem that only appears after you’ve crossed a certain traffic threshold, and it looks nothing like what most tuning guides prepare you for.
The usual advice covers caching layers, image optimization, and database indexes — all valid, none of it where I’ve actually felt the most pain. The real problems were in plugins doing work that nobody had bothered to bound or defer: work that looked harmless at five requests per second and fell apart at twenty.
Load and concurrency are different problems. A thousand users over an hour is a very different problem from a hundred users in the same thirty seconds. Most plugins are written for the first scenario, and most of us test for it too.
What 7.4 seconds of TTFB actually looked like #
Nothing had been deployed — not the server, not the schema. This is the part that makes concurrency bugs genuinely disorienting: the environment looks identical to how it looked when everything was fine, so your first instinct is to look for something that changed, and you won’t find it.
The root cause turned out to be two plugins working against us at the same time:
- A currency conversion plugin doing per-request price recalculation instead of caching converted values
- A search plugin triggering partial index rebuilds during peak hours rather than deferring them
Each added a few hundred milliseconds individually, but under concurrent checkout traffic those costs stacked. We also found about 38 MB of autoloaded options bloating every PHP bootstrap — a problem that’s invisible at low traffic because the memory hit is constant, not variable, but which compounds badly when you’re trying to serve forty simultaneous requests.
The fix was less dramatic than the diagnosis: disabled dynamic price recalculation and switched to cached conversion values, moved index rebuilds to a low-traffic window, cleared the autoload bloat. TTFB settled back around 2.3 seconds.
The bottlenecks that don’t show up until 100k+ #
- Action Scheduler queues growing faster than they clear — a slow, invisible drain that compounds over hours
- Autoloaded options above 1 MB — anything in
wp_optionswithautoload = yesgets pulled into memory on every single request, before your page does anything wp_postmetalookups degrading once the table crosses a few million rows and query patterns shift- WooCommerce session table writes stacking during micro spikes — each active cart creates a write, and they don’t always serialize gracefully
- Object cache hit ratios collapsing during bursts — the cache looks healthy in steady state but falls apart when twenty users hit the same page simultaneously and all miss before the first one warms the key
- Third-party scripts injecting render-blocking time per request — on this site, the two offending plugins alone accounted for 200–400 ms each under concurrent load
- Plugins doing per-request lookups that should be batched or cached once
wp-crontriggering during checkout when concurrency spikes — particularly nasty because cron runs piggyback on incoming web requests by default- Slow queries buried inside transient regeneration logic — not the query you’re watching, but the one that fires when a cached value expires under load
In every case the mechanism is the same: an operation that’s cheap when it runs occasionally becomes expensive when it runs thousands of times per minute with no queue, no lock, and no back-pressure.
The audit checklist I now run at scale #
☐ Check autoloaded options size — query wp_options WHERE autoload = 'yes'
and look for anything above ~900 KB total (that's the threshold I use;
the right cutoff will depend on your server configuration)
☐ Inspect wp_actionscheduler tables for stuck tasks or queues not clearing
☐ Profile wp_postmeta queries with Query Monitor + slow query log together
☐ Disable WooCommerce cart fragments where you don't need real-time cart counts
(fragments fire an AJAX request on every page load for logged-out users)
☐ Move index rebuilds and data generation jobs outside peak windows
☐ Review every third-party script in a waterfall chart — not just for size,
but for whether it's render-blocking
☐ Test with object caching disabled temporarily to expose what the architecture
actually depends on
☐ Run a 24-hour slow query log to catch intermittent spikes that don't show
in a point-in-time profile
☐ Stress test checkout at concurrency above 10 simultaneous sessions
One check deserves its own note: test with object caching disabled. It sounds counterintuitive, but it’s one of the most useful things you can do. If the site becomes unusable without Redis or Memcached, that tells you something important about the architectural assumptions baked into your plugins. Caching should be a performance optimization, not the thing holding the site together.
The framing I’ve come around to #
That gap — between “performs well in testing” and “holds up under concurrency” — is where most WooCommerce scaling problems actually live. You can’t always spot it by reading plugin code or documentation; you find it by profiling under realistic traffic patterns, and sometimes you only find it when the traffic arrives.
Side note: I’m currently enabling ModSecurity with the OWASP Core Rule Set on Apache, running in detection-only mode to baseline false positives before switching enforcement on. Worth mentioning because WAF rules at the wrong threshold can themselves add latency under load — so that’s the next thing I’ll be watching on this same site.