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.