The Core Challenge: Concurrency & Hot Spots
Designing a high-concurrency ticket reservation platform like Ticketmaster or BookMyShow revolves around a single critical invariant: a seat must never be double-booked, even when hundreds of thousands of users attempt to purchase tickets for the same concert within seconds. At the same time, holding a seat during checkout requires a temporary reservation with an automatic timeout if payment is not completed.
Reservation State Lifecycle
Architectural Strategy
- Distributed Virtual Waiting Room: When traffic exceeds venue capacity, users are assigned a position in a distributed queue (using Cloudflare Waiting Room or Redis sorted sets) before accessing the seat selection map.
- Temporary Seat Hold: Give each hold a unique token, owner, and authoritative expiry. Redis may accelerate reads or expiry notifications, but a separately expiring Redis key must not be the only proof that SQL is allowed to book the seat.
- Database Isolation & ACID Guarantees: The authoritative conditional transition verifies the same hold token, owner, state, and unexpired deadline in an ACID-compliant database. Row-level locking or optimistic concurrency can serialize competing transitions:
UPDATE seats SET status = 'BOOKED', user_id = :userId, version = version + 1 WHERE seat_id = :seatId AND status = 'HELD' AND hold_token = :holdToken AND hold_expires_at > CURRENT_TIMESTAMP AND version = :currentVersion; - Read Scaling with In-Memory Caches: High-volume reads of the seating chart are served from Redis bitsets or memory-cached floor plans, while write traffic routes directly through the transactional reservation service.
Payment Reconciliation & Webhook Callbacks
Payment processing is asynchronous, so signed callbacks can be duplicated, delayed, or reordered. If buyer A's hold expires, buyer B may acquire the seat before A's success callback arrives. The callback must idempotently attempt the token-bound booking transition; if it loses, record the payment as unmatched and run an explicit void/refund workflow with reconciliation rather than issuing two tickets or assuming an immediate refund always succeeds.
Worked example: a 50,000-seat stadium on-sale
- 2 million fans arrive in the first 5 minutes. Without a waiting room, seat-map requests peak at hundreds of thousands per second, on data that changes every second.
- The waiting room admits 3,000 users per minute. If an average buyer takes 6 minutes, about 18,000 people are in checkout at once, a load the reservation database can easily handle.
- Holds last 10 minutes. With 50,000 seats and roughly 1 in 4 holds abandoned, expired holds release seats continuously, so the seat map must refresh often (poll every few seconds, or push updates to open sessions).
- Contention is highest on the best seats. The conditional
UPDATE ... WHERE status = 'AVAILABLE'returns 0 rows for everyone except the winner, who gets the seat. Everyone else is told immediately to choose another seat.
Concurrency options
| Technique | How it works | Strength | Weakness |
|---|---|---|---|
| Conditional update / optimistic version | UPDATE ... WHERE status = 'AVAILABLE' AND version = v | No lock held while the user thinks; scales well | Losers must retry or pick again |
| Pessimistic row lock | SELECT ... FOR UPDATE inside a short transaction | Simple reasoning | Lock waits pile up on hot rows; never hold it across user think time |
| Distributed lock (Redis, ZooKeeper) | Lock per seat, then write to the DB | Offloads contention from the DB | Needs fencing tokens; the lock and the DB can disagree |
| Serialised per-event queue | One consumer per event partition processes seat requests in order | No races by construction | Throughput limited by one partition; adds latency |
General admission is a counter, not seats
For standing-room tickets, inventory is a number: UPDATE inventory SET remaining = remaining - :n WHERE event_id = :e AND remaining >= :n. A single counter row becomes a hotspot at thousands of updates per second. Split it into, say, 20 sub-counters of 2,500 tickets each, pick one at random, and fall back to the others when it runs out. This is the same idea as sharded counters.
Fairness and bots
- Randomise positions for everyone who arrives before the on-sale time, so refreshing early gives no advantage. Ticketmaster and Cloudflare Waiting Room both do this.
- Limit tickets per account and per payment card, and require verified accounts for high-demand events (Ticketmaster Verified Fan).
- Issue signed, expiring entry tokens from the waiting room, so bots cannot skip the queue by calling the checkout API directly.