Módulo 4: Monitoring The Launch

Alertas y señal: el síntoma no es lo mismo que el impacto

Descripción

El dashboard de la lección anterior es una vista que alguien tiene que abrir para consultar. Una alerta es distinta: avisa sola, sin que nadie tenga que estar mirando. Esa diferencia la hace poderosa —nadie se pierde un HALT a las tres de la mañana— y, mal diseñada, peligrosa: una alerta que suena por cualquier cosa entrena al equipo a ignorarla, y una alerta que avisa un número técnico suelto, sin decir si algún usuario está siendo afectado, obliga a alguien a investigar antes de siquiera saber si vale la pena preocuparse. Esta lección construye el criterio para distinguir una alerta que sirve de una que solo hace ruido.

Conexión con el módulo. Esta lección no vigila nada nuevo —los guardrails siguen siendo los mismos cuatro de la guía de métricas, y el mecanismo de vigilancia sigue siendo guardrailWatch()—; agrega la capa de notificación: cómo convertir un HALT calculado por código en una alerta que le llega a la persona correcta, con la información correcta, sin ruido de por medio.

Una analogía: el detector de humo que suena porque tostaste pan

Un detector de humo mal calibrado suena igual de fuerte si hay un incendio real o si alguien tostó pan un poco de más. Las primeras veces, la gente de la casa reacciona con la misma urgencia a las dos cosas. Pero después de la quinta vez que la alarma suena por el pan tostado, algo cambia: la gente empieza a ignorarla por default, "seguro es el pan otra vez" — y esa costumbre es exactamente la que hace que, el día que sí hay un incendio real, nadie reaccione a tiempo. El problema nunca fue que el detector sonara demasiado fuerte. Fue que sonaba por cosas que no ameritaban esa urgencia, hasta que la urgencia dejó de significar algo.

Una alerta de "CPU al 85%" sin ninguna conexión con el impacto real en el usuario es ese detector mal calibrado. Puede sonar constantemente sin que ningún comprador de Mercado esté, de hecho, teniendo una mala experiencia — y cada vez que suena así, sin consecuencia real, entrena al equipo a tratarla como ruido. Una alerta de "checkoutLatencyP95Ms cruzó el techo del guardrail" es distinta: cuando suena, significa, con precisión, que un guardrail que protege la experiencia real de un comprador se rompió.

Ejemplo trabajado: alertQuality() sobre dos alertas para el mismo problema

Rob Ewaschuk, ingeniero de Google, resumió esta idea en un ensayo muy citado sobre alerting: cada alerta debería ser accionable, ligada a un síntoma real que le importa al usuario, no a una causa técnica interna que podría o no tener consecuencias. Vamos a construir una heurística simple que aplica ese criterio a dos alertas que, en teoría, podrían avisar del mismo problema de latencia.

// alertQuality: heuristica pedagogica inspirada en la filosofia de alerting
// de Rob Ewaschuk -- una alerta "buena" liga un sintoma a un IMPACTO de
// usuario medible (un guardrail), tiene un umbral explicito, y apunta a un
// runbook con la accion a tomar. No reemplaza un sistema real de alerting;
// es un modelo simplificado para razonar sobre que hace buena a una alerta.
function alertQuality(alert) {
  const hasUserImpact = alert.linksToGuardrail === true;
  const hasThreshold = typeof alert.threshold === 'number';
  const hasRunbook = alert.runbookUrl != null;
  const score = [hasUserImpact, hasThreshold, hasRunbook].filter(Boolean).length;
  return { name: alert.name, hasUserImpact, hasThreshold, hasRunbook, score, verdict: score === 3 ? 'actionable' : 'revisar antes de activarla' };
}

const alerts = [
  { name: 'CPU del servidor de checkout > 85%', linksToGuardrail: false, threshold: 85, runbookUrl: null },
  { name: 'checkoutLatencyP95Ms cruza el techo del guardrail (800ms)', linksToGuardrail: true, threshold: 800, runbookUrl: 'runbooks/latency-guardrail' },
];

console.log('=== alertQuality: dos alertas para el mismo problema, evaluadas ===\n');
alerts.forEach((a) => {
  const r = alertQuality(a);
  console.log(r.name);
  console.log('  liga a impacto de usuario=' + r.hasUserImpact + '  tiene umbral=' + r.hasThreshold + '  tiene runbook=' + r.hasRunbook);
  console.log('  veredicto: ' + r.verdict + '\n');
});

Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:

=== alertQuality: dos alertas para el mismo problema, evaluadas ===

CPU del servidor de checkout > 85%
  liga a impacto de usuario=false  tiene umbral=true  tiene runbook=false
  veredicto: revisar antes de activarla

checkoutLatencyP95Ms cruza el techo del guardrail (800ms)
  liga a impacto de usuario=true  tiene umbral=true  tiene runbook=true
  veredicto: actionable

