Case study: Ticket booking (Ticketmaster)
Design a high-contention ticket booking system: virtual waiting rooms, two-phase seat holds with bounded TTLs, atomic conditional updates (`decrementIfPositive`), and fencing tokens.
35 min9 steps
By the end you can
- Estimate stadium seat inventory memory, flash-crowd read vs write ratios, and waiting-room admission pacing
- Prevent double-selling seats under contention using atomic conditional updates (`decrementIfPositive` / CAS) instead of read-then-write
- Design a two-phase seat reservation protocol with bounded-TTL holds, active+lazy expiry, and fencing tokens
- Predict system behaviour during hot front-row drops, seat-map refresh storms, and database leader failovers
Pin down what you are building
2M fans / 50k seats
- Browse maps; fair waiting room; five-minute holds; payment confirmation
Zero oversell
- Map <100ms; 2-5s staleness allowed; survive 100x spikes
Work out the numbers
Seat inventory
6.4 MB
50k / 128 B
Seat-map reads/s
100k
500k browsers / 5s
Hold attempts/s
~500
50k / 3 / 300s; provision 500-2,000/s
Design the API
GET /v1/events/{eventId}/seats; POST /v1/seats/{seatId}/hold
- Cached map; hold returns token/expiry or 409
POST /v1/holds/{holdId}/confirm; POST /v1/seats/{seatId}/release
- Require current holdToken; confirm payment or release
Model the seat state machine and atomic transitions
AVAILABLE / HELD / SOLD
- Conditional UPDATE claims one seat; holdToken increases; expiry stored
Concurrent claims
- One updated row / 200; other contenders / 409; never read-then-write
Draw the high-level architecture
Order the lifecycle of a successful two-phase seat booking.
- 1API invalidates the seat-map cache and schedules a 5-minute expiry check in the hold-expiry queue
- 2Fan enters payment details; payment service charges the card outside the seat database lock
- 3Fan selects Seat 42; API runs an atomic conditional update (AVAILABLE → HELD) with a 5-minute TTL and monotonic holdToken
- 4Virtual waiting room admits the fan to the event page and serves the cached seat map
- 5API transitions Seat 42 from HELD → SOLD only if holdToken still matches the seat row
Deep dive: Fencing tokens, Lazy + Active Expiry, and Waiting Rooms
Stale payment fencing
- 101 expires; 102 acquires; reject late confirmation 101 and refund
Expiry + admission
- Lazy conditional reclaim plus reaper; signed waiting-room tokens shape checkout
Evaluate alternatives
ticket-booking-unsafe
- Read-then-write races oversell under hot-event
Single actor/event + WAL
- 6.4 MB state; replicated log; sequential holds avoid locks
Best available in section
- Partitioned counters reduce exact-seat contention
Run the failure scenarios
Inject
hot-event: 25% one seat
Observe
One conditional claim wins; others 409
Result
Zero oversell; rejection lowers measured goodput
Inject
map-refresh-storm: 2x
Observe
Cache coalesces misses
Result
Fixture goodput >98%; DB shielded
Inject
bookingdb-failover: ~500ms
Observe
Cached maps survive; holds pause
Result
Semi-sync + fencing prevent lost holds/double sale
Wrap up and self-assess
Protect
- Waiting room; atomic holds; holdToken fencing
Expire
- Bounded TTL; lazy reclaim + active reaper; coalesced map cache
Answer this step to continue.