Módulo 2 · Bases de datos

Lección 09 — Índices y planes de ejecución

EXPLAIN, consultas lentas y el problema N+1: la lección que resuelve la disponibilidad de TicketFlow.

Publicada
En esta lección
  1. Ejercicio 1 — Sin índice vs con índice
  2. Ejercicio 2 — Leer el plan como un senior
  3. Ejercicio 3 — El N+1, cazado y muerto
  4. Ejercicio 4 — Diseña el índice correcto
  5. Ejercicio 5 — El índice que estorba
  6. Entrega

Sobre tu PostgreSQL (psql recomendado: \timing on). No mires solutions.md hasta entregar.

Preparación: genera volumen — 20.000 asientos, 5.000 reservas e ítems (script o generate_series con INSERT...SELECT). Sin volumen no hay planes que leer.

Ejercicio 1 — Sin índice vs con índice

  1. EXPLAIN ANALYZE SELECT * FROM events_payment WHERE reservation_id = 1234; (o tu tabla de pagos). ¿Seq Scan? ¿Coste? ¿Filas estimadas vs reales?
  2. Crea el índice CREATE INDEX ON events_payment (reservation_id); y repite. Anota la mejora.
  3. Ahora filtra por algo no indexado con baja selectividad (WHERE status = 'SUCCEEDED'): ¿seq scan o index? ¿Por qué el planificador puede preferir el seq scan incluso con índice?

Ejercicio 2 — Leer el plan como un senior

  1. Ejecuta la consulta de disponibilidad (07) con EXPLAIN (ANALYZE, BUFFERS).
  2. Identifica: tipo de scan de cada tabla, si hay Sort y con qué método, filas estimadas vs reales por nodo.
  3. Cambia el NOT EXISTS por LEFT JOIN + IS NULL y compara planes. ¿Cuál usó el planificador mejor?

Ejercicio 3 — El N+1, cazado y muerto

  1. En Django shell: from django.db import connection, reset_queries y cuenta queries de un loop que imprima r.user.email para 50 reservas. Anota el número.
  2. Mismo loop con select_related("user"). ¿Cuántas queries?
  3. Añade r.event.title al loop sin tocar el queryset: ¿cuántas ahora? Arréglalo.
  4. Bonus: relación inversa (r.items.all() por reserva) con y sin prefetch_related.

Ejercicio 4 — Diseña el índice correcto

Para cada consulta, propon el índice (columnas y orden) y justifica:

  1. WHERE event_id = X AND state = 'PUBLISHED' ORDER BY starts_at (event)
  2. WHERE gift_card_id = X ORDER BY created_at (ledger de la 08)
  3. WHERE user_id = X AND created_at > now() - interval '30 days' (reservation)
  4. WHERE code =? (cupón, se busca en cada checkout — crítica)
  5. WHERE status = 'PENDING_PAYMENT' AND expires_at < now() (job de expiración — el más caliente)

Ejercicio 5 — El índice que estorba

  1. Crea un índice obviamente inútil: CREATE INDEX ON events_event (description); (texto largo, nunca filtrado).
  2. Mide INSERTs: antes/después con \timing e INSERT...SELECT de 1.000 filas. ¿Cuánto paga cada insert por mantener el índice?
  3. Bórralo (DROP INDEX) y registra la regla que aplicarás antes de crear cualquier índice en adelante.

Entrega

Pega planes recortados y números. Con la corrección cerramos la 09; después Lección 10 — Transacciones.