Las dos alertas, en teoría, podrían apuntar al mismo problema de fondo —el motor de recomendaciones sobrecargando el servidor de checkout—. Pero solo una pasa el criterio completo. La alerta de CPU tiene un umbral (85%), pero no dice nada sobre si algún comprador está siendo afectado —un servidor puede estar al 85% de CPU y servir cada request en 200ms sin problema, o puede estar al 40% y ya estar generando timeouts, según qué más esté pasando ese día— y no apunta a ningún runbook: si suena a las tres de la mañana, quien la recibe tiene que investigar desde cero qué hacer. La alerta de checkoutLatencyP95Ms liga directamente al guardrail que ya se definió, con el mismo techo de 800ms que guardrailWatch() usa para decidir HALT, y apunta a un runbook concreto — quien la recibe sabe, de inmediato, que un comprador real está teniendo una experiencia más lenta de la aceptada, y qué hacer al respecto.

Profundización: la alerta y el guardrail son la misma línea, en dos formas

Vale la pena notar algo que el ejemplo deja implícito: la alerta "buena" de este ejercicio no inventa un umbral nuevo — usa exactamente el mismo, 800ms, que ya vive en el arreglo de guardrails que guardrailWatch() evalúa en cada etapa. Eso no es casualidad, es el diseño correcto: una alerta y un guardrail no son dos sistemas separados que hay que mantener sincronizados a mano — son la misma línea de decisión, expresada dos veces: una como código que corre en cada etapa del rollout (guardrailWatch()), y otra como notificación que le avisa a un humano cuando esa línea se cruza. Si el umbral de la alerta y el umbral del guardrail llegaran a desincronizarse —por ejemplo, si alguien cambia el techo del guardrail a 900ms pero olvida actualizar la alerta que sigue en 800ms—, terminarías con una alerta que suena en un momento que ya no corresponde a una decisión real de frenar, exactamente el tipo de ruido que rompe la confianza en el sistema completo.

Errores comunes

Alertar sobre el síntoma técnico sin ligarlo al impacto de usuario. Qué pasa: el equipo de infraestructura configura alertas sobre CPU, memoria, y latencia de red interna del servicio de recomendaciones —números reales y útiles para diagnosticar— pero ninguna de esas alertas dice, en su mensaje, qué le está pasando al comprador que está en checkout en ese momento. Por qué pasa: los números de infraestructura son los que el equipo técnico mira todos los días, y es natural alertar sobre lo que ya se está observando, sin dar el paso extra de conectar ese número con la experiencia real del usuario. Cómo detectarlo: si una alerta suena y la primera pregunta que alguien hace es "¿esto le está afectando a algún comprador ahora mismo?", y nadie puede contestar sin investigar, la alerta avisó del síntoma, no del impacto. Cómo corregirlo: como en el ejemplo de esta lección, cada alerta debería poder contestar, en su propio mensaje, la pregunta "¿a quién le está pasando esto, y qué tan grave es" — no solo "este número cruzó una línea".

Configurar tantas alertas que la fatiga de alertas apaga la atención de todas, incluida la que sí importa. Qué pasa: cada guardrail, cada golden signal, y cada métrica secundaria termina con su propia alerta, hasta que el canal de notificaciones del equipo recibe decenas de mensajes por día, la mayoría de baja severidad. Por qué pasa: cada alerta individual parece razonable de agregar en el momento en que se configura, y nadie hace, después, la limpieza de preguntarse cuáles de verdad ameritan interrumpir a alguien. Cómo detectarlo: si el equipo tiene la costumbre de revisar el canal de alertas "cuando hay tiempo" en vez de reaccionar a cada una, la fatiga ya se instaló. Cómo corregirlo: aplica el mismo criterio de alertQuality() a cada alerta existente —¿liga a un guardrail real, tiene umbral, tiene runbook?— y elimina o degrada (a un dashboard, no una alerta activa) las que no pasan las tres pruebas.

Confundir un blip transitorio con una regresión real. Qué pasa: la latencia sube momentáneamente por una razón externa —un pico de tráfico de un evento de marketing, un deploy no relacionado que reinició el servidor— y alguien interpreta ese pico único como si fuera la misma regresión sostenida que guardrailWatch() detectó en la etapa de 10%. Por qué pasa: un número que cruza el umbral se ve igual de alarmante sin importar si es un evento aislado de un minuto o una degradación sostenida durante toda la etapa — la alerta, en su forma más simple, no distingue entre las dos cosas. Cómo detectarlo: si la latencia vuelve a la normalidad sola, en minutos, sin ningún cambio de código ni de configuración, probablemente fue un blip, no una regresión causada por el rollout. Cómo corregirlo: guardrailWatch(), tal como está construido en este módulo, evalúa la métrica medida en la etapa —un valor ya agregado (p95), no un pico instantáneo—, precisamente para no reaccionar a ruido de un solo momento. Una alerta bien diseñada debería exigir, de forma similar, que la condición se sostenga por una ventana de tiempo razonable antes de dispararse — no un solo dato fuera de rango.

Ejercicios

