
Flash Sale Scaling
- 59 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Prepare for Black Friday and flash sales with pre-warming, queue-based order intake, atomic inventory reservation, and circuit breakers.
About
Covers infrastructure patterns for 50-100x traffic spikes: back-pressure queues, Redis atomic inventory reservation, and graceful degradation. A developer uses it when planning a product drop or after past sales caused checkout timeouts and oversells.
- Per-platform scaling strategy table
- Queue-based order intake, Redis inventory reservation, and circuit breakers
Flash Sale Scaling by the numbers
- 59 all-time installs (skills.sh)
- Ranked #688 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill flash-sale-scalingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 59 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Prepare for Black Friday and flash sales with pre-warming, queue-based order intake, atomic inventory reservation, and circuit breakers.
Files
Flash Sale Scaling
Overview
Flash sales and product drops generate traffic spikes 50–100× normal load, arriving within seconds of sale start. Without preparation, the checkout service collapses, inventory oversells, and customers see error pages. This skill covers the infrastructure patterns needed to handle extreme traffic: pre-warming, queue-based order intake with back-pressure, Redis-based atomic inventory reservation, and circuit breakers that degrade gracefully under load.
When to Use This Skill
- When planning a flash sale, limited product drop, or major promotional event
- When past sales have caused checkout timeouts, oversells, or database failures
- When Black Friday/Cyber Monday planning is underway and infrastructure needs review
- When a new product announcement is expected to drive sudden high-demand traffic
Core Instructions
Step 1: Determine your platform and what you can control
| Platform | Flash Sale Scaling Strategy | Key Actions |
|---|---|---|
| Shopify | Shopify scales automatically — no infrastructure work needed | Focus on theme speed (cache pages, optimize images), enable Shopify's queue page for high-demand launches, use Launchpad (Shopify Plus) to schedule and automate the sale |
| WooCommerce | You own the server — significant prep required | Upgrade to a scalable host (Cloudways, Kinsta, WP Engine), enable Redis Object Cache + WP Rocket page cache, configure Cloudflare, run load tests 1–2 weeks before |
| BigCommerce | BigCommerce scales automatically | Use BigCommerce's flash sale feature (preview: shows estimated wait time); focus on catalog readiness and theme performance |
| Custom / Headless | Full infrastructure control needed | Apply all patterns below: pre-warm scaling, Redis inventory, queue-based checkout, circuit breakers |
Step 2: Platform-specific flash sale preparation
---
Shopify
Shopify handles scaling automatically and can handle virtually any traffic spike. Your prep work is:
1. Enable Shopify's high-demand checkout queue (Shopify Plus):
- Go to Online Store → Preferences → Checkout
- Enable Checkout concurrency — this activates Shopify's virtual waiting room for high-traffic drops
- For limited-inventory products (product drops): use the built-in inventory reservation so customers who enter checkout have their item held for 10 minutes
2. Use Launchpad (Shopify Plus) for sale scheduling:
- Install Launchpad from the Shopify App Store (free for Plus merchants)
- Schedule sale start/end times, price changes, and inventory availability in advance
- Launchpad handles atomic activation at the scheduled time — avoid manual price changes under load
3. Pre-test your store performance (all Shopify plans):
- Go to Online Store → Themes and click View report to see your store's Core Web Vitals
- Run Google PageSpeed Insights on your most critical pages (product page, collection page, checkout)
- Fix any red/orange issues before the sale — compressing images is the most common fix
4. Notify Shopify support before major launches (Shopify Plus):
- Submit a Flash Sale notification via your Plus support channel — Shopify can pre-allocate resources and monitor your store during the event
---
WooCommerce
WooCommerce requires significant infrastructure work before a high-traffic event:
Hosting upgrade (most critical): 1. Ensure your hosting plan can scale: use Cloudways (horizontal scaling with one click), Kinsta (auto-scaling), or WP Engine (auto-scaling add-on) — not shared hosting 2. If on shared hosting, migrate to a VPS or managed WordPress host at least 1 week before the sale to allow stabilization 3. On Cloudways: go to Servers → [server] → Vertical Scaling before the event and select a larger server size; scale back down after
Cache stack: 1. Install and configure WP Rocket (page cache) + Redis Object Cache (database query cache) 2. WP Rocket: enable Preload cache to warm pages before the sale starts 3. Redis Object Cache: verify Redis is active (green status in Settings → Redis) 4. Enable Cloudflare and set Caching level to Standard; add your store URLs to Cloudflare Page Rules with Cache Everything for product and shop pages
Inventory oversell prevention: 1. Enable WooCommerce's built-in inventory management: WooCommerce → Settings → Products → Inventory → check Enable Stock Management 2. Set Hold stock (minutes) to 60 — this holds an item in a customer's cart for 60 minutes before releasing it back to inventory 3. For limited items: set Allow backorders to Do not allow so the product goes out-of-stock exactly at 0 inventory
Load test before the sale: 1. Use Loader.io (free tier: 1 target, 10K connections) to simulate your expected peak traffic against your staging site 2. Test the checkout flow specifically — product browse is usually cached; checkout hits the database 3. Fix any failures or slow responses before the sale date
---
Custom / Headless
Pre-warm infrastructure (run 30 minutes before sale start):
# Kubernetes: scale checkout deployment up before sale
kubectl scale deployment checkout-service --replicas=50
# Or schedule automatic scaling with a CronJob
# See the CronJob example belowAtomic inventory reservation with Redis (prevents oversells):
// lib/inventory.js
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
// Initialize inventory in Redis before the sale
export async function initializeInventory(productId, quantity) {
await redis.set(`inventory:${productId}`, quantity);
}
// Atomic check-and-decrement using Lua script (runs on Redis server, no race conditions)
const LUA_RESERVE = `
local current = tonumber(redis.call('GET', KEYS[1]))
if current == nil then return -1 end
if current < tonumber(ARGV[1]) then return 0 end
redis.call('DECRBY', KEYS[1], tonumber(ARGV[1]))
return 1
`;
export async function reserveInventory(productId, quantity) {
const result = await redis.eval(LUA_RESERVE, 1, `inventory:${productId}`, quantity);
if (result === 1) return 'reserved';
if (result === 0) return 'out_of_stock';
return 'not_found';
}
export async function releaseInventory(productId, quantity) {
await redis.incrby(`inventory:${productId}`, quantity);
}Queue-based order intake (fast response, async processing):
// checkout API — responds instantly, processes in background
export async function POST(req) {
const order = await req.json();
// 1. Reserve inventory atomically
const reservation = await reserveInventory(order.productId, order.quantity);
if (reservation === 'out_of_stock') {
return Response.json({ error: 'Sold out' }, { status: 409 });
}
// 2. Enqueue — responds to user in <100ms
const orderId = crypto.randomUUID();
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.ORDER_QUEUE_URL,
MessageBody: JSON.stringify({ orderId, ...order }),
}));
return Response.json({
orderId,
status: 'queued',
message: 'Your order is being processed. You will receive a confirmation email shortly.',
});
}
// Background order processor (separate service consuming SQS)
export async function processOrder(message) {
const order = JSON.parse(message.Body);
try {
await capturePayment(order);
await db.orders.create(order);
await sendOrderConfirmationEmail(order);
} catch (err) {
await releaseInventory(order.productId, order.quantity); // release on failure
throw err; // let SQS retry
}
}Circuit breaker for payment processor:
import CircuitBreaker from 'opossum';
const paymentBreaker = new CircuitBreaker(captureStripePayment, {
timeout: 5000, // 5s timeout per call
errorThresholdPercentage: 30, // open if 30% fail
resetTimeout: 30000, // try again after 30s
});
// Fallback: queue for retry instead of failing the customer
paymentBreaker.fallback(async (order) => {
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.PAYMENT_RETRY_QUEUE_URL,
MessageBody: JSON.stringify(order),
DelaySeconds: 30,
}));
return { status: 'payment_queued' };
});Kubernetes pre-scale CronJob:
apiVersion: batch/v1
kind: CronJob
metadata:
name: flash-sale-prescale
spec:
schedule: "30 11 * * 5" # 30 min before noon Friday sale
jobTemplate:
spec:
template:
spec:
containers:
- name: scaler
image: bitnami/kubectl
command: [kubectl, scale, deployment/checkout-service, --replicas=100]
restartPolicy: OnFailureRedis waiting room for extremely high-demand drops:
// Fair queue: customers get a position number when they arrive
export async function enterWaitingRoom(sessionId, productId) {
const queueKey = `sale_queue:${productId}`;
await redis.zadd(queueKey, Date.now(), sessionId); // sorted set, score = timestamp (FIFO)
const position = await redis.zrank(queueKey, sessionId);
return { position: (position ?? 0) + 1 };
}
// Periodically admit batches to checkout
export async function admitFromQueue(productId, batchSize) {
const admitted = await redis.zpopmin(`sale_queue:${productId}`, batchSize);
// Notify each admitted customer they can proceed to checkout
for (let i = 0; i < admitted.length; i += 2) {
await notifyCustomerAdmitted(admitted[i], productId);
}
}Best Practices
- Reserve inventory in Redis, not the database — atomic Redis operations handle thousands of concurrent reservations per second; PostgreSQL row locking under the same load causes timeouts and deadlocks
- Accept orders into a queue during spikes — the user-facing checkout should respond in under 100ms even at peak; defer payment capture, DB writes, and emails to background workers
- Set aggressive timeouts on every external call — a 30-second Stripe timeout under load multiplies into thousands of held connections; use 5-second timeouts with circuit-breaker escalation
- Load test at 2–3× expected peak — test at exactly expected capacity leaves no headroom; size for 3× to account for uneven traffic distribution
- Communicate queue status to customers — show real-time position in the waiting room; customers with visible progress are far more patient than those staring at a spinner
Common Pitfalls
| Problem | Solution |
|---|---|
| Oversells despite inventory check | Use Redis atomic Lua script for check-and-decrement; never check inventory in the application layer then update separately in two operations |
| Auto-scaling too slow to respond | Pre-warm to minimum capacity 30 minutes before the event; configure scale-out cooldown to 30 seconds, not the default 5 minutes |
| Circuit breaker opens on brief latency spike | Tune volumeThreshold and errorThresholdPercentage conservatively; use timeout as the primary trigger rather than error rate for flash sales |
| WooCommerce oversells during a spike | Enable WooCommerce stock management and set Hold stock to 60 minutes; upgrade to a host with Redis Object Cache to reduce database lock contention |
Related Skills
- @database-optimization-commerce
- @ecommerce-caching
- @monitoring-alerting-commerce
- @load-testing-commerce
- @edge-commerce
{
"context": "Tests whether the agent uses opossum with correct threshold settings, tracks circuit state changes using prom-client with the right labels, implements a fallback that queues orders with a delay, routes DB reads/writes to separate pools, and caches product data in Redis with the correct TTL and key pattern.",
"type": "weighted_checklist",
"checklist": [
{
"name": "opossum library",
"max_score": 8,
"description": "Imports and uses opossum for circuit breakers (not a custom circuit breaker implementation or other library)"
},
{
"name": "timeout: 5000",
"max_score": 8,
"description": "Circuit breaker(s) configured with timeout: 5000 (5 seconds)"
},
{
"name": "errorThresholdPercentage: 30",
"max_score": 8,
"description": "Circuit breaker(s) configured with errorThresholdPercentage: 30"
},
{
"name": "resetTimeout: 30000",
"max_score": 8,
"description": "Circuit breaker(s) configured with resetTimeout: 30000 (30 seconds)"
},
{
"name": "volumeThreshold: 10",
"max_score": 8,
"description": "Circuit breaker(s) configured with volumeThreshold: 10"
},
{
"name": "prom-client Counter",
"max_score": 8,
"description": "Uses a prom-client Counter (not Gauge or Histogram) to track circuit state transitions"
},
{
"name": "service + state labels",
"max_score": 8,
"description": "The prom-client Counter has labelNames including both 'service' and 'state'"
},
{
"name": "Fallback with DelaySeconds: 30",
"max_score": 10,
"description": "Payment circuit fallback queues the order to SQS with DelaySeconds: 30"
},
{
"name": "Separate DB pools",
"max_score": 10,
"description": "db-router.ts uses two separate pg Pool instances: one for primary (writes) and one for the read replica"
},
{
"name": "Read/write routing",
"max_score": 8,
"description": "db.write() uses the primary pool and db.read() uses the replica pool (not both using the same pool)"
},
{
"name": "setex TTL 300",
"max_score": 8,
"description": "getProductCached uses redis.setex with a TTL of 300 seconds (not a different TTL or redis.set with EX option in a different value)"
},
{
"name": "product cache key pattern",
"max_score": 8,
"description": "Product cache keys follow the pattern product:{productId}"
}
]
}
Resilient Payment and Database Layer
Problem/Feature Description
An e-commerce platform's checkout service calls Stripe for payment capture and PostgreSQL for order writes. During last month's product launch, a 12-second Stripe latency spike caused 4,000 in-flight requests to each hold a thread waiting for a response. The connection pool drained in under 90 seconds, the service stopped responding, and the ops team had to manually restart pods. Recovery took 11 minutes. Post-mortem identified two root causes: no mechanism to detect and fast-fail calls to an unhealthy Stripe, and no fallback path to keep orders flowing when payment capture is temporarily unavailable.
The team wants to wrap both the Stripe payment call and the database write in circuit breakers that fail fast once a service is struggling, track their state transitions in a format compatible with Prometheus, and fall back to a retry queue when payment fails rather than showing customers an error. Catalog reads should also be separated from writes: during sales the product pages generate enormous read traffic that should not compete with order writes for connections on the primary database. Frequently-accessed product data should be served from cache when available.
Output Specification
Write the following TypeScript files:
circuit-breakers.ts— exportspaymentCircuitanddbCircuit, their Prometheus metrics, and the fallback behavior for the payment circuitdb-router.ts— exports adbobject withread()andwrite()methods routing to separate connection pools, and agetProductCached()function
You may stub out the actual Stripe and database calls (e.g., captureStripePayment, writeOrderToDatabase) — focus on the circuit breaker wiring, metric instrumentation, fallback logic, and DB routing.
Include a config.md file (max 150 words) explaining the circuit breaker thresholds chosen and why timeout is the primary trigger.
{
"context": "Tests whether the agent uses ioredis with a server-side Lua script for atomic inventory reservation, implements correct return semantics, releases inventory via incrby on failure, syncs to database, and implements the fair waiting room using Redis sorted-set commands.",
"type": "weighted_checklist",
"checklist": [
{
"name": "ioredis library",
"max_score": 8,
"description": "Imports and uses ioredis (not redis, node-redis, or any other Redis client library)"
},
{
"name": "Lua script via eval",
"max_score": 12,
"description": "Uses redis.eval() to execute a Lua script for atomic inventory check-and-decrement (not separate GET + DECRBY calls)"
},
{
"name": "Reserve returns 1",
"max_score": 8,
"description": "Lua script returns 1 to indicate a successful reservation"
},
{
"name": "Reserve returns 0",
"max_score": 8,
"description": "Lua script returns 0 to indicate out-of-stock (current quantity less than requested)"
},
{
"name": "Reserve returns -1",
"max_score": 8,
"description": "Lua script returns -1 (or handles nil) to indicate the product key does not exist"
},
{
"name": "incrby release",
"max_score": 10,
"description": "releaseInventory uses redis.incrby() (not redis.set or redis.incr) to return stock to the pool"
},
{
"name": "DB sync function",
"max_score": 8,
"description": "syncInventoryToDatabase iterates over inventory:* keys and calls a database update for each product"
},
{
"name": "zadd with timestamp",
"max_score": 10,
"description": "enterWaitingRoom uses redis.zadd with the current timestamp (Date.now() or similar) as the score for FIFO ordering"
},
{
"name": "zrank for position",
"max_score": 8,
"description": "enterWaitingRoom uses redis.zrank to retrieve the customer's queue position"
},
{
"name": "zpopmin for admission",
"max_score": 10,
"description": "admitFromQueue uses redis.zpopmin to remove and return the next batch of customers from the queue"
},
{
"name": "Inventory key pattern",
"max_score": 5,
"description": "Inventory Redis keys follow the pattern inventory:{productId}"
},
{
"name": "Queue key pattern",
"max_score": 5,
"description": "Waiting room Redis keys follow the pattern sale_queue:{productId} (or equivalent queue-scoped-by-product pattern)"
}
]
}
Sneaker Drop Inventory Service
Problem/Feature Description
A streetwear brand is launching a limited-edition sneaker drop — only 500 pairs available — and expects roughly 80,000 unique visitors to hit the site within the first 30 seconds of the sale going live. Previous drops ran inventory checks against the PostgreSQL database: every checkout read the current stock, confirmed availability, then decremented in a separate write. Under high concurrency this race condition caused 120 pairs to be oversold during the last drop, triggering chargebacks and brand damage.
The team wants to rewrite the inventory layer to use an in-memory store for all reservation operations during the sale. Reservations must be fully atomic — no pair should be sold that doesn't exist — and if an order later fails during payment processing, the reservation must be returned to the pool. Because fair access matters for the brand's reputation, the team also wants a waiting room: customers who arrive when inventory is available get a queue position and are admitted in arrival order rather than at random.
Output Specification
Write a single TypeScript file inventory.ts implementing the following exported functions:
initializeInventory(productId, quantity)— sets the initial stock countreserveInventory(productId, quantity)— atomically reserves stock; returns one of'reserved','out_of_stock', or'not_found'releaseInventory(productId, quantity)— returns stock to the pool when an order failssyncInventoryToDatabase()— writes current Redis stock counts back to the databaseenterWaitingRoom(sessionId, productId)— adds a customer to the fair queue; returns their position and a signed tokenadmitFromQueue(productId, batchSize)— pops the next batch of customers from the queue and notifies them
You may use placeholder implementations for external calls (e.g., db.products.updateInventory, signQueueToken, notifyCustomerAdmitted) — focus on the Redis interaction logic.
Include a brief DESIGN.md file (max 200 words) explaining the atomicity guarantees of your inventory reservation approach.
{
"context": "Tests whether the agent uses @aws-sdk/client-sqs for SQS integration with correct FIFO queue parameters, implements a database-free fast-path checkout, releases inventory and notifies the customer on processing failure, configures the HPA with correct replica counts and CPU target, and writes an Artillery load test with the correct phase structure and scenario weights.",
"type": "weighted_checklist",
"checklist": [
{
"name": "@aws-sdk/client-sqs",
"max_score": 8,
"description": "Imports SQSClient and SendMessageCommand from @aws-sdk/client-sqs (AWS SDK v3), not from the legacy aws-sdk v2 package"
},
{
"name": "No DB call in fast path",
"max_score": 10,
"description": "The POST checkout handler does NOT make any database write before returning — it validates, reserves in Redis (or stub), and enqueues only"
},
{
"name": "MessageGroupId = customerId",
"max_score": 10,
"description": "The SQS SendMessageCommand sets MessageGroupId to the customer's ID (not a static value or orderId)"
},
{
"name": "MessageDeduplicationId = orderId",
"max_score": 10,
"description": "The SQS SendMessageCommand sets MessageDeduplicationId to the orderId"
},
{
"name": "status: 'queued' response",
"max_score": 8,
"description": "The POST handler returns a response with status field set to 'queued' without waiting for DB write or payment"
},
{
"name": "Release + notify on failure",
"max_score": 8,
"description": "processOrder catch block calls both releaseInventory (or equivalent) and a customer notification before rethrowing"
},
{
"name": "HPA minReplicas: 50",
"max_score": 10,
"description": "hpa.yaml sets minReplicas to 50"
},
{
"name": "HPA CPU target: 60",
"max_score": 8,
"description": "hpa.yaml sets the CPU averageUtilization target to 60"
},
{
"name": "Artillery warm-up phase",
"max_score": 8,
"description": "load-test.yml contains a warm-up phase with duration: 60 and arrivalRate: 10"
},
{
"name": "Artillery spike phase",
"max_score": 8,
"description": "load-test.yml contains a spike phase with duration: 30 and arrivalRate: 500"
},
{
"name": "Artillery scenario weights",
"max_score": 8,
"description": "load-test.yml includes two scenarios: one with weight 70 (checkout flow) and one with weight 30 (catalog browse)"
},
{
"name": "Sustained load phase",
"max_score": 4,
"description": "load-test.yml contains a sustained load phase with duration: 120 and arrivalRate: 100"
}
]
}
Black Friday Checkout Service and Infrastructure Readiness
Problem/Feature Description
A fashion retailer is preparing for their biggest Black Friday sale. Last year the checkout API made a synchronous database write for every order, which worked fine at normal traffic but collapsed under the 40× spike when the sale opened. The database became the bottleneck, latency climbed above 30 seconds, and customers saw timeout errors before the team could scale. This year, the engineering team wants three things in place before the sale goes live.
First, the checkout API needs a fast path: accept an order, do the minimum work necessary, and immediately return a confirmation to the customer — without touching the database synchronously. Orders queued this way must be processed reliably in the background, with inventory reservations released and customers notified if processing fails. Second, the Kubernetes deployment needs an autoscaling configuration that can sustain the initial traffic surge, because reactive scaling alone won't spin up pods fast enough when thousands of visitors arrive simultaneously. Third, before the sale, the team needs to run a load test that simulates realistic Black Friday traffic — both shoppers racing to checkout and visitors browsing the catalog — to verify the system can handle more than just the expected peak.
Output Specification
Produce the following files:
checkout.ts— the Next.js-style route handler (POSTexport) and the SQS consumer (processOrderexport) implementing the fast-path checkout and background order processorhpa.yaml— Kubernetes HorizontalPodAutoscaler manifest for the checkout-service deploymentload-test.yml— Artillery load test configuration file
You may stub external calls (e.g., capturePayment, sendOrderConfirmationEmail, reserveInventory) — focus on the SQS message structure, queue routing, and the shape of the configuration files.
{
"name": "finsi/flash-sale-scaling",
"version": "0.1.0",
"summary": "Auto-scaling, queue-based ordering, and circuit breakers for traffic spikes",
"skills": {
"flash-sale-scaling": {
"path": "SKILL.md"
}
}
}