BookMyShow
Distributed SystemsPaymentsBooking SystemReal-time
0) Problem Restatement
Design a system like BookMyShow where users can browse movies, select seats in a theater, and complete booking with payments. The core challenge is preventing overselling of seats while keeping the system scalable.
1) Requirements
1.1 Functional
- Browse movies, shows, theaters.
- See seat layout with availability.
- Select and hold seats.
- Complete booking with payment.
- Cancel/refund tickets.
1.2 Non-Functional
- Consistency on seat booking (no double-booking).
- Scalability (peak traffic during blockbuster releases).
- Low latency for seat availability (< 1s refresh).
- High availability, fault tolerance.
2) High-Level Architecture
2.1 Overview
- Client (App/Web) → API Gateway → Services (Show, Inventory, Payment) → Cache/DB → Notifications.
- Key challenges: seat locking (atomic, low-latency), payment integration (async), and scaling under spikes.
2.2 Flow Diagram
Architectural Diagram Locked
3) Components (what & why)
Client (App/Web)
- UI for browsing shows, seat map, selecting seats, and paying.
- Requests seat hold and completes payment.
- Shows confirmation and stores tickets.
API Gateway
- Authentication, authorization, rate limiting, request validation.
- Routes to Show Service, Inventory Service, and Payment Service.
- Provides edge-level protections and metrics.
Show Service
- Movie metadata: movies, theaters, screens, show times.
- Serves seat layout templates and static show info.
Seat Inventory Service (Core)
- Maintains seat states: AVAILABLE, HELD, BOOKED.
- Core responsibilities:
- Atomically allocate/hold seats.
- Maintain holds with TTL.
- Confirm seats after successful payment.
- Release seats on timeout/payment failure.
- Should be horizontally scalable and partitioned by
show_id.
Cache / DB
- Redis (hot) for seat availability lookups and fast holds (with TTL).
- Primary DB (SQL/NoSQL) for persistent bookings and audit trail.
- Use CQRS: reads from cache, writes through Inventory Service to DB.
Locking Mechanism
- Use atomic Redis operations (SETNX, Lua scripts) or DB row-level transactions for final persist.
- TTL-based holds to auto-release uncompleted holds.
Payment Service
- Integrates with external gateways (Stripe/PayU/Paytm).
- Handles async callbacks, retries, idempotency tokens, and refunds.
- Marks booking CONFIRMED on success.
Notification Service
- Sends email/SMS/push confirmations and tickets.
- Asynchronous worker processing.
4) Data Model (minimal)
Movie
sqlMovie(movie_id, title, language, duration, genre)
Show
sqlShow(show_id, movie_id, theater_id, screen_id, start_time, end_time)
Seat (per show)
sqlSeat(seat_id, show_id, row, col, status) -- status ∈ {AVAILABLE, HELD, BOOKED}
Booking
sqlBooking(booking_id, user_id, show_id, seats[], status, created_at, expiry_time) -- status ∈ {PENDING, CONFIRMED, CANCELLED}
5) Key Flows
5.1 Browse Flow
- Client requests shows and seat layout.
- Show Service returns metadata and seat template.
- Seat availability comes from Redis (fast read).
5.2 Seat Hold (Select Seats)
- User selects seats → client calls Inventory Service:
/holdSeats(show_id, seats[], user_id). - Inventory Service atomically sets seats to HELD in Redis with TTL (e.g., 5 minutes) using Lua script or SETNX patterns.
- Service returns a
hold_id(temp reservation) to the client.
5.3 Payment & Confirm
- Client initiates payment with Payment Service using idempotency token.
- Payment Service calls gateway; on success callback:
- Payment Service notifies Inventory Service to confirm
hold_id. - Inventory Service persists booking to DB, marks seats BOOKED, and removes TTL holds in Redis.
- Payment Service notifies Inventory Service to confirm
- On failure or timeout:
- Hold TTL expires; Redis auto-releases seats.
- If payment fails after confirmation, trigger rollback/refund flow.
5.4 Cancellation / Refund
- Cancel booking → mark CANCELLED in DB, release seats (update Redis), initiate refund if applicable.
6) Concurrency & Correctness (Deep Dive A: Seat Locking, ~8–10 mins)
Problem
Multiple users may attempt to hold the same seat concurrently. We must prevent double-booking while keeping latency low.
Approach
-
Redis Atomic Hold (preferred for latency)
- Maintain a Redis hash or bitmap per
show_id(e.g.,show:{show_id}:seats). - Use a Lua script that checks all requested seats are AVAILABLE and atomically sets them to HELD with metadata
{user_id, hold_id, expires_at}. - Script returns success/failure, thereby guaranteeing atomic multi-seat holds.
- Maintain a Redis hash or bitmap per
-
TTL for Holds
- Set a TTL for each held seat or the hold entry (e.g., 5 minutes).
- If the user does not complete payment, seats auto-release.
-
DB Confirmation (durable)
- After payment success, perform a DB transaction to persist booking and set seats to BOOKED.
- Use an idempotency token to ensure re-entrant safety for retries.
-
Fallback to DB Locking (if Redis fails)
- Use DB row-level locking or optimistic concurrency (version numbers) as a fallback to ensure correctness.
Edge Cases & Mitigations
- Partial success in multi-seat hold: Lua script ensures all-or-nothing holds.
- Redis crash before DB persist: Use a short window; on DB reconcile job, mark seats from HELD to AVAILABLE if not confirmed.
- Clock skew / TTL drift: Use server-side timestamps and conservatively short TTLs.
7) Deep Dive B: Payments & Idempotency (~8–10 mins)
Payment Flow Characteristics
- External gateways are async and may be slow or flaky.
- Must avoid double-charging and ensure eventual consistency.
Steps
-
Initiate Payment
- Client posts to Payment Service with
hold_idand an idempotency token. - Payment Service calls gateway.
- Client posts to Payment Service with
-
On Gateway Callback
- Gateway calls our callback URL with transaction status.
- Payment Service verifies callback signature, matches idempotency token, and notifies Inventory Service.
-
Confirm Booking
- Inventory Service verifies the hold is still valid and atomically performs DB write to mark seats BOOKED.
- Persist payment record and link to booking.
-
Failure Path
- If payment fails or times out, do not confirm booking. Allow TTL to release seats.
- If payment is captured but DB update fails, Payment Service retries the finalization idempotently.
Idempotency & Safety
- Use an idempotency key for the entire payment+confirm workflow.
- Payment Service stores state machine per key: INITIATED → PROCESSING → SUCCESS → NOTIFIED.
- Re-entrant callbacks detect already-processed keys and no-op.
8) Scaling & Performance (~5 mins)
Partitioning
- Partition services by
show_id(each show independent) so seat state is sharded and hotspots are distributed.
Caching
- Use Redis for fast read path (availability) and for holds.
- Cold data (past shows/bookings) persisted in DB, moved to cold storage.
Resilience to traffic spikes
- Autoscale Inventory Service, use request queuing at API Gateway for surge control.
- For blockbuster releases, implement pre-book queuing and adaptive rate limits.
Monitoring & SLOs
- Metrics: holds/sec, confirms/sec, failed payments, Redis ops, DB latency.
- SLOs: <1s seat availability read, >99.9% booking durability, near-zero double-book.
9) Failure Modes & Recovery
Redis Failure
- If Redis unavailable: degrade to DB-backed locking (slower but consistent), or apply circuit-breaker to reject holds temporarily.
Payment Gateway Delays
- Accept asynchronous confirmation; keep holds short; offer “pending” UI feedback.
Partial Writes
- Reconciliation job: scan HELD entries older than TTL and reconcile with DB.
- Audit logs for every hold/confirm operation for forensic and retry.
10) Trade-offs & Alternatives
Redis vs DB locking
- Redis provides very low latency and atomic multi-key Lua scripts — ideal for seats. DB locking is more durable but slower.
Synchronous vs Asynchronous Booking Persist
- Persisting synchronously on payment confirmation ensures strong durability. Asynchronous persist risks temporary inconsistencies but scales writes.
Seat Hold TTL length
- Short TTL reduces lock contention but risks user UX friction (time to complete payment). Choose ~3–5 minutes, tuned by analytics.
11) Security & Data Integrity
- Use TLS for all endpoints; secure payment callbacks with signatures.
- Validate user identity before allowing holds.
- Audit logs of holds and confirmations; immutable payment records.
12) Interview Time Allocation (45 min)
- 5 min: Requirements & scope
- 10 min: HLD & architecture diagram
- 5 min: Data model & flows
- 10 min: Scaling & failure handling
- 15 min: Deep dives (Seat locking + Payments)
13) Summary
- Core of BookMyShow is safe seat allocation under high concurrency and integrating payment semantics reliably.
- Use Redis for low-latency holds (atomic Lua scripts + TTL), DB for durable booking records, and Payment Service with idempotency for safe external interactions.
- Partition by
show_id, autoscale services, and prepare reconciliation jobs for failure recovery.
