Módulo 10 · Observabilidad y depuración

Lección 47 — Depurar en producción

Reproducir fallos y escribir postmortems sin señalar culpables.

Publicada
En esta lección
  1. Ejercicio 1 — El protocolo en frío
  2. Ejercicio 2 — El incidente
  3. Ejercicio 3 — La reproducción
  4. Ejercicio 4 — El postmortem
  5. Ejercicio 5 — El on-call sostenible
  6. Resumen del profesor

Ejercicio 1 — El protocolo en frío

  1. El checklist (extracto del oncall.md):
1. CONFIRMAR (2 min):  dash del 46 → ¿el síntoma? ¿desde cuándo? ¿tras deploy?
2. ACOTAR (5 min):     trace (46) + log (45) → ¿endpoint/tenant? ¿cuántos afectados?
3. MITIGAR:            flag (27) → rollback (41) → scale (55)  [en este orden]
4. LOCALIZAR:          traza → log → py-spy dump (37) → pg_stat_* → git diff
5. EXPLICAR:           postmortem blameless (§4) con acciones
  1. El simulacro: el job pesado del 31 sube la CPU a 90% durante 4 min; el paso 1 pregunta "¿el USUARIO lo sufre?" → el p95 de endpoints plano, el error rate plano → NO page (la alerta de CPU no existe: la 46 la puso en ticket por síntoma-vs-causa). El criterio: página el SÍNTOMA del usuario; la causa en ticket con dash. El pánico de "¡CPU 90%!" muere en el paso 1.
  1. Las 5 respuestas del incidente: (¿quiénes?) los checkouts de todos los tenants desde 14:22 (el log por tenant confirma: no es uno); (¿dónde?) el endpoint del checkout (el dash lo separa); (¿desde cuándo?) 14:22 (la anotación del deploy); (¿qué cambió?) la PR del prefetch (el diff); (¿cuánto impacto?) p95 2.8 s, ~180 checkouts afectados, budget del 46 quemándose a burn 9 — el impacto cuantificado es el que prioriza la mitigación.

Ejercicio 2 — El incidente

  1. Las mitigaciones medidas: (a) flag: FEATURE_SEAT_PREFETCH=false + reload del settings cache (30 s) → p95 base en ~4 min; NADA se pierde (el flag solo apaga el prefetch). (b) rollback: 11 s de switch (41) + re-deploy del azul + los otros 2 features del deploy FUERA de prod (el rollback es el martillo: revierte todo). (c) scale-out: 3 réplicas más → el p95 baja a 1.9 s (disfraza: la contención en Redis sigue; la factura sube para siempre). La decisión: flag por quirúrgico y reversible; rollback el plan B si el flag no existe (lección: TODO feature de riesgo nace con su flag, la 27/41).
  1. El guard del regreso: la revisión del 50 con la checklist "¿la optimización toca un recurso limitado? ¿qué pasa bajo rate limit/contención?" habría preguntado "¿el prefetch respeta el rate limiter?" — y el test del 36 con la mezcla bajo el límite lo mediría. La lección del postmortem: las "optimizaciones" entran por la puerta del review con el mismo rigor que las features (el caché mal puesto del 38 es el antipatrón más común de rendimiento).

Ejercicio 3 — La reproducción

  1. El dataset anonimizado: 200 tokens de request con UUIDs sintéticos (mismo patrón de llegada: la tasa del 14:22), 1 evento con 20k asientos, el token del rate limiter agotado. Sin email/nombre/IP real (la 23: el patrón es el dato; la identidad, no).
  1. El test rojo (integración + carga en miniatura):
python
@pytest.mark.django_db(transaction=True)
def test_prefetch_bajo_rate_limit_degrada_checkout(rate_limiter, evento, meseta_local):
    rate_limiter.agota("prefetch")          # el estado del incidente
    with self.assertLess(  # el p95 de 30 checkouts
        p95(lambda: checkout(client, evento)), 0.8):
        ...
# FAILED: p95 = 2.71 s (la firma del incidente)
  1. El fix elegido: el prefetch ASYNC (el 29: la disponibilidad "recién vista" se refresca en background, el request sirve del estado consistente) + el caché del 38 con single-flight para el caso sync. Test verde (p95 0.42 s) + meseta del 36 verde. El test de regresión: test_prefetch_no_bloquea_checkout_bajo_rate_limit — el nombre ES el postmortem en miniatura.

Ejercicio 4 — El postmortem

  1. El documento (extracto):
markdown
# Postmortem: degradación del checkout — 2027-09-28 14:22 UTC
Impacto: 180 checkouts con p95 2.8 s (48 min), budget del 46: -12% del mes. Sin pérdida de dinero.
Timeline (UTC):
  14:20  deploy a1b2c3d (PR "optimización disponibilidad": prefetch síncrono)
  14:22  p95 checkout cruza 800 ms; alerta page del burn rate
  14:31  trace+log localizan el span seat_availability_prefetch bajo rate limit
  14:36  FEATURE_SEAT_PREFETCH=false → p95 base a las 14:40
Causas: raíz — el prefetch síncrono compite por el rate limit del Redis bajo contención.
Contribuyentes: (1) la PR entró como "optimización" sin test de carga; (2) el feature
sin flag en el primer deploy; (3) la revisión sin checklist de recursos limitados.
Acciones: [las 4 del §4 con issue/dueño/fecha]
  1. El test blameless: la primera versión decía "el junior desplegó sin flag" → reescrito: "el despliegue no exige flag para features nuevas (el pipeline no lo pregunta)" — el sistema cambia (la política del 41), la persona no se marca. El tono cambia: el documento pasa de juicio a ingeniería.
  1. Las acciones: P1 el test de regresión (protege HOY), P2 el canary automático del 41 (protege el próximo), P3 el runbook del rate_limited (protege al on-call). La sin-issue es un deseo: las 4 quedaron con issue, dueño y fecha — el postmortem sin acciones cerradas es una confesión sin penitencia.

Ejercicio 5 — El on-call sostenible

  1. La métrica de madurez: incidente A (el del §2): MTTR 18 min (diagnóstico manual, sin runbook); game day B (Redis): MTTR 11 min (runbook de la 46); hallazgo C (drift del 44): 4 min (el plan lo caza). La tendencia baja EXPLICA la inversión: el módulo 10 compró minutos de MTTR con logs (45), métricas (46) y protocolo (47) — el MTTR es la métrica que justifica la observabilidad a los que facturan el tiempo.
  1. El oncall.md final (2 páginas): protocolo de 5 pasos con comandos → las 5 alertas page con runbook (burn checkout, sagas, outbox, heartbeat, 5xx) → checklist del postmortem con la regla blameless → reglas del on-call de 1 (MOC, blackout de cambios durante incidente, recuperación agendada). El test del documento: el tú de las 3 a.m. lo sigue sin pensar — y el tú de la semana 30 lo agradece porque el sistema aprendió de cada incidente.

Resumen del profesor

  • Confirmar → acotar → mitigar → localizar → explicar: el usuario primero, la causa después, el ego nunca.
  • El incidente se resuelve con la cadena dash→trace→log→diff→flag; la mitigación quirúrgica (flag) vence al martillo (rollback) si la feature nació con flag.
  • El postmortem blameless con acciones cerradas es el único mecanismo por el que el sistema aprende; el MTTR descendente es la prueba.