List functional requirements for the system (Ask the chat bot for hints if stuck.)...
List non-functional requirements for the system...
Estimate the scale of the system you are going to design...
100 Million Daily active users
Read QPS = 100M/100k = 1000
During peak hours = 2 * QPS = 2000
Write will be 1% of users who buys ticket
write QPS = 10
During peak hours = 20
So it is a read heavy system
Define what APIs are expected from the system...
GET /v1/theatres(:locationId)
Retrieve list of theatres for this location
Response:
data: [
{
theatreId:
address:{
}
email:
phone:
}
]
GET /v1/movies(:theatreId)
We need to provide theatreId and it will return all the movies running in this theatre
Response:
data: [
{
movieId:
movieName:
timeslot:
}
]
GET /theaters/{theaterId}/movies/{movieId}/seats?datetime={dateTime}
This will give the information about seats for a specific theatre and for a specific movie running in that theatre
Response:
{
"theaterId": 123,
"movieId": 456,
"dateTime": "2024-09-25T18:30:00",
"seats": [
{
"position": "A1",
"occupied": true
},
{
"position": "A2",
"occupied": false
},
{
"position": "A3",
"occupied": true
},
{
"position": "B1",
"occupied": false
}
]
}
POST /v1/seats/hold
This request will to hold the seat for the specific movie
Request params
POST /tickets/hold
Content-Type: application/json
{
"theaterId": "123",
"movieId": "456",
"datetime": "2024-09-25T19:30:00",
"seatId": "A1"
}
It will return response based on the availability
200 OK:
{
ticketId:
Status: held
}
400 Bad Request:
If the theatreId, movieId is invalid
500 Internal server error
If not able to hold the seat please try again
GET /ticket/:ticketId/status
It will return the status of the ticket
POST /v1/payment
It will be available to make payment
Request Body:
{
ticketId:
info:{
creditcard no:
expiry date:
cvv no:
}
Response:
200 OK if the payment successful
Defining the system data model early on will clarify how data will flow among different components of the system. Also you could draw an ER diagram using the diagramming tool to enhance your design...
Theatre
Show
Seat
Movie
Bookings
Show_Seats
Database Choices
You should identify enough components that are needed to solve the actual problem from end to end. Also remember to draw a block diagram using the diagramming tool to augment your design. If you are unfamiliar with the tool, you can simply describe your design to the chat bot and ask it to generate a starter diagram for you to modify...
Client: The client customer used to book a ticket. It could be a web app or mobile app.
Load Balance: Balance the traffic from client evenly to different servers. The load could be balanced with path based, round robin or consistent caching approaches.
Api Gateway: Api Gateway will be responsible with authentication, rate limiting and others.
CDN: Store static files, for example movie preview images, movie tailor and theater images. When user request static data, the request will be routed to the CDN which physically close to the client.
Info Service: Info service will be responsible to read request from clients. For example, get theater info, get movie info and get seat info.
Ticket Service: Ticket service will be responsible to update the database to hold a ticket and query a ticket status.
Task Scheduler: The task scheduler will be responsible to schedule a time after user hold a ticket. If the user didn't pay the ticket in a certain time (5 min or 10 min), the task scheduler will be responsible to update the status of the ticket.
SQL Database: The database will be responsible to store the information of the theater, movie, seat and ticket.
Database Cache: Will cache the SQL Database to read data faster.
Notification Service: Notification service will be responsible to send ticket confirmation notification to the client through SMS, email or push notifications.
Explain how the request flows from end to end in your high level design. Also you could draw a sequence diagram using the diagramming tool to enhance your explanation...
Dig deeper into 2-3 components and explain in detail how they work. For example, how well does each component scale? Any relevant algorithm or data structure you like to use for a component? Also you could draw a diagram using the diagramming tool to enhance your design...
Let's talk about database and concurrency. These three operations must either succeed as a whole, or fail as a whole.
In other words, if anything fails within (1) - (3), the process must be rolled back as if nothing happened. For example:
a. User A selects seat 1.
b. User A books seat 1.
c. User A fails to pay because they forgot their PayPal password.
In this case, after a timeout (e.g. 10 - 15 min), seat 1 must be made available again, so that other users can book them.
We also have to do this with concurrency in mind. When multiple users try to book the same seats on a same show, the system must reject some of the requests, gracefully.
When select_seats() is called, we create rows in Show_Seats table to signify that the seats for this show are held for N minutes. We can express this by setting the rows' status to "held", with a timestamp.
We will use uniqueness constraints on Show_ID and Seat_ID on this table to provide consistency.
This is an example of SQL statement:
insert into Show_Seats table (Show_ID, Seat_ID, status, timestamp)
values (100, 10, held, 4-22-2024 12:00:00),
(100, 11, held, 4-22-2024 12:00:00),
(100, 12, held, 4-22-2024 12:00:00)
This operation would fail and be rolled back, for example, if another client successfully booked seats 12 on the same show. The important thing is that this insert statement inserts all the seats in the booking at once. This way, if the operation fails, it would be rolled back for all the seats.
Going to book() stage, we need to update the status from "held" to "booked".
begin transaction
select status, timestamp from Bookings where booking_ID = 200 and status = 'held' for update
update Bookings set status='booked', timestamp = '4-22-2024 12:00:02' where booking_ID = 200
end transaction
This is using pessimistic locking on the row. This ensures only one thread succeeds to change the status of this row. We assume performance would be acceptable because it will only lock these rows. At the booking or finalization stage, only one client would be accessing these rows, so we think the scalability aspect would be OK.
Note that, although we are using RDB's powerful transaction functionality, we are NOT putting the whole process (select_seats(), book(), finalize_booking()) in one transaction. Doing so would have significant performance and scalability impact, as it would lock rows in DB for minutes. DB transactions are meant to last for a very short amount of time (milliseconds).
Instead, we are using a transaction in one operation (SQL book() and finalize_booking()), which would finish very quickly.
Explain any trade offs you have made and why you made certain tech choices...
Dive deep into the SQL Database.
The most important part for the Database is to make sure there is no double booking.
To make sure double booking won't happen, there are two locking mechanisms to make sure two requests won't update the same database row at the same time.
According to estimated write QPS, which is 2 for peak hours, which is low, I will choose Pessimistic locking. Another reason is that the ticket booking workflow didn't need low latency. User will tolerate for couple second delays. If later the traffic increase, I could consider switch to Optimistic locking if later we have increased traffic and required low latency of the system.
Try to discuss as many failure scenarios/bottlenecks as possible.
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?