Ejercicio 1 — Expand-contract
1-2. La migración de datos por lotes:
def copy_title(apps, schema_editor):
Event = apps.get_model("events", "Event")
qs = Event.objects.filter(label__isnull=True).order_by("pk")
batch = 1000
while True:
items = list(qs[:batch])
if not items:
break
for e in items:
e.label = e.title
Event.objects.bulk_update(items, ["label"], batch_size=batch)- Dual write: en
save()del modelo o en los servicios de creación se setean ambos campos. Los 3 eventos de prueba tienen ambas columnas iguales. - Tras CONTRACT (RemoveField de title), el código viejo que hace
event.titlelanzaFieldError/OperationalError: por eso el paso 4 solo ocurre cuando NINGÚN pod corre código viejo (el orden de despliegue es parte del diseño).
Ejercicio 2 — NOT NULL
- Django genera el AlterField con default; PostgreSQL moderno hace el default como metadato y luego valida — pero el default queda "pegado" al schema (y con versiones/acciones variadas puede rewrite): el camino explícito evita sorpresas y documenta la intención.
- Camino largo: nullable (instantáneo) → backfill por lotes (no bloquea, memoria controlada) → AlterField NOT NULL (valida sin backfill pendiente). Con \timing ves que el backfill es lo lento — y por lotes no bloquea a nadie.
- El linter señala las AddField NOT NULL, RemoveField y AlterField de tipo de tu historial: la lista es tu deuda técnica de migraciones, para planear (no para asustarse).
Ejercicio 3 — CONCURRENTLY
- Migración:
class Migration: atomic = False; operations = [RunSQL("CREATE INDEX CONCURRENTLY idx_reservation_created ON events_reservation (created_at);", reverse_sql="DROP INDEX IF EXISTS idx_reservation_created;")] - Sin CONCURRENTLY, el CREATE INDEX toma un lock que bloquea INSERT/UPDATE de la tabla mientras dura el build (en tablas grandes: segundos-minutos de API parada). Si un CONCURRENTLY falla a medias, queda un índice INVALID que ocupa y NO se usa — hay que DROP y reintentar.
- El check SQL devuelve los índices inválidos; en CI/después de deploys grandes es un buen smoke.
Ejercicio 4 — Las tres mejoras
- NOT NULL: nullable → backfill (valores razonables: created_at para updated_at vacíos, etc.) → AlterField.
- CHECK
expires_at > created_at: primero corrige filas viejas que lo violen (¿hay?SELECT COUNT(*) WHERE NOT (expires_at > created_at)); despuésAddConstraint. Si hay violadores, la migración falla al aplicar — por eso el orden: corregir, luego restringir. - Slug: nullable → backfill (
slugify(title) + id corto, garantizando unicidad con retry por colisión) → UNIQUE. (El AddConstraint UniqueField se aplica tras el backfill para no fallar.)
Resumen del profesor
- Migración = historia versionada: una intención, reverse siempre, lo desplegado no se toca.
- Expand → dual write → switch → contract: el patrón que hace el renombrado sin apagar nada.
- Lo bloqueante (NOT NULL a ciegas, índices grandes, ALTER TYPE) se hace por el camino largo y medido.
- apps.get_model y lotes en RunPython: determinismo y memoria.
Después de la corrección: Lección 12 — NoSQL, cierre del módulo de datos.