Design a booking system

A million bookings a day is easy. A hundred thousand buyers in one minute for five thousand seats is the round: a hold that expires, one conditional update that can never sell a seat twice, and a line in front of the hot event so its row is never asked for more than it can do.

The round this design follows

How do you prevent double booking?

Make the check and the write one statement on one row. The inventory row for an event and seat class carries the total, the seats held, the seats sold and a version, and a hold is an update that adds to held only where the seats left cover the request and the version is the one you read. Two buyers of the last seat both send that update; the database applies them one at a time under the row's lock, so the second finds nothing left or a newer version and is refused. A design that reads the count, decides in the application and then writes has a gap between the two, and two buyers fit through it.

Why hold seats instead of booking them at checkout?

Because paying takes minutes and deciding who gets a seat takes a millisecond. A hold takes the seat out of the count at once and gives the buyer ten minutes to pay; if they pay, the hold becomes a booking, and if they do not, a sweeper returns the seats. Without a hold you either lock the seat for the whole payment, which holds a database lock for minutes, or you check before paying and book after, which sells the seat twice when two buyers pay at once.

What happens when a hundred thousand people try to buy five thousand seats in a minute?

That is 1.67 thousand attempts a second on one inventory row, and one row takes about 1 thousand serialised updates a second. So a waiting room goes in front of the event: buyers get a place in line, and the line admits 700 a second, seventy percent of what the row can take. At that rate every seat is held within about seven seconds, and the line then tells everyone else the event is sold out, keeping them in order for the seats that come back when holds expire.

What if one inventory row is still too slow?

Split it. Three bucket rows of a third of the seats each take three times the updates, and a buyer starts at a bucket picked from their id and moves to the next when it is empty. The cost is that a buyer asking for several seats can be refused while seats remain, spread across buckets that each have fewer than they asked for, and near the end the buckets have to be merged back into one.

What happens if the expiry sweeper stops?

No double booking, because the confirm checks that the hold is still live and refuses one that ended. But the seats in expired holds stay counted as held, so the event shows sold out while seats sit unused. That is why the sweeper runs with a standby, and why the alert is the age of the oldest hold whose end time has passed: a healthy sweeper keeps it at a few seconds.