Back to explorer
System Architectures• 45 Min

Design a Flash Sale System (High Concurrency Checkout)

HARD

Design a Flash Sale System (High Concurrency Checkout)

Flash sales—such as limited sneaker drops, Black Friday PlayStation launches, or Taylor Swift concert tickets—present one of the most brutal engineering challenges in distributed systems: millions of concurrent users slamming the platform to buy only 1,000 available items within the span of 30 seconds.


1. System Requirements

Functional Requirements

  1. Flash Sale Landing Page: Display item details, current price, countdown timer, and live inventory state (IN_STOCK, FEW_LEFT, SOLD_OUT).
  2. Virtual Waiting Room: When incoming traffic surpasses backend capacity, queue users fairly in a virtual waiting room with an estimated wait time.
  3. Atomic Inventory Reservation: Reserve an item for 10 minutes upon successful checkout entry. If the user completes payment within 10 minutes, the order is confirmed; otherwise, inventory releases back to the pool.
  4. Order Placement & Idempotent Payment: Securely finalize checkout with payment gateway integrations without double charges.
  5. Anti-Bot & Abuse Prevention: Block scripted automated bot scalpers using CAPTCHAs, device fingerprinting, and account velocity checks.

Non-Functional Requirements

  1. Strict Zero Overselling: 1,000 available items must result in *at most* 1,000 confirmed sales. Overbooking is catastrophic.
  2. Database Protection: The relational database must never be exposed to millions of concurrent read/write requests; traffic must be absorbed at the edge and memory tier.
  3. High Availability: 99.99% uptime during the 30-minute flash sale window.
  4. Fairness: Prevent early request leaks and ensure fair FIFO or lottery-based admission from the virtual waiting room.

2. Capacity & Scale Estimation

  • Total Inventory: 5,000 units.
  • Registered Shoppers: 2,000,000 users.
  • Traffic Surge at Drop (T=0): 500,000 concurrent active users clicking "Buy Now" within the first 5 seconds $\rightarrow 100,000\text{ peak QPS}$.
  • Database Master Capacity: Standard PostgreSQL master handles ~2,000 write transactions/sec.
  • Core Problem: $100,000\text{ write QPS} \gg 2,000\text{ DB write capacity}$. The system will experience catastrophic database thread pool exhaustion without front-line shielding.

3. High-Level Architecture Blueprint

code
                                  +-----------------------+
                                  |    Shopper Clients    |
                                  +-----------+-----------+
                                              |
                                              v
                                  +-----------------------+
                                  | Cloudflare CDN Edge   |
                                  | (Static Assets Cache) |
                                  +-----------+-----------+
                                              |
                                              v
                                  +-----------------------+
                                  |  Virtual Waiting Room |
                                  | (Token Bucket / FIFO) |
                                  +-----------+-----------+
                                              | Admitted Shoppers (10k QPS)
                                              v
                                  +-----------------------+
                                  |   Flash Sale Gateway  |
                                  +-----------+-----------+
                                              |
                                              v
                                  +-----------------------+
                                  | Redis Cluster Memory  |
                                  | (Atomic Lua Script    |
                                  | Inventory Reservation)|
                                  +-----+-----------+-----+
                                        |           |
             +--------------------------+           +--------------------------+
             | (Reserved Success)                   | (Stock Depleted)
             v                                      v
+------------------------+             +------------------------+
|  Kafka Order Queue     |             | Return Immediate       |
|  (Buffered Writes)     |             | "Sold Out" Payload     |
+-----------+------------+             +------------------------+
            |
            v
+------------------------+
|  Order Execution Svc   |
|  (RDBMS Transaction)   |
+-----------+------------+
            |
            v
+------------------------+
|  Payment Gateway (PSP) |
+------------------------+

4. Deep Dives & Core Engineering Patterns

Deep Dive 1: The Virtual Waiting Room

Before shoppers hit application servers, they encounter an edge Virtual Waiting Room:

  1. At $T-15\text{ minutes}$, arriving visitors receive a cryptographically signed cookie containing an assigned queue ticket number.
  2. The waiting room uses WebSockets or periodic polling (every 5s) to check queue progression.
  3. At drop time, the admission engine admits users in batches (e.g. 2,000 users per second) matching backend processing capacity.
  4. Admitted users receive a temporary JWT checkout token valid for 10 minutes.

Deep Dive 2: Atomic Inventory Reservation with Redis Lua

Relational row locks (SELECT ... FOR UPDATE) cause deadlocks under 100,000 concurrent updates. Instead, inventory is pre-warmed in Redis and reserved atomically via Lua:

lua
-- KEYS[1]: flash_sale:{item_id}:stock
-- KEYS[2]: flash_sale:{item_id}:reservations
-- ARGV[1]: user_id
-- ARGV[2]: quantity (e.g., 1)
-- ARGV[3]: reservation_ttl_seconds (e.g., 600)

local stock = tonumber(redis.call('get', KEYS[1]) or '0')
if stock < tonumber(ARGV[2]) then
    return -1 -- SOLD_OUT
end

-- Check if user already has an active reservation
if redis.call('hexists', KEYS[2], ARGV[1]) == 1 then
    return -2 -- ALREADY_RESERVED
end

-- Atomically decrement stock and record user reservation
redis.call('decrby', KEYS[1], ARGV[2])
redis.call('hset', KEYS[2], ARGV[1], ARGV[3])
return 1 -- SUCCESS

Because Redis executes Lua scripts as a single atomic, uninterruptible operation on its event loop, race conditions are mathematically impossible.

Deep Dive 3: Inventory Release on Payment Expiration

If a user reserves an item but abandons checkout:

  • The reservation carries a 10-minute TTL.
  • When the timer expires, a Redis KeySpace notification or a delayed Kafka message triggers a compensation worker that increments the Redis inventory counter: INCRBY flash_sale:{item_id}:stock 1.

Key Architecture Trade-Offs & Decision Matrix

Decision FactorChoice AChoice BSelected Architectural Rationale
Communication ProtocolREST over HTTP/2WebSockets / gRPCUse REST for stateless CRUD APIs; use WebSockets for low-latency bidirectional streaming.
State ManagementCentralized Distributed StoreLocal Memory with Sticky SessionsCentralized Redis / Database state allows seamless container auto-scaling and zero-downtime rolling deploys.
Concurrency ControlOptimistic Locking (version column)Pessimistic Locking (SELECT FOR UPDATE)Optimistic locking delivers superior throughput for low-conflict consumer operations.