Django shell + threading (Lesson 03 gave you the muscle). Do not look at solutions.md before submitting.
Setup: python manage.py shell with two free seats and two users.
Exercise 1 — The race, reproduced
- Write
reserve_without_lock(seat_id, user): readis_reserved(), sleep 0.05s, create the item. Run it with 2 threads on the same seat. How many reservations were created? (the I1 constraint may save the data with IntegrityError — catch it and note which thread won). - Remove the conditional constraint (temporary migration) and repeat: 2 reservations? Document the disaster and restore.
Exercise 2 — select_for_update: exclusion works
- Same function, but with
Seat.objects.select_for_update().get(pk=...)insidetransaction.atomic(). What does thread 2 do? How many reservations at the end? - Switch to
nowait=True: which exception reaches thread 2? How do you translate it into an HTTP response? - With
skip_locked=Trueand 2 threads reserving different seats of the same event: did each reserve theirs without waiting on each other?
Exercise 3 — Deadlock: provoke it and fix it
- Thread A locks seat 1 then seat 2 (two separate transactions, with sleeps). Thread B: first 2, then 1. Does PostgreSQL kill anyone? What exact error?
- Fix it with a global order (both lock with
order_by("id")). Does it still happen? - Add automatic retry: catch the deadlock error and retry once with backoff.
Exercise 4 — Isolation level in action
- On READ COMMITTED (default): thread A starts a transaction and reads an event's price; thread B changes it and commits; A re-reads inside its transaction. Did it see the change? (non-repeatable read).
- Repeat with
REPEATABLE READ(transaction.atomic()+SET TRANSACTION ISOLATION LEVEL REPEATABLE READ). What does A read now? - Write the rule: when you raise the level and when a point lock suffices.
Exercise 5 — The definitive transaction
Implement the lesson's full reserve() (ordered locking + rules + creation + I2) and demonstrate it:
- 5 threads, 5 different seats: all 201, no perceptible waits.
- 2 threads, same seat: 1 winner and 1 with your domain exception → 409.
- Final metric:
len(connection.queries)of one reservation (should be a small, stable number) and the total time of the 5 reservations from point 1.
Submit
Paste code, outputs and numbers. With this we close the heart of the data module; next Lesson 11 — Versioned migrations.