Frontend Labs
⋯

40. Ticketing & Flash Sales (Ticketmaster)

Business context. A huge on-sale event: 2M fans for 50k seats. The outcomes are fairness and trust (bots and crashes produce headlines), sell-through, and no overselling. The demand spike is 100–1000× the normal level for 30 minutes.

Requirements. Browse the event; join the sale; pick seats or best available; hold them for ~10 minutes; pay; get the ticket. Never sell a seat twice. Survive 2M concurrent users. Fair ordering.

Design.

Users ─▶ CDN (static event page; fully cached) ─▶ Virtual waiting room (queue service)
            assign random/arrival-order position → token; admit at the rate the backend can sustain
        ─▶ Admitted users (signed admission token) ─▶ Seat map service (cached snapshot, refreshed per second)
        ─▶ Reserve: Inventory service — atomic hold:
              UPDATE seats SET status='HELD', hold_id=?, expires_at=now()+10m
              WHERE event_id=? AND seat_id IN (...) AND status='AVAILABLE'   -- all-or-nothing check
           (or Redis Lua per-section counters for "best available" GA tickets, persisted to DB)
        ─▶ Checkout (payment §39, idempotency) ─▶ CONFIRM hold → SOLD (conditional on hold_id & not expired)
        ─▶ Hold expiry: TTL sweeper releases expired holds back to inventory

Deep dives.

  • The waiting room is the key architectural element. It converts an unbounded spike into a controlled admission rate, protects every downstream system, and is where fairness is implemented (randomized positions for everyone who arrived before the start, bot checks, one position per verified account).
  • Inventory contention: row-level conditional updates on seats; partition by event section to spread lock contention. For general admission, treat inventory as counters in shards (e.g. 10 shards × 5,000 tickets), decremented atomically, so contention is split across shards.
  • Holds = TCC (§12.3): a reservation with expiry. The expiry must be enforced at confirm time (a conditional update), not only by the sweeper, or a late payment could confirm a seat that was already released and resold.
  • Bots: verified fan programs, rate limits per account/device/IP, CAPTCHAs at the queue entrance, purchase limits per account.

Strategic lens. Capacity: don't size the whole stack for 2M concurrent users. Size the waiting room for 2M (cheap, static, CDN-backed) and the transactional core for the admission rate (say, 5k checkouts/min). That is a cost decision as much as a technical one. Run a load test that replicates the on-sale (see scale guide §6.1–6.2) before every major event.

🏗️ Flow: the on-sale minute by minute.

 T-60m  Event page fully cached at CDN; waiting room pre-scaled; bot rules armed
 T-15m  Users arrive → waiting room (static page, CDN) → device fingerprint, CAPTCHA if suspicious
 T-0    Everyone in the "early" pool gets a RANDOM queue position (fairness); late arrivals appended
        Admission controller: admit_rate = f(checkout service p99, DB CPU, inventory left)
 T+1m   Admitted (signed token, 10-min TTL) → seat map (cached snapshot ≤ 1 s old)
        Select seats → HOLD (conditional update, all-or-nothing) → 10-min timer
        Pay (idempotent) → CONFIRM (conditional on hold_id & not expired) → ticket issued
 T+10m  Expired holds released → admission controller lets in more users matching freed inventory
 Sold out → waiting room switches to "sold out" page (stop admitting; protect everything)

🌍 How it's built in production.

  • Ticketmaster, Eras Tour presale (November 2022): Ticketmaster reported about 3.5 billion system requests, 4× its previous peak, largely from bots and fans without presale codes. The site degraded and the public sale was cancelled. Lessons: abusive traffic is part of capacity planning, and verified-fan pre-registration is a business-level admission control.
  • Queue-it and similar managed waiting rooms are widely used for drops and on-sales. A good buy answer: the waiting room is commodity; the inventory core is yours.
  • Shopify flash sales: Shopify has described using scriptable load balancers (OpenResty/Lua) to queue and throttle checkout traffic for a single shop, so one merchant's drop doesn't flood the pod.
  • Alibaba Singles' Day: about 544k orders/s at peak (2019), achieved through months of capacity rehearsal (full-link stress tests in production) and inventory sharding.

⚠️ Corner cases.

  • Payment succeeds after the hold expired and the seat was resold. The confirm must be conditional. If it fails, auto-refund immediately and apologize. Never oversell.
  • Orphan seats: users leave single empty seats between groups, which hurts sell-through. Enforce adjacency rules at selection time.
  • Queue token sharing and reselling: bind admission tokens to the account plus the device, and make them single-use.
  • Hold release bursts: thousands of holds expire at the same second (all admitted together), creating a sudden inventory wave. Jitter the hold TTLs.
  • Partial hold failure: 3 of 4 requested seats are available. Make holds all-or-nothing in one transaction, and suggest alternatives.
  • Price changes mid-flow (dynamic pricing): lock the price on the hold, not at page view.

Generated from real-life-system-design-guide.md · 8 Oct 2026 16:36