I've watched a perfectly good flash sale turn into a 500-error waterfall in under 90 seconds. It was a sneaker drop in 2018 - we had 12,000 concurrent users at 9:59 AM, and 1.1 million at 10:01 AM. The origin didn't melt down slowly, it just disappeared. If you're building sale infrastructure for that kind of traffic spike, you can't think in terms of scaling up - you have to assume your origin is already offline and design the edge to carry the entire event.
Why Most Sale Infrastructure Fails in the First 60 Seconds
Traditional e-commerce architectures are built for steady-state traffic with occasional peaks. A database, a few app servers, maybe an auto-scaling group that adds instances when CPU hits 70%. That model works fine for a normal Tuesday, but a flash sale isn't a peak - it's a wall. When that traffic spike hits, your auto-scaler is still waiting for CloudWatch metrics to trigger while your origin connection queue is already 10x over capacity. I've seen teams try to solve this with bigger origin servers and it never works. You don't need a bigger boat, you need to keep most of that traffic from ever reaching the boat.
The core problem is origin-centric thinking. Every uncached request for a product page, every API call for inventory status, every cart validation - they all funnel back to one place. During a flash sale, even a 90% cache hit ratio isn't enough. If you have a million requests per minute and 10% hit your origin, that's still 100,000 requests per minute hammering your database. Your sale infrastructure has to invert that ratio. We're aiming for 98-99.5% offload at the edge, which means being brutally honest about what can be cached, what can be served stale, and what absolutely must go to origin. Getting your CDN Product Page Optimization: Sub-Second Loading for Higher Conversion Rates right before the sale is what gives you that headroom.
# Edge caching strategy for flash sale product pages
# Serve stale for 2 minutes while revalidating in background
# Based on HTTP Working Group Specifications for Cache-Control
location /products/flash-sale/ {
proxy_cache sale_cache;
proxy_cache_valid 200 10s;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
proxy_cache_background_update on;
proxy_cache_lock on;
# This is the critical part for traffic spikes
add_header Cache-Control "public, max-age=10, stale-while-revalidate=120, stale-if-error=300";
add_header CDN-Cache-Status $upstream_cache_status;
# Collapse thundering herd - only one request goes to origin
proxy_cache_lock_timeout 5s;
proxy_pass http://origin_shield;
}
Engineering CDN Burst Capacity Before the Spike Arrives
CDN burst capacity isn't something you automatically have just because you're on a major provider. Yes, the big CDNs have enormous networks, but your configuration determines whether you can actually use that capacity. An unoptimized setup will still send every cache miss back to origin, and worse, it will do it from every edge PoP individually. I learned this the hard way during a beauty brand's Black Friday event - we had 180 edge nodes all requesting the same uncached product JSON at once. We effectively DDoS'd ourselves. That's when I started insisting on origin shielding and pre-warming for every major sale.
Shielding is non-negotiable for flash sale workloads. Instead of 200 PoPs hitting origin, you put a mid-tier cache layer in front - usually one or two shield PoPs per region. All misses funnel through the shield, so origin sees one request per object instead of hundreds. But you can't just turn it on and hope. You need to pre-warm the cache with your hero products, images, and API responses at least 30 minutes before the sale starts. And don't forget dynamic content - your CDN Cart Session Management: Maintaining Shopping State Across Edge Nodes needs to be tested under load, because session validation can become your biggest origin bottleneck. For the actual money path, CDN Checkout Flow Acceleration: Edge Optimization for the Revenue-Critical Path should be isolated to a separate, non-cached route with its own rate limits and queueing.
// Pre-warm script to prime CDN cache before flash sale
// Run this 30 minutes before sale start time
const HERO_URLS = [
'/products/flash-sale-2024.json',
'/api/inventory/flash-sale-2024',
'/images/hero/flash-sale-banner.webp'
];
const EDGE_POPS = ['iad', 'lax', 'lhr', 'nrt', 'syd'];
async function prewarmCache() {
for (const pop of EDGE_POPS) {
for (const url of HERO_URLS) {
await fetch(`https://cdn.example.com${url}`, {
method: 'PURGE_WARM', // or standard GET with cache priming headers
headers: {
'X-Prewarm': 'true',
'X-Edge-Pop': pop
}
});
console.log(`Warmed ${url} at ${pop}`);
// Don't hammer the shield - add delay
await new Promise(r => setTimeout(r, 200));
}
}
}
// Verify hit ratio before sale
// Aim for >95% warm rate on hero objects
prewarmCache().then(() => checkCacheStatus());
Choosing Cache TTLs When Inventory Can't Be Wrong
Here's the uncomfortable trade-off every team faces during a flash sale: if you cache too aggressively, you'll sell inventory you don't have. If you don't cache enough, your sale infrastructure collapses. There's no perfect answer, just calculated risk. For the catalog pages and images, you can be aggressive - 30 to 60 seconds with a long stale-while-revalidate window. The product description doesn't change during the sale. But for price and inventory, you need a much tighter strategy. We use what the web.dev Articles call stale-while-revalidate patterns, but with edge-specific tweaks for commerce.
What works best for most of our clients is a split TTL. Product HTML gets a 10-second max-age with 120 seconds of stale-while-revalidate. That means the edge serves instantly for 10 seconds, then serves stale while fetching a fresh copy in the background for the next two minutes. For the inventory API, we drop to 2-3 seconds max-age and use request collapsing so only one origin fetch happens per second, per product. It introduces a tiny bit of oversell risk - maybe 2-3 orders for the last unit - which you handle with an async queue and graceful cancellation email, rather than by letting the database handle 50,000 concurrent inventory checks. That queue is far better than a crashed checkout. The HTTP Working Group Specifications (https://httpwg.org/specs/) define these Cache-Control directives, but how you apply them during a 100x spike is what separates a sale that converts from one that trends on Twitter for the wrong reasons.
| Caching Strategy | Cache Hit Ratio | Origin Req / sec (at 1M RPM) | p95 TTFB at Edge | Oversell Risk |
|---|---|---|---|---|
| No Cache / Origin Only | 0% | 16,600 | 850ms - 2200ms | None (but origin fails) |
| Standard 60s TTL | 88-92% | 1,300 - 2,000 | 45ms - 110ms | High (60s stale inventory) |
| 10s + SWR 120s (Recommended) | 98.2-99.1% | 150 - 300 | 18ms - 35ms | Low (<3 sec window) |
| 2s API TTL + Request Collapsing | 96.5-97.8% | 350 - 580 | 22ms - 48ms | Minimal (1-2 orders) |
Putting It Into Practice: Stale-While-Revalidate and Request Collapsing
Setting a 2-5 second TTL on your inventory API is only half the battle. If you don't handle what happens *after* that TTL expires, you'll still slam your origin. Without protection, that TTL expiry creates a thundering herd — 40,000 edge nodes all revalidating the same hot SKU at once. That's exactly how you turn a cache miss into an origin outage. What you want is to serve stale content while you revalidate in the background, and to collapse duplicate origin fetches into a single request.
We typically run a 5s TTL with a 30s stale-while-revalidate and a 60s stale-if-error window for flash sale stock endpoints. That gives us a 94-97% cache hit ratio even at 50,000 RPS, while ensuring shoppers never see a hard error if the origin hiccups for a few seconds. Pair that with request collapsing (also called request coalescing) and origin shielding, and you’ve cut origin load by another 10-20x. Instead of 5,000 requests for the same product hitting origin, only one goes through — the rest wait for that single response. If you're building this for the first time, our breakdown of CDN Inventory API Caching: Real-Time Stock Visibility with Aggressive TTLs walks through the exact header strategy we use to keep stock accurate to within 3 seconds.
// Cloudflare Worker / Fastly Compute@Edge pattern for flash sale inventory
export default {
async fetch(request, env, ctx) {
const cache = caches.default;
let response = await cache.match(request);
if (response) {
// Serve stale immediately, revalidate in background
const age = parseInt(response.headers.get('age') || '0');
if (age > 5) {
ctx.waitUntil(fetchAndCache(request, cache));
}
return response;
}
// Request collapsing: only one origin fetch per cache key
return fetchAndCache(request, cache);
}
}
async function fetchAndCache(request, cache) {
const originResponse = await fetch(request, {
cf: { cacheTtl: 5, cacheEverything: true }
});
// Clone and set SWR headers
const response = new Response(originResponse.body, originResponse);
response.headers.set('Cache-Control', 'public, max-age=5, stale-while-revalidate=30, stale-if-error=60');
response.headers.set('CDN-Cache-Control', 'max-age=5');
await cache.put(request, response.clone());
return response;
}
Advanced Patterns: Surviving Thundering Herds and Graceful Degradation
You can have perfect caching and still fail at checkout. The product page might stay up, but your add-to-cart, inventory reservation, and payment APIs aren't cacheable in the same way. For those, you need admission control at the edge. We’ve seen sales where the cache held fine at 80,000 RPS, but the origin reservation service buckled at just 800 RPS. The fix isn't more origin servers — it's an edge queue. Implement a lightweight waiting room or token bucket at the CDN that lets only 500-1,000 checkout requests per second through to origin, and serves everyone else a static, cached queue page with a 5-second meta-refresh. It feels better to shoppers than a 503, and it keeps your database from deadlocking.
The other killer is the oversell. It happens when your edge serves a cached "In Stock: 12 left" response for 30 seconds after the last item sold. Don't rely on TTL alone to prevent this. Use a two-layer approach: edge cache for reads, but do a real-time reservation check on add-to-cart via a non-cached API call that goes through origin shield. If you’re personalizing prices per user or segment during the sale, you can't just bypass cache entirely — you'll destroy your hit ratio. Instead, use edge compute to inject pricing via an Edge Dictionary or Workers KV lookup, which we cover in detail in Price Personalization at CDN Edge: Dynamic Pricing Without Origin Round-Trips. That keeps your HTML cacheable while price logic runs in <5ms at the edge.
When things do go wrong — and they will — you need to know in under 10 seconds. Don't rely on origin logs alone. Monitor edge cache status codes: a sudden jump in MISS or BYPASS, or a drop in hit ratio from 95% to 70%, is your earliest warning that a cache key fragmented or a TTL was misconfigured. We also add a synthetic purge test 5 minutes before the sale: purge a single canary URL, measure time-to-revalidate across all 300+ PoPs, and if it’s over 800ms, we know purging won’t be viable during the spike and we’ll lean on short TTLs instead. And if you must experiment during the sale, don’t do it with origin-side A/B tests — you’ll double your cache keys and halve your hit rate. Use A/B Testing Product Pages at CDN Edge: Experimenting on Layout and Content to bucket users at the edge with a cookie and serve variants from the same cached object.
Frequently Asked Questions
What TTL should we use for inventory APIs during a flash sale?
Don't use 60 seconds — you'll oversell. Don't use 0 seconds — you'll DDoS your own origin. For most flash sales, we recommend 2-5 seconds for stock levels with stale-while-revalidate of 30 seconds and stale-if-error of 60 seconds. That keeps data accurate to within a few seconds while keeping your cache hit ratio above 95%. For truly hot SKUs with <20 units left, switch to a 1-second TTL or bypass cache for reservation checks only.
How can we personalize prices without killing CDN cache performance?
You don't need to bypass cache for every user. Keep your product page HTML fully cacheable and inject personalized pricing at the edge using Edge Workers, KV stores, or Edge Dictionaries. The worker fetches the base page from cache (one cached object for all users) and then does a sub-5ms lookup to overlay the correct price. This way you get dynamic pricing without origin round-trips and without fragmenting your cache into thousands of per-user variants.
Can we run A/B tests during a high-traffic flash sale?
You can, but not the way you do it on a normal day. Origin-based A/B testing will fragment your cache and can cut your hit ratio by 30-50%. Instead, run the experiment entirely at the edge. Assign users to variants via a cookie at the CDN, then use edge logic to rewrite layout or content from a single cached origin response. Limit it to 2 variants max during the sale — more variants mean more edge compute and more risk.
What happens if our origin goes down for 30 seconds mid-sale?
If you've configured stale-if-error correctly, shoppers won't notice. The CDN will continue serving the last known good stale response for up to 60 seconds while it retries origin in the background. Without stale-if-error, those same requests become 500/503 errors instantly. We always set stale-if-error=60s and stale-while-revalidate=30s on all sale-critical endpoints specifically for this scenario — it's the cheapest high-availability you can buy.