Security Notes
System Design

Design Ticketmaster (Ticket Booking)

5 min read 7 sections

DifficultyMedium | HelloInterview: problem breakdown


Problem Statement

Design a ticket booking system where users can search for events, view seat maps, select seats, and purchase tickets. The key challenge: prevent double-booking under high concurrent demand for popular events.

๐Ÿ“–

Real-world: The Taylor Swift "Eras Tour" presale (November 2022) is the canonical Ticketmaster horror story โ€” the system took 3.5 billion requests (4ร— its prior peak), much of it from bots and scalpers, the queue collapsed, and the public sale was cancelled, triggering a U.S. Senate antitrust hearing. The lesson it teaches is the one this design is really about: the hard problem isn't the database lock that prevents double-booking โ€” it's surviving a thundering herd where most traffic is hostile. That makes it a security design as much as a scaling one: the real defenses are a virtual waiting room / queue to shed load, aggressive bot detection (scalper bots vs humans), and rate limiting โ€” the seat-reservation TTL lock is necessary but it's the easy 20%. Interviewers reward candidates who bring up the bot/abuse layer unprompted.


Requirements

Functional

  • Browse and search events by location, date, category
  • View seat map with real-time availability
  • Select specific seats and hold them for a checkout window
  • Complete purchase with payment
  • View booked tickets

Non-Functional

  • 10M events, 1B tickets
  • Flash sales: 500k concurrent users trying to book a popular event in seconds
  • Seat hold must be exclusive โ€” a seat cannot be booked by two users simultaneously
  • Seat hold timeout: 10 minutes (if user abandons checkout, seat is released)
  • Read-heavy under normal conditions; write-spike on popular event sales

Core Design

The Central Challenge: Inventory Lock

Seat booking is a critical section: select a seat, mark it as held, charge the user โ€” all atomically without double-booking.

Approach 1: Database Optimistic Locking

sql
-- Read current status
SELECT status FROM seats WHERE seat_id = ? AND status = 'available'
-- Update only if still available (OCC โ€” optimistic concurrency control)
UPDATE seats SET status='held', user_id=?, held_until=NOW()+600
WHERE seat_id = ? AND status = 'available'
-- Check affected rows: if 0 โ†’ someone else grabbed it first
  • Good for low-contention; degrades under high contention (retry storms)

Approach 2: Redis Distributed Lock + DB Write (recommended for flash sales)

python
lock_key = f"seat_lock:{seat_id}"
lock_value = str(uuid4())   # unique to this request
# Acquire lock: SET NX PX 10000 (10 second lock)
acquired = redis.set(lock_key, lock_value, nx=True, px=10000)
if not acquired:
    return "seat not available"
# Inside lock: write to DB
db.execute("UPDATE seats SET status='held', user_id=? WHERE seat_id=? AND status='available'")
# Release lock: Lua script to ensure only owner releases

Approach 3: Queue-based serialization (best for very hot seats)

  • All seat requests for a popular event go into a queue (per event)
  • Single consumer processes requests in order โ€” no concurrency issue
  • Users see a "queue position" virtual waiting room
  • Used by Ticketmaster, EventBrite for sold-out events

Architecture

     Users (browsers/apps)
          โ†“
    API Gateway (rate limit per IP, auth)
          โ†“
    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚  Search Service             โ”‚  โ†’ Elasticsearch (event search)
    โ”‚  Event Service              โ”‚  โ†’ PostgreSQL (event/venue data)
    โ”‚  Seat Service               โ”‚  โ†’ PostgreSQL (seat inventory)
    โ”‚  Booking Service            โ”‚  โ†’ PostgreSQL + Redis locks
    โ”‚  Payment Service            โ”‚  โ†’ Stripe / payment processor
    โ”‚  Notification Service       โ”‚  โ†’ Email, SMS, push
    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
          โ†“                 โ†“
    PostgreSQL          Redis Cluster
    (source of truth)   (locks + seat status cache)

Database Design

sql
events(event_id, name, venue_id, date, ...)
venues(venue_id, name, capacity, seating_map_json, ...)
seats(
  seat_id      UUID PRIMARY KEY,
  event_id     UUID REFERENCES events,
  section      TEXT,
  row          TEXT,
  number       INT,
  status       ENUM('available','held','sold'),
  held_by      UUID,       -- user_id
  held_until   TIMESTAMP,  -- null if available or sold
  price        DECIMAL
)
bookings(booking_id, user_id, event_id, seat_ids[], payment_id, status, created_at)

Seat Hold + Release Flow

1. User selects seats (optimistic: UI shows available)
2. POST /hold โ†’ Booking Service
   - Acquire Redis lock per seat_id (10s TTL)
   - UPDATE seats SET status='held', held_until=NOW()+600
   - Return hold_id + expiry timer
3. User completes payment (within 10 min)
   - Payment Service charges card
   - On success: UPDATE seats SET status='sold'
   - Release Redis lock
4. If user abandons:
   - Background job: find seats WHERE status='held' AND held_until < NOW()
   - UPDATE seats SET status='available', held_by=NULL
   - OR: Redis lock TTL expires โ†’ next requester can grab it

Read Scalability

Seat availability reads: cache in Redis (seat:{seat_id}:status) with 1-second TTL. Users see near-real-time availability without hitting DB on every request. On status change, invalidate cache.

Seat map display: pre-render SVG/JSON map per event; cache in CDN; update only seats that change (delta updates via WebSocket).


Key Design Decisions

DecisionChoiceReason
Concurrency controlRedis lock + DB writeFast lock acquisition; DB is source of truth
Flash sale queueVirtual waiting roomSmooths demand spike; fairness for users
Seat status cacheRedis (1s TTL)Reduce DB reads under high concurrent read
Payment integrationExternal processor (Stripe)Don't build payments; handle webhook callbacks
Checkout timeout10 minutes with background GCStandard industry practice

Security Considerations

ThreatMitigation
Bot scalpingCAPTCHA at seat selection; device fingerprinting; behavioral analysis (click speed)
Race condition / double bookingRedis distributed lock; DB-level uniqueness constraint on (event_id, seat_id, status=sold)
Payment fraudPCI-DSS compliant payment processor; 3DS2 authentication for high-value orders
Account takeover โ†’ ticket theft2FA on accounts; email confirmation for bookings; re-auth for high-value purchases
Seat hoardingLimit seats per transaction per account; hold timeout enforcement
API enumeration of seat availabilityRate limit; return aggregate counts, not individual seat IDs unless in booking flow

Interview Tips

The concurrency problem is the whole interview

spend the most time on it.

Virtual waiting room

is a real pattern used in production (Ticketmaster uses it) โ€” mentioning it shows industry knowledge.

Distinguish seat hold from seat sold

two-phase: hold (reserve for checkout) โ†’ sold (payment confirmed). If payment fails, seat returns to available.

Webhook handling for payment

Stripe/payment processor sends payment.succeeded webhook โ€” you need to handle this idempotently (replay safe).