Premier League Predictor: FastAPI & Redis — Chapter 4, Exercise 1 ===================================================================== TASK Modify this chapter's own except WatchError: continue branch so it re-queues the SADD directly instead of looping back to re-check SISMEMBER (i.e. treat it like the textbook counter-retry pattern). Run the real two-concurrent-request scenario against this version and report what actually happens. SOLUTION Changing only the WatchError handler, from looping back to the top of the while loop to instead re-queuing the write directly: except WatchError: # the "textbook" version -- retry the write, don't re-check pipe.multi() pipe.sadd(gw_key, team_id) await pipe.execute() return "ACCEPTED (paired via blind retry)" Running the real two-concurrent-request scenario against this version: Fixture A (blind retry): ACCEPTED (paired via blind retry) Fixture B (blind retry): ACCEPTED (paired) Real final used_teams set: {'5'} Both requests report ACCEPTED. This is the exact same real bug this chapter's own fully naive version produced, just reproduced through code that looks, at a glance, like it's using real optimistic locking. The reason is straightforward once traced through: WATCH only protects the specific commands queued between MULTI and EXEC -- it never re-runs the check that decided whether SADD should even happen in the first place. When the WatchError branch blindly re-queues SADD, it's trusting a decision ("this team isn't used yet") that was made against data that's already known to be stale, since a WatchError only fires because the watched key changed underneath it. SADD itself doesn't complain either -- adding an already-present member to a Set is a real, silent no-op that still returns successfully, so the second call genuinely does "succeed," just not in the way the caller intended. This confirms directly why this chapter's own retry loop re-checks SISMEMBER instead: WATCH by itself only guarantees the write hasn't been corrupted by a concurrent change -- it says nothing about whether the original decision to write is still valid. For a first-writer-wins rule, re-validating that decision on every retry isn't an optional nicety, it's the actual fix. WHY THIS WORKS AS AN ANSWER ---------------------------- It makes the real, minimal code change requested, reproduces the original bug through real execution rather than predicting it abstractly, and correctly explains why: WATCH protects the write's own atomicity, not the validity of the decision that led to queuing that write in the first place.