**00:00:00 — Alex:** Hey, welcome to Development Radio. I'm Alex, here with Maya. Today's story is about Redis locks. But we're going to arrive there through buying a concert ticket.

**00:00:10 — Maya:** That's a nice place to start. I'd be the person studying the seating plan, trying to find one perfect seat. Meanwhile, every perfectly good seat is being bought by somebody else.

**00:00:21 — Alex:** I'd choose something I could afford and close the seating plan before it talked me into an upgrade.

**00:00:26 — Maya:** Sensible. I'd call you while I was still deciding. Then you'd have to hear my argument for being three rows closer.

**00:00:32 — Alex:** I'd hear you out. You can make a surprisingly good case for three rows when it's somebody you really want to see.

**00:00:39 — Maya:** Once the booking is finished, though, I want to stop negotiating with the website. I've got my seat. Now I can look forward to the music.

**00:00:46 — Alex:** And that ordinary moment is our story. Someone chooses a seat and clicks Buy. Our app has to turn that into a booking they can trust.

**00:00:55 — Maya:** Let's use seat fourteen C. One person, one seat. I want to follow what happens behind that click.

**00:01:01 — Alex:** Let's start with the small version.

**00:01:03 — Maya:** Let's build a small ticketing app. Two servers can accept bookings. Both use the same Redis, and both save bookings in the same Postgres database.

**00:01:12 — Alex:** Postgres holds the seat record: who's booked fourteen C. Redis holds a separate lock for that seat. A server has to get the lock before it tries to book. If somebody else already has the lock, that request waits or backs off.

**00:01:27 — Maya:** And the lock expires after ten seconds. We need an expiry because a server might crash while holding it. Otherwise nobody else could get a turn.

**00:01:35 — Alex:** Right. Ten seconds is the limit we're choosing for this story. It gives a booking request time to work, but also gives the next request a way in if the first server disappears.

**00:01:45 — Maya:** Okay. I want to follow the first request before we bring in the second.

**00:01:49 — Alex:** Lena wants seat fourteen C. Her request goes to server A. A gets the Redis lock, then checks Postgres. The seat is free. But before A saves Lena's booking, the server pauses.

**00:02:01 — Maya:** So Lena hasn't got a seat yet. Her server checked, but it hasn't written anything.

**00:02:07 — Alex:** Exactly. Keep A there, paused before its write. The ten seconds pass. Redis expires the lock. A is still paused.

**00:02:15 — Maya:** The lock has ended. Lena's request hasn't ended with it.

**00:02:19 — Alex:** That's the gap. Redis removed its lock key. It didn't cancel the work waiting inside server A.

**00:02:25 — Maya:** Now let Omar try the same seat.

**00:02:27 — Alex:** Omar's request reaches server B. B can get a new Redis lock. It checks Postgres, and fourteen C is still free. Remember, Lena's server never saved her booking. B books the seat for Omar and commits that change.

**00:02:41 — Maya:** Omar has the seat now. If that were my confirmation email, I'd close the booking page. Done. I wouldn't keep checking whether the ticket was still mine.

**00:02:49 — Alex:** I'd probably leave the email open. I like seeing the confirmation.

**00:02:53 — Maya:** You can keep the tab. I'm done with it. Meanwhile, Lena's server is still paused.

**00:02:59 — Alex:** Yes. Omar's server finishes and releases its lock. Then A wakes up. Lena's server carries on from where it paused: just before saving her booking.

**00:03:09 — Maya:** It still has its old answer: the seat was free. It hasn't heard about Omar.

**00:03:14 — Alex:** And our original write doesn't check again. A overwrites the seat's buyer with Lena and sends her a confirmation. Now the database says Lena. Omar still has his email.

**00:03:25 — Maya:** There are our two promises. The second server didn't break into a lock. The first server simply kept going after its own lock was gone.

**00:03:33 — Alex:** Yes. We can make the pause twelve seconds. It might be a long garbage-collection pause. The important part is that it lasts longer than the ten-second lock. A resumes with work still to do.

**00:03:45 — Maya:** I want to hold onto that distinction. Redis knows the ten seconds have passed. But the code running on A doesn't get erased when Redis removes a key.

**00:03:54 — Alex:** And the next line of that code can still send a write to Postgres. That's why a live server isn't proof that its lock is still valid.

**00:04:01 — Maya:** My first repair would be another check. Before A writes, ask Redis whether the lock still belongs to A. In the story we just told, that would catch it.

**00:04:11 — Alex:** It would catch that pause. Move the pause a little later, though. A asks Redis and gets a yes. Then A stops, before the database write. The lock expires while A is stopped.

