Design a High-Concurrency Ticket Booking Platform Like Ticketmaster
An event ticketing platform (e.g., Ticketmaster, Live Nation, StubHub) manages high-demand ticket sales for concerts, sporting events, and theater performances. The critical architectural challenge is surviving catastrophic traffic spikes (e.g., millions of fans competing for 50,000 stadium seats at 10:00 AM) while guaranteeing zero double booking and low-latency interactive seat selection.
1. Understanding the Problem
Functional Requirements
- Browse & Search Events: Users can search for events by artist, venue, city, and date.
- Interactive Seat Map: View real-time seating charts with live seat availability status (Available, Reserved, Booked).
- Temporary Seat Hold: Users select seats and hold them for 10 minutes to complete checkout.
- Checkout & Payment: Process payment, generate cryptographically signed digital tickets (QR/Barcodes), and mark seats as permanently booked.
- Virtual Waiting Room: During massive viral sales, queue surplus users fairly to prevent backend overload.
Non-Functional Requirements
- Strict Consistency (Zero Double Booking): A seat must never be sold to more than one person under any circumstances.
- Extreme Peak Surge Tolerance: System must survive traffic surges up to 100xβ500x normal baseline load.
- Seat Hold Expiration Reliability: Expired holds must immediately return to the available inventory pool.
- High Availability for Browsing: Event browsing and general info must remain accessible even if ticket purchase queues are throttled.
Capacity Estimations & Peak Surge Sizing
- Total Registered Users: 50 Million users.
- Major Stadium Concert: 50,000 seats.
- Viral Surge Demand: 5 Million fans hitting the sale at 10:00:00 AM.
- Traffic Spike: 5,000,000 users / 10 seconds 500,000 QPS at peak.
- Database Sizing (5 Years):
- 100,000 events/year 5 years = 500,000 events.
- Average seats per event: 10,000 seats 5 Billion total seat records.
- 5 Billion 200 bytes 1 TB storage (easily manageable in partitioned relational storage).
- Core Problem: This is not a big data volume problem; it is an extreme write-contention and concurrency problem.
2. The Set Up
Defining the Core Entities
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β EVENT β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β event_id β UUID β PRIMARY KEY β
β venue_id β UUID β INDEX, FK β
β title β VARCHAR(255) β NOT NULL β
β start_time β TIMESTAMP β NOT NULL β
β sale_start_time β TIMESTAMP β NOT NULL β
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SEAT β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β seat_id β UUID β PRIMARY KEY β
β event_id β UUID β COMPOSITE INDEX, FK β
β section β VARCHAR(32) β e.g. "Section 102" β
β row_number β VARCHAR(16) β e.g. "Row A" β
β seat_number β INT β e.g. 14 β
β price_cents β INT β NOT NULL β
β status β VARCHAR(16) β AVAILABLE / HELD / ..β
β held_until β TIMESTAMP β NULLABLE β
β user_id β UUID β NULLABLE (Holder) β
β version β BIGINT β Optimistic Version β
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BOOKING β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β booking_id β UUID β PRIMARY KEY β
β event_id β UUID β INDEX, FK β
β user_id β UUID β INDEX, FK β
β total_amount β INT β In cents β
β status β VARCHAR(32) β PENDING / PAID / ... β
β idempotency_key β VARCHAR(64) β UNIQUE β
β created_at β TIMESTAMP β NOT NULL β
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
The API Design
1. Hold Seats (Temporary 10-Minute Lock)β
POST /api/v1/events/{event_id}/seats/hold
Content-Type: application/json
Authorization: Bearer <queue_jwt_token>
Idempotency-Key: hold_9a8f2-1082
{
"seat_ids": [
"seat_sec102_rowA_14",
"seat_sec102_rowA_15"
],
"hold_duration_seconds": 600
}
Response (200 OK):
{
"reservation_id": "res_8819203",
"held_until": "2026-09-22T23:10:00Z",
"seats": [
{"seat_id": "seat_sec102_rowA_14", "price_cents": 12000},
{"seat_id": "seat_sec102_rowA_15", "price_cents": 12000}
],
"total_price_cents": 24000
}
2. Confirm Booking & Purchaseβ
POST /api/v1/bookings/checkout
Content-Type: application/json
{
"reservation_id": "res_8819203",
"payment_method_id": "pm_card_visa_4421"
}
3. High-Level Design
Ticketmaster High-Concurrency Seat Booking PipelineInteractive 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
Interactive Component Inspector
Click any architecture node on the SVG canvas to view under-the-hood engine mechanics, failure gotchas, and runtime tags.
Walkthrough of the Traffic Influx Architecture
1. Traffic Absorption & Virtual Waiting Roomβ
- 30 minutes before 10:00 AM, users entering the event page are routed to an edge Virtual Waiting Room (powered by Cloudflare Workers or a dedicated Go queuing cluster).
- Users are assigned a signed cryptographic Queue Token (JWT) containing their randomized arrival position.
- The Waiting Room throttles the downstream admission rate to precisely match the backend capacity (e.g. 500 checkout sessions admitted per second).
- When a user reaches the front of the queue, their JWT token is upgraded with an
admit: trueclaim, granting access to the seat selection service.
2. Real-Time Seat Selection & Reservationβ
- The user views the stadium seat map. The frontend polls or streams seat state via WebSockets backed by a Redis Bitmap / Set representing seat availability for that event.
- User selects Seat 14 and 15 and clicks "Reserve".
- An atomic Redis Lua Script executes:
- Validates that both seats are currently
AVAILABLE. - Atomically transitions them to
HELDand stores a reservation record with a 600-second TTL.
- Validates that both seats are currently
- Returns confirmation to the user; countdown timer starts in the browser.
3. Checkout, Payment & Ticket Generationβ
- User enters payment info. The Order Service calls the Payment Gateway (Stripe/Adyen) with an
Idempotency-Key. - Upon payment success, a relational database transaction commits the booking:
- Updates
SEATstatus toBOOKEDin PostgreSQL. - Inserts
BOOKINGrecord and creates digital ticket barcodes. - Emits an event to Kafka for PDF email generation.
- Updates
- The Redis seat status is transitioned from
HELDtoBOOKED.
4. Potential Deep Dives & Bottlenecks
Deep Dive 1: Concurrency Control (Pessimistic vs Optimistic vs Redis Lua)
How do we prevent double-booking when 50,000 users click the same row of seats simultaneously?
| Approach | Implementation | Under-the-Hood Behavior | Bottleneck / Production Gotcha |
|---|---|---|---|
| Pessimistic Locking | SELECT ... FOR UPDATE | Locks database row in InnoDB buffer pool. Subsequent transactions queue up. | Catastrophic database lock wait timeouts (Lock wait timeout exceeded). Connection pool exhausted in seconds. |
| Optimistic Locking | UPDATE seat SET status='HELD', version=v+1 WHERE seat_id=? AND version=v | Relies on row version check. First writer wins; remaining writers get 0 rows updated. | Prevents corruption, but high contention causes 99.9% of threads to fail and retry, thrashing database CPU. |
| Redis Distributed Lock (Redlock) | Acquire lock per seat via SET seat:id lock_token NX PX 600000 | In-memory key reservation. Fast, but multi-seat transactions require acquiring multiple distributed locks. | Partial lock deadlocks (User A locks Seat 14 and wants 15; User B locks Seat 15 and wants 14). |
| Atomic Redis Lua Script (Recommended) | Execute multi-seat verification and reservation in a single Lua execution | Single-threaded Redis thread runs all seat updates atomically without network pauses. | Zero race conditions, sub-millisecond execution. Survives 200K+ QPS per Redis shard. |
Production Redis Lua Script for Atomic Multi-Seat Reservation:β
-- KEYS: List of seat keys (e.g. event:101:seat:14, event:101:seat:15)
-- ARGV: [1] user_id, [2] reservation_id, [3] ttl_seconds (600)
-- Step 1: Verify ALL requested seats are available
for i, key in ipairs(KEYS) do
local status = redis.call('HGET', key, 'status')
if status ~= false and status ~= 'AVAILABLE' then
return {0, key} -- Fail: At least one seat is unavailable
end
end
-- Step 2: Atomically reserve ALL requested seats
for i, key in ipairs(KEYS) do
redis.call('HSET', key, 'status', 'HELD', 'user_id', ARGV[1], 'res_id', ARGV[2])
redis.call('EXPIRE', key, tonumber(ARGV[3]))
end
return {1, 'SUCCESS'}
Deep Dive 2: Handling Seat Hold Expiration Reliably
What happens when a fan reserves seats but closes their laptop without paying?
- Problem: If expired seats are not returned immediately, venue capacity is artificially suppressed, leaving empty seats at showtime.
- Solution Architecture:
- Passive Expiration (Lazy Check): Whenever a user inspects a seat, if
status == 'HELD'andheld_until < NOW(), immediately treat it asAVAILABLE. - Active Expiration (Redis Keyspace Notifications + Delayed Queue):
- Upon reservation, push a delayed message
{"res_id": "res_8819203", "event_id": 101}into a Redis Sorted Set (ZSET) wherescore = expiration_timestamp. - A background sweeper worker continuously polls
ZRANGEBYSCORE delay_queue 0 <current_timestamp>. - If the reservation has not been completed, the sweeper resets the seats in Redis and PostgreSQL back to
AVAILABLE.
- Upon reservation, push a delayed message
- Passive Expiration (Lazy Check): Whenever a user inspects a seat, if
Deep Dive 3: Sharding & Database Partitioning Strategy
How do we shard the database when one event has extreme traffic while others have minimal activity?
- Partition Key: Partitioning strictly by
event_idcreates a massive hot partition / hotspot when tickets for a superstar concert go on sale, overwhelming the single shard hosting thatevent_id. - Composite Sharding Key:
(event_id, section_id)- A stadium is composed of 50+ independent sections (Section 101, Section 102, Floor, Balcony).
- Sharding by
(event_id, section_id)distributes the 50,000 seats across multiple database shards and Redis nodes. - Fans browsing Floor tickets hit Shard A, while fans browsing Balcony tickets hit Shard B.
5. Architectural Trade-Off Matrix
| Design Area | Option A | Option B | Selected Choice & Rationale |
|---|---|---|---|
| Spike Ingress Management | Direct API Gateway Influx | Edge Virtual Waiting Room (Queue-It) | Virtual Waiting Room: Dropping 5 Million concurrent requests directly onto backend services causes immediate cascading failure. The waiting room smooths ingress to match DB capacity. |
| Seat Hold Storage | In-Memory Redis Lua | Relational Database Row Locks | Redis Lua: Relational row locks under 100K QPS freeze connection pools. Redis handles atomic checks in sub-millisecond memory. |
| Consistency vs Availability | High Availability (AP) | Strict Consistency (CP) | Strict Consistency (CP): Double booking leads to severe brand damage, legal liability, and customer outrage. Rejecting a reservation is always preferred over selling the same seat twice. |
6. What is Expected at Each Level?
Mid-Level (L4 / IC4)
- Identifies the need for temporary seat holding with a countdown timer.
- Designs the data schema linking Events, Venues, Seats, and Bookings.
- Understands the difference between optimistic and pessimistic locking.
- Proposes basic database updates to mark seats as reserved.
Senior (L5 / IC5)
- Implements atomic multi-seat reservation using Redis Lua scripts to eliminate deadlocks.
- Designs the Virtual Waiting Room architecture with cryptographic JWT tokens to absorb 500x traffic surges.
- Handles automated expiration and rollback using delayed message queues or Redis sorted sets.
- Explains composite sharding on
(event_id, section_id)to prevent hot partition meltdown.
Staff+ (L6 / Principal)
- Designs an end-to-end anti-bot and scalper mitigation defense (Cloudflare Turnstile, browser fingerprinting, behavioral analysis).
- Details 2-phase payment reconciliation: Handling situations where payment succeeds at Stripe but the client connection drops before seat confirmation.
- Evaluates multi-region failover trade-offs: Explains why cross-region active-active seat reservation is prone to split-brain double bookings and justifies active-passive single-leader per event.