Ejercicio 1 — Evalúa una tercera alerta. El equipo propone una alerta nueva: "complaintRate sube" — sin especificar de cuánto a cuánto, sin ligarla explícitamente a ningún guardrail configurado, y sin runbook todavía. Usando alertQuality(), ¿qué campos saldrían en false, y cuál sería el veredicto?

Ver solución

Con linksToGuardrail: false (no está ligada explícitamente al guardrail de complaintRate que ya existe, con su maxIncrease de 0.005), threshold: undefined (no especifica de cuánto a cuánto), y runbookUrl: null (no tiene runbook), los tres campos —hasUserImpact, hasThreshold, hasRunbook— saldrían en false, con score: 0 y veredicto 'revisar antes de activarla'. Esta alerta necesita, como mínimo, un umbral explícito (el mismo maxIncrease: 0.005 del guardrail ya definido) y un runbook antes de considerarse lista.

Ejercicio 2 — Diseña la alerta correcta para complaintRate. Usando el mismo guardrail que ya conoces ({ metric: 'complaintRate', type: 'maxIncrease', limit: 0.005 }), escribe el objeto de alerta que sí pasaría las tres pruebas de alertQuality().

Ver solución
const complaintAlert = {
  name: 'complaintRate sube mas de 0.5pp respecto a la linea base',
  linksToGuardrail: true,
  threshold: 0.005,
  runbookUrl: 'runbooks/complaint-rate-guardrail',
};

Con linksToGuardrail: true, threshold: 0.005 (el mismo limit del guardrail real, sin inventar un número nuevo), y un runbookUrl definido, alertQuality() devolvería score: 3 y verdict: 'actionable' — la misma disciplina que ya viste con la alerta de latencia, aplicada ahora al guardrail de quejas.

Ejercicio 3 — Explica la fatiga de alertas a un compañero nuevo. En 2-3 frases, sin usar la palabra "fatiga", explica por qué tener demasiadas alertas de baja calidad es, en la práctica, casi tan peligroso como no tener ninguna alerta.

Ver solución

Una respuesta razonable: "Si configuramos una alerta por cada número que se nos ocurra, la mayoría del tiempo esas alertas van a sonar por cosas sin consecuencia real, y con el tiempo el equipo aprende, sin decidirlo conscientemente, a no tomarlas en serio. El problema es que ese aprendizaje no distingue entre las alertas ruidosas y la única que sí importa — el día que suene la alerta de latencia por un HALT real, corre el riesgo de recibir la misma reacción de 'seguro no es nada' que las demás. Por eso vale más tener pocas alertas, bien ligadas a un guardrail real, que muchas alertas de todo lo que técnicamente se puede medir."

Resumen y siguiente paso

En esta lección construiste alertQuality(), una heurística de tres preguntas —¿liga a un impacto real de usuario? ¿tiene un umbral explícito? ¿apunta a un runbook?— inspirada en la filosofía de alerting de Rob Ewaschuk, y la aplicaste a dos alertas para el mismo problema técnico: una de CPU, que falló las tres pruebas, y una ligada al guardrail de latencia, que las pasó todas. Viste que una alerta bien diseñada no inventa un umbral propio — reusa el mismo umbral que ya vive en el guardrail que guardrailWatch() evalúa, para que alerta y vigilancia nunca se desincronicen. Y nombraste el costo de las alertas de baja calidad: entrenan al equipo a ignorar el canal completo, justo antes de que la alerta que sí importa necesite atención inmediata.

Antes de avanzar deberías poder: distinguir una alerta que avisa un síntoma técnico de una que avisa un impacto real de usuario; aplicar las tres preguntas de alertQuality() a cualquier alerta nueva antes de activarla; y explicar por qué demasiadas alertas de baja calidad son casi tan peligrosas como no tener ninguna.

La lección 7 cierra el arco temático del módulo con una pregunta distinta: no "¿cuándo suena la alarma?", sino "¿cuánta regresión te queda antes de que suene?" — el error budget, el presupuesto explícito de cuánto puedes gastar en latencia extra antes de que se agote.

Recursos

  • Rob Ewaschuk, "My Philosophy on Alerting" — docs.google.com/document/d/199PqyG3UsyXlwieHaqbGiWVa8eMWi8zzAn0YfcApr8Q. El ensayo original, muy citado por el propio Google SRE Book, sobre qué hace que una alerta sea accionable en vez de ruido — la base directa de alertQuality() en esta lección. En inglés.
  • Google SRE Book, Capítulo 6, "Monitoring Distributed Systems" — sre.google/sre-book/monitoring-distributed-systems. Cita explícitamente el trabajo de Ewaschuk y desarrolla el principio de que las alertas deben corresponder a síntomas con impacto real, no a causas internas sueltas. En inglés.
  • LaunchDarkly, "Introducing Guardrail Metrics: best-practice metrics for every release" — launchdarkly.com/blog/introducing-guardrail-metrics. Describe cómo un sistema real de guarded rollouts notifica automáticamente al detectar una regresión ligada a un guardrail, en vez de alertar sobre métricas de infraestructura sueltas. En inglés.