**00:04:23 — Maya:** And when A resumes, it's still carrying a yes from before the pause. I haven't joined the check to the write. I've only moved them closer together.

**00:04:32 — Alex:** Which can look reassuring in the code. The two lines are right beside each other. But the database receives the write later. It can't tell that the earlier Redis answer has gone stale.

**00:04:43 — Maya:** For the twelve-second pause we started with, a thirty-second lock would still be there when A wakes. I do want to acknowledge that. The longer expiry helps this request.

**00:04:53 — Alex:** It does. Lena's server would wake up with time left on its lock. Omar's request wouldn't have got in during those twelve seconds.

**00:05:00 — Maya:** But if we're choosing thirty because we need a guarantee, we still owe ourselves an answer about a longer pause. More time gives us margin. It doesn't put a limit on how long A can stop.

**00:05:12 — Alex:** And if A crashes, the next buyer waits longer for the lock to expire. We might accept that cost. We still need something that can refuse A's write after its time runs out.

**00:05:23 — Maya:** Before we leave Redis, our unlock code does guard against one late action. A can't just wake up and delete whichever lock happens to be there. We check that the stored random value is still A's.

**00:05:35 — Alex:** Yes, that stops A from deleting somebody else's lock. Keep it. But the harmful action we're following is a booking write. It goes to Postgres. Checking a value before deleting a Redis key doesn't make Postgres refuse that booking.

**00:05:49 — Maya:** So come back to the moment that matters. Omar's booking is already saved. Lena's server wakes up and tries to change the buyer to Lena. I want the database to say no to that write.

**00:06:00 — Alex:** Then put the condition in the write itself. Ask Postgres to set the buyer to Lena only if the buyer is still empty. That's one database operation. We aren't checking, returning to the application, and deciding to write afterwards.

**00:06:15 — Maya:** Let me run that against Omar's booking. The seat's buyer already says Omar. So the condition, buyer is empty, isn't true anymore.

**00:06:23 — Alex:** Right. Postgres leaves Omar's booking alone and reports that it updated zero rows. Zero matters here. It means Lena's request did not get the seat.

**00:06:33 — Maya:** I would want that result handled explicitly. No confirmation email just because the database call finished without an exception.

**00:06:40 — Alex:** Yes. A has to look at the result. Zero rows means tell Lena the seat was taken. Only a booking that committed successfully gets a confirmation. The database's refusal has to reach the customer.

**00:06:53 — Maya:** Lena still loses the seat, which feels lousy. But she finds out while she can choose another one. She doesn't learn it at the concert.

**00:07:01 — Alex:** And Omar keeps the seat he already booked. We haven't made the server pause disappear. We've made the late request unable to overwrite his booking.

**00:07:09 — Maya:** Which leaves me wondering whether I'd keep Redis here at all. If two requests reach that conditional update, the database still protects the seat. The lock isn't what makes the booking correct.

**00:07:20 — Alex:** I'd look at how much unwanted work the lock saves. Imagine a busy on-sale, with lots of people clicking fourteen C together. Redis can turn some requests away before they all reach the same database row.

**00:07:33 — Maya:** So keeping it is a load question. If this is a quiet ticketing app, I might prefer less machinery. If the same seat is getting hammered, the lock may earn its place.

**00:07:44 — Alex:** I agree. Either way, the condition stays on the booking write. We can discuss whether Redis is worth operating without making Omar's seat depend on that answer.

**00:07:53 — Maya:** Before I'd trust this repair, I want to see the pause happen on purpose. Sending two requests close together might never catch the awkward moment.

**00:08:01 — Alex:** In an isolated test, stop Lena's server right before its booking write. Leave it stopped until the Redis lock has expired. Now let Omar's request book fourteen C and commit. Only then let Lena's server continue.

**00:08:16 — Maya:** And don't stop at checking that one request failed. Look at the seat: Omar should still own it. Look at Lena's result: zero updated rows and no confirmation email.

**00:08:26 — Alex:** That gives us the whole path we were worried about. The old request wakes up, reaches the database, and is refused there. We're testing the refusal, not hoping the two requests arrive in a lucky order.

**00:08:38 — Maya:** For a lock in your own system, that's the question I'd take away: if its old holder resumes after expiry, what can still stop the write? Follow one request all the way to that answer.

**00:08:50 — Alex:** Back at the concert, Omar has fourteen C. Lena knows she needs another seat. The lock can expire, and the server can wake up late. We still make only one promise about that chair.
