Design a High-Concurrency Flash Sale System
A flash sale system (e.g., Nike SNKRS sneaker drops, Xiaomi flash sales, Amazon Prime Day lightning deals) sells a strictly limited quantity of high-demand items (e.g., 10,000 items) at steep discounts. At the exact second the sale begins (e.g., 12:00:00 PM), millions of users and automated botnets hit the system simultaneously, creating an extreme write-contention spike that would instantly incinerate standard relational e-commerce databases.
1. Understanding the Problem
Functional Requirements
- Flash Sale Landing Page: Display real-time countdown, item details, and sale status (Upcoming, Live, Sold Out).
- Atomic Inventory Reservation: Deduct inventory with zero overselling (selling 10,001 items when stock is 10,000 is an unacceptable failure).
- Bot & Scalper Defense: Filter out automated bot scripts, multi-account ballot stuffing, and click-spamming tools.
- Asynchronous Order Creation: Once inventory is reserved, allow the user 10 minutes to complete payment and shipping details.
- Inventory Rollback: If a user reserves an item but fails to pay within 10 minutes, the stock is automatically returned to the sale pool.
Non-Functional Requirements
- Extreme Peak Surge Tolerance: System must survive traffic spikes of 100x to 500x normal baseline load in a single second.
- Strict Consistency (Zero Overselling): Inventory deduction must be strictly atomic and deterministic.
- Sub-Second Reservation Latency: Users receive immediate confirmation ("You've secured an item! Complete payment in 10:00") or ("Sold Out") in
< 500ms. - System Isolation: A flash sale event must never degrade or crash the general marketplace, login services, or unrelated product catalog browsing.
Capacity Estimations & Sizing
- Total Registered Shoppers: 10 Million users waiting for the sale.
- Total Stock: 10,000 units.
- Traffic Spike at 12:00:00 PM:
- 1 Million concurrent shoppers clicking "Buy Now" within the first 3 seconds: !
- Database Reality:
- A standard PostgreSQL or MySQL database server handles ~5,000 to 10,000 queries/sec.
- Sending 500,000 concurrent transactions trying to update the exact same inventory row (
UPDATE products SET stock = stock - 1 WHERE id = ?) causes immediate row-lock exhaustion, connection pool starvation, and total server collapse within 500 milliseconds. - Architectural Mandate: Multi-tier traffic shedding, edge token gating, and in-memory Redis atomic Lua execution are non-negotiable.
2. The Set Up
Defining the Core Entities
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FLASH_SALE_ITEM β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β sale_id β UUID β PRIMARY KEY β
β product_id β UUID β INDEX, FK β
β total_stock β INT β e.g. 10,000 β
β available_stock β INT β Physical available β
β flash_price_centsβ INT β Discounted Price β
β start_time β TIMESTAMP β e.g. 12:00:00 PM β
β end_time β TIMESTAMP β e.g. 12:30:00 PM β
β status β VARCHAR(16) β UPCOMING/ACTIVE/ENDEDβ
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SALE_RESERVATION β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β reservation_id β UUID β PRIMARY KEY β
β sale_id β UUID β COMPOSITE INDEX, FK β
β user_id β UUID β COMPOSITE UNIQUE, FK β
β status β VARCHAR(16) β HELD / PAID / EXPIREDβ
β expires_at β TIMESTAMP β 10-Minute Hold TTL β
β created_at β TIMESTAMP β NOT NULL β
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FLASH_ORDER β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β order_id β UUID β PRIMARY KEY β
β reservation_id β UUID β UNIQUE, FK β
β user_id β UUID β INDEX, FK β
β total_cents β INT β Final Billed Amount β
β status β VARCHAR(16) β PENDING / PAID β
β created_at β TIMESTAMP β NOT NULL β
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
The API Design
1. Attempt Flash Sale Purchase (Request Token)β
POST /api/v1/flash-sales/{sale_id}/reserve
Content-Type: application/json
Authorization: Bearer <jwt_token>
Idempotency-Key: fls_99a812-4019-b201
{
"user_id": "usr_88192a01",
"captcha_token": "cf_turnstile_response_token..."
}
Response (200 OK - Success):
{
"status": "RESERVED",
"reservation_id": "res_441029",
"checkout_url": "/checkout?reservation_id=res_441029",
"expires_at": "2026-09-22T23:10:00Z"
}
Response (200 OK - Sold Out):
{
"status": "SOLD_OUT",
"message": "All items have been claimed."
}
3. High-Level Design
Multi-Tier Traffic Absorption Architecture
500,000 QPS (100% Raw Traffic)
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TIER 1: CDN Edge Caching (Cloudflare / Fastly) β
β - Static assets and product images 100% cached. β
β - Edge Turnstile CAPTCHA filters 70% of bots. β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 150,000 QPS Remaining
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TIER 2: API Gateway Token Gating & Rate Limiting β
β - 1 request per user account limit. β
β - Token bucket sheds traffic exceeding 2x total stock. β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 20,000 QPS Remaining
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TIER 3: In-Memory Redis Atomic Lua Reservation β
β - Atomic stock decrement via single-threaded script. β
β - Emits 10,000 successful reservations to Kafka. β
β - Remaining requests get instant "Sold Out". β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Exactly 10,000 Successful Orders!
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TIER 4: Asynchronous Database & Order Worker Pool β
β - Smoothly writes 10,000 orders to PostgreSQL at 50/s. β
β - ZERO database CPU spikes or lock contention! β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
4. Potential Deep Dives & Bottlenecks
Deep Dive 1: Atomic Inventory Deduction (Redis Lua Script)
How do we guarantee that exactly 10,000 units are sold with zero race conditions at 50,000 QPS?
- Why Redis Lua: Redis executes Lua scripts as a single atomic unit. No other command can read or write to the keys while the script executes.
- One User, One Item Constraint: The script checks a Redis Set (
sale:{id}:users) to ensure a user cannot claim two items.
-- KEYS[1]: sale:inventory (Number of items remaining)
-- KEYS[2]: sale:users (Set of user IDs who already reserved)
-- ARGV[1]: user_id
-- ARGV[2]: reservation_id
-- ARGV[3]: hold_ttl_seconds (600)
-- Step 1: Check if user already reserved an item
if redis.call('SISMEMBER', KEYS[2], ARGV[1]) == 1 then
return {0, "ALREADY_RESERVED"}
end
-- Step 2: Check available inventory
local stock = tonumber(redis.call('GET', KEYS[1]) or '0')
if stock <= 0 then
return {0, "SOLD_OUT"}
end
-- Step 3: Atomically decrement stock and record user
redis.call('DECR', KEYS[1])
redis.call('SADD', KEYS[2], ARGV[1])
-- Step 4: Record temporary reservation hold
local res_key = "reservation:" .. ARGV[2]
redis.call('HSET', res_key, 'user_id', ARGV[1], 'status', 'HELD')
redis.call('EXPIRE', res_key, tonumber(ARGV[3]))
return {1, "SUCCESS"}
- Execution Speed: This Lua script executes in 0.05 milliseconds! A single Redis primary node can process over 50,000 reservations per second with zero race conditions.
Deep Dive 2: Token Gating (The "Sold Out" Short-Circuit)
What happens to the 990,000 users who clicked "Buy Now" but didn't get one of the 10,000 items?
- The Problem: If 990,000 users keep hitting Redis, network interfaces and CPU will be exhausted on pointless queries.
- The Global Sold-Out Flag:
- As soon as the Redis inventory reaches
0, the Lua script sets an in-memory key:sale:{id}:is_sold_out = true. - The API Gateways cache this flag locally in their process RAM (using a 1-second TTL).
- Once the gateway sees
is_sold_out == true, it immediately short-circuits all subsequent requests at the API Gateway layer, returning{"status": "SOLD_OUT"}without ever contacting Redis or the database!
- As soon as the Redis inventory reaches
Deep Dive 3: Automatic Rollback on Checkout Abandonment
What if 500 users claim an item in Redis but their credit card fails or they abandon their cart?
- The Rollback Pipeline:
- When a reservation is created, an event
{reservation_id, user_id, sale_id}is placed into a Delayed Queue (Redis Sorted Set) wherescore = expiration_timestamp (now + 600s). - If the user completes payment within 10 minutes, the order service marks the reservation as
PAIDin PostgreSQL and deletes it from the delayed queue. - A background Rollback Worker polls the delayed queue:
- For any reservations reaching their 10-minute expiration without being paid:
- Executes an atomic rollback script in Redis:
INCR sale:inventoryandSREM sale:users user_id. - Stock is immediately restored to the active sale pool, allowing other waiting users to purchase!
- When a reservation is created, an event
5. Architectural Trade-Off Matrix
| Design Area | Option A | Option B | Selected Choice & Rationale |
|---|---|---|---|
| Inventory Store | PostgreSQL SELECT FOR UPDATE | In-Memory Redis Lua Atomic CAS | Redis Lua: Relational row locks under 500K QPS freeze connection pools in 500ms. Redis handles atomic checks in sub-millisecond memory. |
| Order Creation | Synchronous DB insert during click | Asynchronous Kafka Queue to Worker Pool | Asynchronous Queue: Buffers the 10,000 orders and writes them to PostgreSQL at a steady, sustainable 50 orders/sec, completely protecting the database. |
| Page Rendering | Dynamic Server-Side Render (SSR) | Static CDN HTML + Dynamic Token API | Static CDN: CDN absorbs 99% of page traffic and image assets; origin servers only handle the tiny lightweight /reserve JSON endpoint. |
6. What is Expected at Each Level?
Mid-Level (L4 / IC4)
- Identifies the critical bottleneck of database row-level locking during flash sales.
- Proposes caching stock in Redis.
- Understands the need to prevent double buying (one item per user).
- Designs basic asynchronous order creation using message queues.
Senior (L5 / IC5)
- Details the multi-tier traffic shedding architecture (CDN API Gateway Token Bucket Redis Lua Async DB).
- Implements atomic stock verification and user deduplication using a single-threaded Redis Lua script.
- Solves checkout abandonment using delayed queues and automatic stock rollbacks.
- Designs the "Sold Out" short-circuit flag at the API gateway layer to protect Redis from redundant queries.
Staff+ (L6 / Principal)
- Designs advanced bot mitigation and fairness algorithms: Device fingerprinting, Proof-of-Work (PoW) client hashing challenges, and lottery-based ballot drops (Nike SNKRS draw model).
- Architects multi-region inventory sharding: Handling global flash sales across US, Europe, and Asia without cross-region WAN replication race conditions (regional stock allocation quotas).
- Details failure mode resilience: Handling Redis master node failure midway through a sale with zero inventory over-allocation or phantom reservations.
