Booking Across Services: Sagas, 2PC, and Overbooking
A hotel stay or a flight itinerary is several resources that must be reserved all-or-nothing, often across different services — the distributed-transaction problem ticketing dodges. A runnable lab on sagas, 2PC's blocking, date-range atomicity, and deliberate overbooking.
Selling one ticket is a single atomic decrement. Booking a trip is not: a hotel stay needs a room on every night of a range, a flight itinerary needs a seat on every leg — and those resources often live in different services or shards, so there's no shared database transaction to make it all-or-nothing. Reserve leg A, fail on leg B, and leg A is now a charged, held reservation for a trip that can't happen. This is the distributed transaction — the cross-shard write DDIA calls partitioning's Achilles heel — made measurable.
Runnable companion:
multi-resource-bookingon GitHub.make saga && make twopc && make stay && make overbookreproduces every number below (pure Python). It bridges the inventory labs (ticketing, flash-sale) into distributed transactions.
Reserve every resource or none — the saga
Reserve resources one at a time and a later failure strands the earlier ones. 100,000 bookings, 3 resources each:
per-leg fail naive orphan reservations saga orphan reservations
5% 13,759 0
15% 34,473 0
30% 50,447 0
A naive multi-step booking leaks a reservation on every partial failure — money charged, inventory held for a trip that can't happen. A saga reserves each step (each commits locally) and, on failure, runs compensating actions to release the earlier ones → zero orphans: the atomic guarantee without a shared transaction. (If all resources share one database, skip the saga — one ACID transaction is simpler and isolated. The saga is for when they don't.) The price: every forward step needs a correct inverse (release, refund, cancel), and a compensation can itself fail, so it needs retries + a dead-letter path.
Saga vs 2PC: the blocking trade-off
Both give all-or-nothing across services. 2PC (prepare → vote → commit) adds isolation — participants lock, no one sees a half-booking — but a coordinator crash after the prepare leaves them blocked, holding locks until recovery:
coordinator fail 2PC blocked bookings saga blocked bookings
2% 2,017 0
5% 4,918 0
10% 9,965 0
A saga never waits on a coordinator (commit-or-compensate locally), so it never blocks — trading 2PC's isolation for availability. Rule of thumb: 2PC for co-located, short transactions; sagas for cross-service, long-lived ones — which is most real bookings.
Inventory is per date, and stays are ranges
Hotel/flight stock is keyed by night (per date). A stay reserves a room on every night of its range, all-or-nothing. 10 rooms/night, 30 nights:
stay nights atomic: stays naive: stays naive orphan-nights
1 213 213 0
2 137 132 21
3 89 83 39
5 49 46 52
A 1-night stay is a single counter — nothing to strand. From 2 nights up, naive per-night booking leaks room-nights and completes fewer stays; atomic leaks nothing. Longer stays also succeed less often — they need a run of consecutive open nights.
The airline exception: deliberate overbooking
Ticketing must never oversell. Airlines oversell on purpose — no-shows leave paid seats empty. Capacity 200, 10% no-show:
overbook empty seats bumped flights bumping
0% 20.0 0.00 0.0%
10% 2.9 0.93 29.5%
20% 0.0 15.99 99.9%
Overbooking near ~11% (no-show / (1 - no-show)) nearly fills the plane; past that, bumps climb fast, and variance means even the sweet spot bumps sometimes — priced in as bump compensation. Same counter as ticketing, opposite risk appetite: a yield decision, not a correctness one.
What I'd say in an interview
- Booking a trip is a cross-resource atomic write. Co-located → one ACID transaction. Across services → a saga (reserve + compensate); measured, a naive multi-step booking leaks tens of thousands of orphan reservations.
- 2PC gives isolation but blocks on coordinator failure; sagas stay available but aren't isolated — pick sagas for cross-service, long-lived flows.
- Inventory is per date/interval — reserve the whole range atomically; availability falls with stay/route length.
- Overbooking is a deliberate yield trade-off tuned to the no-show rate — the opposite of ticketing's zero-oversell.