Requirements
Functional Requirements:
- Users can see available shows.
- Users can view a seating map to pick seats.
Non-Functional Requirements:
- Low latency
- Scalablility
- Reliability: no double-booking of same seat
- Availability: system is operational under high load and machine/data center failure
API Design
- get_shows(keyword): see available shows based on optional keyword
- get_seats(show_id): get available seats of a show
- reserve_seat(seat_id): reserve a seat
- payment(user_id, seat_id): call to 3rd party payment gateway
High-Level Design
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
End-to-end user flow:
- Screen 1: user enter web page, browse existing shows (support search)
- Select a show to view available seats
- Screen 2: Select a seat and confirm to proceed to payment page
- Screen 3: Select a payment platform and complete the payment in 3rd party website
- Screen 4: Receive a booking successful confirmation in the web and confirmation email with ticket details
Backend server design:
- The backend server exposes 4 above APIs to web/app client
- get_shows and get_seats are simple read APIs
- 3 different seat states: AVAILABLE, PENDING, RESERVED
- reserve_seat API: mark the seat as "PENDING", get_seats should not show "PENDING" seats
- A webhook in backend server to handle payment callback:
- Update seat state in storage / memory
- Update UI in web/app client
- Send confirmation email with ticket details
- If the seat is AVAILABLE (not PENDING anymore due to timeout), refund to user
- Have a logging/analytic service
Detailed Component Design
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
Ensure no double-booking:
- To prevent 2 users from selecting the same seat (Screen 2) and subsequently make payment for the same seat (Screen 3), we check for seat state in reserve_seat API
- If the seat is "PENDING", show error to user and prompt user to select another seat
- To prevent an user from holding a seat for too long, set expire time for pending by having a separate process on server to periodically clean up expired "PENDING" seat in database.
- To reduce database read in each reserve_seat call, we also write seatId -> userId mapping to Redis when user reserve_seats (besides writing to DB), this represents "PENDING" seat. Then in user flow, after user select seats, the reserve_seats API will first check the Redis cache if this seat is held by another user, if yes, raise error and ask the user to select another seat, else check & write to DB & add the entry to Redis cache and move user to payment API.
Design for scalability & availability & low latency under high load (e.g. mega-sale event):
- get_shows and get_seat are read-heavy => We can scale by slave nodes or cache the content in Redis. If the website has images / videos, we move the media content to CDN.
- Run multiple instances of the backend server to handle high load, have a load balancer (e.g. Nginx) to distribute requests to server
- Slave nodes & backend servers distributed in multiple data centers to ensure high availability during disaster
Rate limit (e.g. each user can booked multiple seats, but can hold only 1 seat pending):
- To prevent user from holding too many seats at the same time, we store the mapping from userId => pending_seat in a cache (Redis). We check this cache in reserve_seats API to ensure user is not currently holding any seat. Set this cache to expire (same time as pending seat threshold) and also need to ensure cache update upon payment.