Most WooCommerce stores hit the stock-sync wall in the same place: between 80 and 120 orders per day. Below that, the default WooCommerce setup is fine. Above it, the cracks show: oversold SKUs, 504s at checkout, ops teams reconciling spreadsheets at midnight after a sale.
The wall is not a database bug. It is an architecture limit. Here is what breaks, and the pattern that fixes it.
What breaks at scale
WooCommerce's default stock model uses synchronous database writes inside the checkout transaction:
- Customer adds product to cart.
- Customer clicks "Place order".
- WooCommerce decrements the SKU's stock in
wp_postmeta. - WooCommerce inserts the order in
wp_posts/wp_postmeta. - WooCommerce queues the order-completed action.
- Customer sees the thank-you page.
At one order per minute, this works fine. At five orders per minute (a typical flash sale rate), three problems emerge:
- Lock contention on
wp_postmeta. Every stock decrement takes a row lock on a shared, unindexed table. Concurrent decrements queue. - Connection pool exhaustion. A queued decrement holds a database connection for 100-300ms. At 10 concurrent checkouts, a default pool of 20 connections is half-used; at 30 concurrent checkouts the pool is empty and the next checkout 504s.
- Action Scheduler backlog. WooCommerce's async hooks (emails, webhooks, ERP sync) queue into Action Scheduler. Under load, the queue grows faster than the cron-driven runner can drain it, and downstream systems get hours-old events.
The dashboard does not show any of this until customers complain.
The fix: event-driven sync
Replace the synchronous decrement with an event-driven model:
- Checkout writes the order in a single transaction, with stock unchanged.
- The order-completed hook emits a webhook to a queue.
- A queue worker consumes the event, decrements stock atomically using a database-level operation (
UPDATE ... SET stock = stock - ? WHERE stock >= ?), and fires downstream sync. - Failed events get a retry budget with exponential backoff.
- A monitor pages on sync lag exceeding a threshold (60 seconds is a sensible default).
The atomic UPDATE matters. It prevents the classic race condition where two concurrent checkouts each see 1 unit in stock, both decrement, and the SKU ends up at -1.
UPDATE wp_postmeta
SET meta_value = meta_value - 1
WHERE post_id = ? AND meta_key = '_stock' AND CAST(meta_value AS SIGNED) >= 1
If this returns 0 rows affected, the unit was already taken. The queue worker can either mark the order as oversold (refund flow) or fail-fast back to the customer.
Idempotency: the bug everyone has
The other source of stock-sync chaos is non-idempotent handlers. A handler that decrements stock on every event seen will double-decrement when an event is retried.
Make every handler idempotent:
- Every event has a unique ID, persisted in a
processed_eventstable. - Handler checks the table on entry. If the event ID is present, return immediately.
- After processing, insert the event ID into the table inside the same transaction as the state change.
This pattern is mechanical and boring. Every retry-safe system in production looks like this.
The fulfilment side
Stock sync is half the problem. The other half is fulfilment: vouchers, tracking, courier callbacks.
Pattern:
- Order completed → webhook to fulfilment provider (ELTA, ACS, Speedex, Box Now, etc.).
- Provider returns voucher ID and tracking URL synchronously.
- Order updated with voucher ID and tracking URL.
- Customer notified via email and (optionally) SMS / Viber with the tracking link.
The whole loop should complete in under 60 seconds for the customer experience to feel "instant". With a properly tuned queue, even at peak volume this is achievable.
ERP integration
Most growing WooCommerce stores in Greece eventually integrate with an ERP: Softone, Entersoft, Megasoft, or similar. The integration pattern:
- WooCommerce remains the source of truth for the product catalog and the order events.
- ERP is the source of truth for accounting, stock at the warehouse level (vs. the storefront level), and invoicing.
- A bidirectional sync runs every 5-15 minutes. Stock-at-warehouse flows down to WooCommerce as a clamp on storefront stock. Orders flow up to the ERP for invoicing.
Avoid making the ERP the source of truth for storefront stock. ERP systems are not designed for real-time response to web traffic, and the latency between an ERP-side stock change and a WooCommerce-side display can be 5-30 minutes, which is forever during a sale.
Monitoring
The single biggest difference between a store that scales and one that does not is monitoring. Required dashboards:
- Queue depth. How many unprocessed events are in flight. Alert when sustained above N (depends on volume).
- Sync lag. Time from order completion to fulfilment-system acknowledgment. Alert above 60 seconds.
- Oversold count. Per day. Alert immediately if non-zero.
- Webhook failure rate. Per minute. Alert above 1% sustained.
Without these, you find out about the bug from a customer email three days later.
What scaling actually looks like
A WooCommerce store with this architecture comfortably handles:
- 500 orders per day baseline.
- 2,000+ orders per day during sales.
- Black Friday spikes of 10,000+ orders over 4 hours.
Past that, the bottlenecks move to the front end (checkout page load times, payment gateway latency, image delivery) and the database (wp_postmeta for high-cardinality stores; consider a custom orders table per WooCommerce's HPOS migration). Most stores never need to go there.
What this looks like with us
WooCommerce automation engagements start with an order-flow audit: where the synchronous writes happen, what the current queue depth looks like, where idempotency is missing. The fix is rolled out gradually with 10% / 50% / 100% traffic phases so a regression can be reverted in minutes.
If you want the flow mapped rather than described, the $990 automation audit costs the manual work in hours per month and ranks each automation by the hours it gives back.

