The Third Status That Makes the Real Lock Disappear

How soft locking replaces a two-minute database lock with a status field, why that lock only needs to last half a millisecond, and why an in-memory lock can't do this job across multiple servers.

August 30, 20264 min read9 / 44

A database lock can safely guard the moment seats get checked and marked, but holding that same lock for the whole two-minute payment window is too expensive at BookMyShow's scale. What's left is figuring out how a seat stays off-limits to everyone else for those two minutes once the lock is already gone.

Soft Locking: A Third Status, Not a Held Lock

A seat's status has been either available or booked so far. Soft locking adds a third one: locked.

That third status is the whole trick. Instead of holding an expensive database lock for the entire payment flow, you flip the seat's status to locked and let that status do the blocking work instead. Something still has to flip it back eventually, whether the payment succeeds, fails, or the user just walks away — this post covers how the lock itself works, and the next one covers how it gets released.

The Actual Algorithm

Here's the full sequence, refined from the earlier version:

  1. Lock the requested seat rows in the database (the SERIALIZABLE isolation from the last post).
  2. Read those rows and check each seat's status.
  3. If any seat isn't available, release the lock and reject the whole booking.
  4. If every seat is available, update all of them to locked, then release the lock.

The lock still matters here. Without it, two people could both read a seat as available at the same instant, before either one writes anything, and both would wrongly proceed.

Why It's Safe to Release the Lock Right After Step Four

Here's the part that makes this work. Once step four finishes, the status itself takes over the job the lock was doing.

Picture a second request arriving right after. It gets the database lock without any trouble. It reads the seats without any trouble.

But when it checks their status, it doesn't see available anymore. It sees locked, and that alone is enough to correctly reject it.

The lock only had one job: make sure nobody could read stale data while the first request was busy updating it. Once the update finishes, the data itself carries the "this is taken" fact forward. Nothing else needs to hold anything.

The Lock Only Lasts a Fraction of a Second

Steps one through four happen entirely on the database, with no network round trip to a user's browser in between. The whole thing takes about half a millisecond.

That answers a question worth asking directly: what happens if two requests arrive within that same half a millisecond? One of them wins the lock first. The second one doesn't get rejected instantly. It waits.

A fraction of a millisecond later, the first request finishes and releases the lock. The second request then gets its turn, reads the seats, and sees locked instead of available. It gets rejected too, just correctly and half a millisecond later than it might have expected.

This is what actually fixes the scaling math from before. A lock held for half a millisecond, even across thousands of simultaneous bookings, never adds up to anywhere near 100,000 open locks.

The two-minute number only mattered when the plan was to hold the lock for the whole payment. Soft locking removes that plan entirely.

Why an In-Memory Lock Won't Work Here

One more option worth ruling out directly: could this be solved with an in-memory lock, using something like Java's own concurrency tools, instead of a database lock?

No, because BookMyShow runs on more than one server. An in-memory lock only protects requests handled by the same process. If two overlapping booking requests land on two different servers, an in-memory lock on one of them does nothing to stop the other.

The lock has to live somewhere every server can see, which means it has to live in the shared database, not in any one server's memory.

Finding a Critical Section Isn't Mechanical

There's no formula for spotting the critical section, the exact lines that need this protection. You read the code and look for shared state, the specific point where more than one execution path can touch the same data at the same time. That's what needs guarding.

One thing worth keeping straight while you're looking: a function's code exists once. Calling it from two places at the same time doesn't create two copies of that code.

Take the book-ticket function itself. Two different users calling it at the same instant doesn't spin up two versions of its logic. Each call gets its own local variables instead, tracked in its own call stack: which seats, which user, which show.

What both calls actually share is the seats table sitting in the database. That shared table is the critical section, not the function's code.