Skip to main content

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

  1. Flash Sale Landing Page: Display real-time countdown, item details, and sale status (Upcoming, Live, Sold Out).
  2. Atomic Inventory Reservation: Deduct inventory with zero overselling (selling 10,001 items when stock is 10,000 is an unacceptable failure).
  3. Bot & Scalper Defense: Filter out automated bot scripts, multi-account ballot stuffing, and click-spamming tools.
  4. Asynchronous Order Creation: Once inventory is reserved, allow the user 10 minutes to complete payment and shipping details.
  5. 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: PeakΒ Ingressβ‰ˆ300,000Β toΒ 500,000Β requests/sec\text{Peak Ingress} \approx \mathbf{300,000\text{ to 500,000 requests/sec}}!
  • 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

Flash Sale Multi-Tier Traffic Shedding & Atomic Lua EngineInteractive Topology
Peak QPS
250K/sec
Hold TTL
600 sec
Consistency
Strict ACID
Double Booking
0.00%
Active Scenario: Atomic stock deduction via Redis Lua script with 10-minute hold TTL
ShoppersWeb / AppVirtual QueueWaiting RoomRedis ClusterLua Stock LedgerOrder ServiceSaga CoordinatorPostgres DBACID Final Ledger
Interactive Component Inspector
Click any architecture node on the SVG canvas to view under-the-hood engine mechanics, failure gotchas, and runtime tags.

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:
    1. As soon as the Redis inventory reaches 0, the Lua script sets an in-memory key: sale:{id}:is_sold_out = true.
    2. The API Gateways cache this flag locally in their process RAM (using a 1-second TTL).
    3. 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!

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:
    1. When a reservation is created, an event {reservation_id, user_id, sale_id} is placed into a Delayed Queue (Redis Sorted Set) where score = expiration_timestamp (now + 600s).
    2. If the user completes payment within 10 minutes, the order service marks the reservation as PAID in PostgreSQL and deletes it from the delayed queue.
    3. 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:inventory and SREM sale:users user_id.
      • Stock is immediately restored to the active sale pool, allowing other waiting users to purchase!

5. Architectural Trade-Off Matrix

Design AreaOption AOption BSelected Choice & Rationale
Inventory StorePostgreSQL SELECT FOR UPDATEIn-Memory Redis Lua Atomic CASRedis Lua: Relational row locks under 500K QPS freeze connection pools in 500ms. Redis handles atomic checks in sub-millisecond memory.
Order CreationSynchronous DB insert during clickAsynchronous Kafka Queue to Worker PoolAsynchronous Queue: Buffers the 10,000 orders and writes them to PostgreSQL at a steady, sustainable 50 orders/sec, completely protecting the database.
Page RenderingDynamic Server-Side Render (SSR)Static CDN HTML + Dynamic Token APIStatic 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 β†’\to API Gateway Token Bucket β†’\to Redis Lua β†’\to 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.
πŸ“–
Track Page Progress0 / 635 Read
Knowledge Base Completion0%