Requirements
Functional Requirements:
- Users search for movies/events by location, date, genre
- Browse theaters, showtimes, and seat maps
- Select seats (with real-time availability)
- Hold seats temporarily during checkout
- Process payments and issue e-tickets
- Cancellation and refund flow
- Admin panel for theater partners to manage listings, inventory, pricing
Non-Functional Requirements:
- High concurrency: thousands of users competing for the same seats (e.g., a blockbuster release)
- Low latency on seat availability checks
- Strong consistency on booking — no double-selling a seat
- High availability (targeting 99.99%)
- Eventual consistency is acceptable for search/catalog but not for seat reservation
~50M monthly active users, ~5M daily
Peak: 50k+ concurrent seat selection requests for a hot show
~500k bookings/day, ~200M tickets/year
Average payload per booking: ~2KB → modest storage, but high write throughput during spikes
API Design
Search & Browse
GET /movies?city=&date=&genre=→ movie listingsGET /movies/{id}/showtimes?date=&venue_id=→ available showtimesGET /showtimes/{id}/seats→ seat map with real-time availability
Booking Flow
POST /showtimes/{id}/holdbody:{ seat_ids: [...] }→ temporarily locks seats, returns a hold_token with TTL (say 7 min)POST /bookingsbody:{ hold_token, payment_info }→ confirms booking, charges paymentDELETE /bookings/{id}→ cancellation
Admin
POST /venues/{id}/showtimes→ create showtimePUT /showtimes/{id}/pricing→ update pricing tiers
High-Level Design
User — id, name, email, phone, payment_methods
Movie/Event — id, title, genre, language, duration, rating, poster_url
Venue (Theater) — id, name, location (lat/lng), city, screens
Screen — id, venue_id, seat_map (rows × cols with categories like Gold/Silver)
Showtime — id, movie_id, screen_id, start_time, pricing_tiers
Seat Inventory — showtime_id + seat_id → status (AVAILABLE / HELD / BOOKED)
Booking — id, user_id, showtime_id, seats[], total_price, status, payment_id, created_at
Payment — id, booking_id, method, amount, status, gateway_txn_id
Detailed Component Design
Step 1: User views seat map → GET /showtimes/{id}/seats → Booking Service reads from Redis (seat status cache). Fast, but slightly stale is okay here — it's just a visual hint.
Step 2: User selects seats and hits "Book" → POST /showtimes/{id}/hold { seat_ids: [A1, A2] } → Booking Service attempts an atomic operation:
sql
-- Postgres with row-level locking
UPDATE seat_inventory
SET status = 'HELD', hold_token = :token, hold_expires_at = NOW() + interval '7 min'
WHERE showtime_id = :sid AND seat_id IN ('A1','A2') AND status = 'AVAILABLE';
-- Check affected rows == requested count, else rollback
This is the serialization point. Only one user wins. The loser gets a "seats unavailable" response immediately. Redis is updated after the write.
Step 3: User completes payment within hold window → POST /bookings { hold_token, payment } → Payment Service charges the card → on success, Booking Service transitions seats from HELD → BOOKED. → Event published to Kafka → Notification Service sends confirmation email/SMS + Ticket Gen Service creates QR code PDF.
Step 4: Hold expires (user abandoned) → A background Hold Expiry Worker runs every 30s scanning for expired holds and flips them back to AVAILABLE. Alternatively, use Redis key TTL as a trigger.
Handling hot showtimes (the "Avengers problem"):
- A single showtime's seats fit in a few KB, so a single Postgres row-level lock is viable up to a point.
- For extreme spikes, shard seat inventory by showtime_id — each showtime's seats live on one shard, so the lock contention is isolated.
- Add a virtual waiting room (queue-based admission) for mega-popular releases: users get a random position, admitted in batches. This converts a stampede into a controlled flow.
Redis usage:
- Seat availability cache per showtime (refreshed on every write). Reads hit Redis, writes go to Postgres then invalidate Redis.
- Hold token TTLs as a backup expiration mechanism.
Search:
- Elasticsearch indexes movies, venues, showtimes. Updated asynchronously via CDC (Change Data Capture) from Postgres.
- Geo queries for "theaters near me" using ES geo_point.
Payments:
- Idempotency keys on every payment request to handle retries safely.
- Two-phase: authorize during hold, capture on confirmation. If hold expires, release the auth.
Availability & fault tolerance:
- Postgres primary-replica with synchronous replication for the booking path.
- If Redis goes down, fall back to Postgres reads (slower but correct).
- Payment failures → seats stay HELD until TTL expires, user can retry.
- Circuit breaker on payment gateway calls.
Observability:
- Track key metrics: seat hold success rate, payment conversion rate, hold expiry rate, p99 latency on the booking path.
- Alert on double-book anomalies (should be zero, ever).