Assuming a DAU of 100M daily users, with 1% booking (10M) while rest browse the products, search.
Bookings: 1M/day = 10/sec.
At peak it could go upto 200 bookings/sec
During a movie launch or booking start, this number could be 1000 bookings/sec for the limited seats.
Seat Activity (Selection, deselection) - on an average 5 per booking -> 50/sec normally, 1000/sec during normal peak (evening time), which can reach upto 5000 /sec during movie launch
Read will be 20:1 ratio
Reads = 200/sec, peak at 4000/sec, with extreme peak at 20K/sec
This is a read heavy system.
Storage:
Bookings data: 1M * 2KB = 2GB/day = 730GB/year
Browse Movie Details / Show Times
GET /v1/movie/{movie_id}
{movie_id: uuid, title: string, description: string, cast: [], run_time_sec: int, rating: double, languages: [Hindi, malayalam]}
GET /v1/movie/{movie_id}/shows?location=Hyderabad&language=Hindi&screen=2D
{
theatres: [{show_id, show_timing}]
has_more: True,
cursor: uuid
}
Seat Selection
GET /v1/seat/availability/{show_id}
{
seats: {
available: [seat_ids]
taken: [seat_ids]
}
}
POST /v1/seat/
Header:
Idempotency-Key: uuid
Authentication: Bearer
{
user_id: uuid,
seats: [seat_id_1, seat_id_2],
}
Response: 200 for hold, 409 for conflict
Order Placement
POST /v1/order/
Header:
Idempotency-Key: uuid
Authentication: Bearer
{
order_id: uuid,
movie_details: {}
price: int
currency: string
payment_url: string
}
DELETE /v1/order/{order_id}
Header:
Idempotency-Key: uuid
Authentication: Bearer
{
order_id: uuid,
movie_details: {}
refund_amount: int
currency: string
payment_url: string
}
Status: 204
View Orders
GET /v1/order/{order_id}
{
order_id: uuid,
movie_details: {},
seats: [seat_id_1, seat_id_2],
amount: 100
}
Clients requests are routed through LoadBalancer and then through Gateway and Rate Limiter.
We have Auth service to handle Authentication, Login/Logout
Users can Search for movie name, cast etc, which is powered through Elastic Search. When movie catalog is updated, it is updated in the DB and then through Kafka, it is asynchronously added to Elastic Search. Since this data is static and is read heavy, we cache it in Redis and CDN for low read latency.
We have seat selection service, which checks the status of seats in Redis and updates them. When a seat is selected, it emits an event in Kafka, which is directed through Web Socket and pushes the updates to the clients in near real time.
However, for stricter check, we will check the status of all the seats an user is trying to book/hold in Postgres and only hold if all seats are avaialbe. This can be done in a single atomic operation.
Order Service checks the status of seats, validates the availability of the seat, adds the order details to the DB and marks payment status as processing. It calls the payment service, which returns it with a URL, which is returned to the client.
The payment service calls the 3rd party payment integration and listens to the webhooks for status update. In the meanwhile we will show processing to the user.
order and payment are done using idempotency keys to avoid duplication.
Order service also returns the order details to the users, which can be cached in Redis for low read latency
Notification, monitoring services also reads from the kafka topics.
Movie Details, Order Details, Seat Details, Payment info: Postgres
Caching: Redis
Search: Elastic Search
Movie details:
(
movie_id: uuid,
title: string
description: string
cast: {lead_hero: user_id, director: user_id_2....}
duration_sec: int
)
Show details:
(
show_id: uuid,
movie_id: uuid,
theatre_id: uuid,
start_time: timestamp
created_at: timestamp
)
seat details:
(
show_id: uuid
seat_id: uuid,
status: ENUM [booked, held, available]
created_at: timestamp
updated_at: timestamp
user_id: uuid (denormalized for better user_id, seat_id mapping)
)
Indexed at (user_id, show_id, seat_id)
We will partition by show_id.
Order:
(
order_id: uuid,
user_id: uuid
show_id: uuid
booking_id: uuid
payment_status: ENUM,
created_at: timestamp,
last_updated: timestamp
)
Payment:
(
booking_id, payment_id, amount, currency, payment_medium, created_at, updated_at
)
Redis is for caching product data, hot seat selection
We will save the title, description, actors details in Elastic Search for searching the movies. We will support fuzzy search as well.
Seat Selections:
When an user selects a seat, we will check in Redis, if it is available, and if it is available (all the seats), we will mark all the seats as held in one atomic move (lua script). We will also write it to the Kafka topic.
Workers can periodically update the data in Postgres and hold it for 10 mins, to let payment complete.
Workers will also update Websocket, which will broadcast it to the all the users who are current subscribed to the same show_id, so that the update is in almost real time, with 100-200ms of delay.
Validation service is responsible for marking these things, so that we do not double book at any point.
A stricter check will be present at booking time.
If the payment process is not completed within 10 mins, a background worker will mark the seats as available. This will save us from abandoned seat selection, network issues etc.
Payment:
We will forward the payment request to a third party, with an idempotency key for retries. We will pass the amount in the lowest denomination possible, to avoid dealing with flowing point. (E.g. 10000 paise, instead of 100Rs)
Our service will listen on the webhook for status updates. Users can retry with the same idempotency keys in case of payment failure (network issue). For other failures such as card declined, wrong pin/otp, we will ask the users to re-enter the details.
On successful payment, the order is marked from Processing to completed.
During cancellation, we will issue a refund against the booking id, based on business rules.
Failures during Payment will be dealth with SAGA pattern. We will issue a correcting step against the wrong action (Refund for double booking/ double charges etc)
In case of Redis failure, we can return the cached data from CDN as the product details do not change frequently, so we can aggresively cache with high TTLs.
For seat selection, Postgres will be the source of truth, however we will protect it behind circuit breakers in order for preventing failure cascading.
If an user disconnects from the websocket it can ask for a fresh connection.
If the number of users is too high for an event (world cup tickets, block buster movies), which are predictable, we can pre-warm up new services, keep active-active backups during the event.
We can also put the users in a queue and let their request get processed at a speed our servers can handle.