Some conflicts only exist when the store is busy
An extension conflict that shows up in QA is the easy kind. You reproduce it, you trace it, you fix it. The hard kind passes every test with one user and only breaks when real traffic arrives.
These conflicts are not random. They come from resource contention that a single-user test can never trigger.
This article covers why load changes the rules, the specific conflicts that hide until concurrency exposes them, and how to reproduce them before your customers do.
Why load changes the rules
With one request at a time, extensions rarely fight. Each one runs, finishes, and releases whatever it was holding before the next request starts.
Under concurrency, that changes. Requests overlap, and they compete for the same locks, cache keys, database rows, and memory. Two pieces of code that are individually correct can still deadlock or starve each other when they run at the same time.
The usual culprits
A handful of patterns account for most load-only conflicts. Each is invisible until requests overlap:
- Two extensions invalidating each other's cache tags, so every request rebuilds cache the other just cleared.
- Plugins stacked on the same hot method, fine alone but combining badly when hundreds of requests hit them at once.
- Database deadlocks on shared tables like
quoteand inventory, where two extensions update the same rows in different orders. - Memory limits that each extension respects alone but exceed together once concurrency multiplies the footprint.
None of these is a bug you can see by reading one code path. They only appear in the overlap.
Session lock contention, the quiet one
The most common load conflict we find is also the least obvious. PHP locks the session while a request holds it, so two requests sharing a session are processed one after another, not in parallel.
An extension that fires many AJAX calls, each opening and writing the session, serializes those calls behind the session lock. With one user testing, the delay is invisible. Under load, the same customer's requests queue up and the store feels slow for reasons the code review never showed.
The fix is to close the session early on requests that do not need to write it, or to stop the extension from writing the session on every call. Both require finding the offender first.
Database deadlocks between extensions
When two extensions write the same rows in different orders, InnoDB will eventually catch them mid-transaction and kill one to break the deadlock. The killed request throws an error the customer sees.
These are intermittent by nature, because they need the two transactions to overlap on exactly the wrong rows. That is why they never reproduce on demand in QA.
Reading SHOW ENGINE INNODB STATUS right after a deadlock shows the two transactions involved and the rows they fought over. That output usually names the tables, which points you at the responsible modules.
Memory pressure multiplies under concurrency
Memory is the constraint teams forget when adding extensions. Each one measures fine in isolation, using a slice of the PHP memory_limit on a single request.
Under concurrency the server runs many PHP workers at once, and their memory adds up against the total the box has, not the per-request limit. A set of extensions that each look reasonable can push the server into swap, or trigger the out-of-memory killer, only when enough requests run together.
That failure looks like random 500s and killed processes, with nothing wrong in any single request. Watching memory during a load test is the only way to catch it before production does.
How to reproduce it before customers do
The only reliable way to surface a load conflict is to create load. A staging environment and a load-testing tool such as siege, k6, or Locust let you push production-like concurrency at the store.
While the test runs, watch the systems that show contention. SHOW FULL PROCESSLIST reveals queries piling up, the slow query log captures what stalls, and SHOW ENGINE INNODB STATUS reports deadlocks as they happen.
If you have application performance monitoring in place, its transaction traces will point at the exact method where requests spend their time waiting. That is often the fastest route to the conflicting code.
Isolating the offender
Once you can reproduce slowdown or errors under load, isolation is mechanical. Disable one suspect extension, run the same load test, and compare.
When the numbers change, you have found a participant in the conflict. Knowing which extensions fight under load, and why, is exactly the kind of finding that separates a store that survives a traffic spike from one that goes down during its busiest hour.