E-Commerce Architecture: Handling Black Friday Traffic
The caching, queueing, and inventory-locking strategies that let an e-commerce platform survive a 20x traffic spike without overselling stock.
A traffic spike does not break an e-commerce platform evenly. It breaks it at one point, and everything downstream of that point looks broken too. Understanding which point goes first is most of the preparation.
The distribution is heavily skewed. The overwhelming majority of requests are people browsing — category pages, product pages, search. A much smaller number are carts. A smaller number still are checkouts. This matters because the browsing traffic is almost entirely cacheable and the checkout traffic is almost entirely not, which means they need completely different treatment.
Serve Browsing From Cache, Not From Your Database
Under load, a product page rendered per request is your first bottleneck, and it is the most avoidable one. The same page is being generated thousands of times per minute with identical output.
Cache it at the CDN with a short time to live. Thirty to sixty seconds is usually the right range: long enough that a spike collapses into a handful of origin requests, short enough that a price change propagates quickly. Combine it with stale-while-revalidate so that expiry never causes a user-visible wait — the cached copy is served while a background request refreshes it.
The objection is always inventory accuracy. The answer is to separate the page from the stock number: cache the page and load availability as a small, separate, uncached request. The description, images, specifications, and reviews are static for minutes; only the stock indicator needs to be live, and it is a tiny payload.
Category and search pages benefit even more, because they are more expensive to build and are hit before product pages in every user journey.
Cache the Expensive Reads Behind the Cache
Behind the CDN, the same principle applies at the application layer. Product data, category trees, pricing rules, and promotional configuration are read constantly and change rarely.
Watch for the pattern where a page needs one query for the product and then one query per related item — the N+1 that is invisible at low traffic and dominant at high traffic. Batch those.
The specific hazard under a spike is cache stampede. When a popular key expires, every concurrent request misses simultaneously and they all hit the database at once, which is the exact moment you can least afford it. Guard against it with a lock so only one request regenerates while the others wait or serve stale, and stagger your expiry times so keys do not all fall over together.
Inventory Is Where Correctness Actually Matters
Everything above is a performance problem. Inventory is a correctness problem, and overselling is worse than being slow, because it converts a technical failure into a customer service failure and a refund.
The naive implementation reads the stock level, checks it against the requested quantity, and writes the decrement. Between the read and the write, another request does the same thing. Both succeed. You have sold one unit twice, and under a spike you have sold it fifteen times.
The fix is to make the decrement atomic and conditional in a single database operation: decrement where the current quantity is greater than or equal to what is requested, and treat zero affected rows as out of stock. The database resolves the race; your application does not have to.
Then decide deliberately when stock is committed. Reserving at add-to-cart is friendly to buyers and lets abandoned carts hold inventory hostage, which requires a reservation expiry and a sweeper. Committing at payment authorization is the common middle ground and what most platforms should choose. Committing at order confirmation maximizes throughput and accepts that some checkouts will fail late, which is the worst possible moment to disappoint a customer.
There is no correct answer, but there is a wrong one, which is not having made the choice explicitly.
For Extreme Contention, Serialize
Standard atomic decrements handle normal load well. A single heavily promoted item under a spike is a different regime: thousands of concurrent requests contending for the same database row produce lock contention that degrades everything sharing that database.
For those specific items, move the contention out of the primary database. A Redis counter decremented atomically absorbs the contention at far higher throughput, with the database updated asynchronously as the durable record. Alternatively, queue the purchase attempts for that item and process them in order — users see a brief wait and a definite answer, which is a better experience than a timeout.
This is worth building only for known high-contention items, and it is worth knowing in advance which ones those will be. Your merchandising team knows.
Make Checkout Idempotent
Under load, users double-click. Networks retry. Mobile connections drop and reconnect mid-request. Any of these can submit the same order twice, and a customer charged twice is a support ticket, a refund, and a lost repeat purchase.
Generate an idempotency key on the client when the checkout begins and send it with the request. The server records it, and a second request with the same key returns the original result rather than creating a new order. Payment providers support the same mechanism on their side, and you should use both.
This is a small amount of work that eliminates an entire category of incident, and it is much harder to retrofit after the orders table has duplicates in it.
Move Everything Non-Essential Off the Critical Path
The only things that must happen before you can confirm an order are: validate the cart, authorize payment, commit inventory, and persist the order.
Everything else belongs in a queue. Confirmation emails, invoice generation, warehouse notification, analytics events, loyalty point accrual, recommendation model updates, ERP synchronization, fraud scoring that is not a hard gate.
The reason is not only speed. It is failure isolation. If your email provider is degraded — and under a spike, your providers are also under a spike — a synchronous send means checkout fails. Queued, it means emails arrive late and orders complete.
Make the consumers idempotent and give them a dead letter queue. Retries are certain, and a job that sends two confirmation emails on retry is a bug that only appears when the system is already under stress.
Third Parties Are Your Most Likely Outage
Your payment gateway, tax calculator, shipping rate service, address validator, and fraud engine are all outside your control and all correlated with your traffic peak.
Every external call in the checkout path needs an aggressive timeout, and every one needs a defined behavior when it fails. Some can degrade gracefully: fall back to flat-rate shipping if the rate service times out, use cached tax rates, skip non-blocking fraud checks and flag for review. Others cannot — you cannot complete a purchase without authorizing payment.
Write that list down before the event. Deciding which services are optional during an incident, at three in the morning, with orders failing, is not when you want to be having that conversation.
Circuit breakers matter here. When a dependency is failing, continuing to call it with a full timeout on every request converts a slow dependency into a total outage, because all your request handlers are blocked waiting. Failing fast after a threshold keeps the rest of the system responsive.
Load Test the Journey, Not the Endpoints
Testing individual endpoints tells you very little, because production load is a distribution across a journey with think time between steps, and it is the interaction that breaks.
Model the real funnel: many browsers, fewer carts, fewer checkouts, with realistic delays. Test at several multiples of your expected peak until you find the breaking point, because knowing where it breaks is more useful than knowing that it survived one particular number.
Then include the failure scenarios. What happens when the payment provider adds two seconds of latency. What happens when a cache node is lost. What happens when the database fails over. These are the situations that actually occur, and testing only the happy path at high volume tells you nothing about them.
Prepare the Operational Side
Scale up before the event rather than relying on autoscaling to react — autoscaling responds in minutes and a spike arrives in seconds. Pre-warm caches with your top products. Freeze deployments for the duration. Have a dashboard showing conversion rate, checkout success rate, and payment authorization rate, because those three numbers tell you whether you have a real problem faster than infrastructure metrics do.
And decide in advance what you will turn off. A documented list of non-essential features that can be disabled to shed load — recommendations, live search suggestions, review rendering — turns a potential outage into a temporarily reduced experience. That decision is easy to make calmly in advance and very hard to make under pressure.