Django shell + threading (la 03 te dio el músculo). No mires solutions.md hasta entregar.
Preparación: python manage.py shell con dos asientos libres y dos usuarios.
Ejercicio 1 — La raza, reproducida
- Escribe
reservar_sin_bloqueo(seat_id, user): leeesta_reservado(), duerme 0.05s, crea el item. Lánzalo con 2 threads sobre el mismo asiento. ¿Cuántas reservas se crearon? (la constraint de I1 puede salvar los datos con IntegrityError — captúrala y anota cuál thread ganó). - Quita la constraint condicional (migración temporal) y repite: ¿2 reservas? Documenta el desastre y restaura.
Ejercicio 2 — select_for_update: la exclusión funciona
- Misma función, pero con
Seat.objects.select_for_update().get(pk=...)dentro detransaction.atomic(). ¿Qué hace el thread 2? ¿Cuántas reservas al final? - Cambia a
nowait=True: ¿qué excepción llega al thread 2? ¿Cómo la traduces a una respuesta HTTP? - Con
skip_locked=Truey 2 threads reservando asientos distintos del mismo evento: ¿cada uno reservó el suyo sin esperarse?
Ejercicio 3 — Deadlock: provócalo y arréglalo
- Thread A bloquea asiento 1 y luego el 2 (dos transacciones separadas, con sleeps). Thread B: primero el 2, luego el 1. ¿PostgreSQL mata a alguien? ¿Qué error exacto?
- Arregla con orden global (ambos bloquean
order_by("id")). ¿Sigue pasando? - Añade reintento automático: captura el error de deadlock y reintenta una vez con backoff.
Ejercicio 4 — Nivel de aislamiento en acción
- En READ COMMITTED (default): thread A inicia transacción y lee el precio de un evento; thread B lo cambia y commitea; A relee dentro de su transacción. ¿Vió el cambio? (lectura no repetible).
- Repite con
REPEATABLE READ(transaction.atomic()+SET TRANSACTION ISOLATION LEVEL REPEATABLE READ). ¿Qué lee A ahora? - Escribe la regla: cuándo subes el nivel y cuándo basta bloqueo puntual.
Ejercicio 5 — La transacción definitiva
Implementa reservar() de la lección completa (bloqueo ordenado + reglas + creación + I2) y demuéstrala:
- 5 threads, 5 asientos distintos: todos 201, sin esperas perceptibles.
- 2 threads, mismo asiento: 1 ganador y 1 con tu excepción de dominio → 409.
- Métrica final:
len(connection.queries)de una reserva (debe ser un número pequeño y estable) y tiempo total de las 5 reservas del punto 1.
Entrega
Pega código, outputs y números. Con esto cerramos el corazón del módulo de datos; después Lección 11 — Migraciones versionadas.