~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
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 availabilityBooking Flow
POST /showtimes/{id}/hold body: { seat_ids: [...] } → temporarily locks seats, returns a hold_token with TTL (say 7 min)POST /bookings body: { hold_token, payment_info } → confirms booking, charges paymentDELETE /bookings/{id} → cancellationAdmin
POST /venues/{id}/showtimes → create showtimePUT /showtimes/{id}/pricing → update pricing tiersUser — 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
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"):
Redis usage:
Search:
Payments:
Availability & fault tolerance:
Observability: