Módulo 4: Monitoring The Launch

El dashboard de lanzamiento: qué mostrar, y qué no

Descripción

guardrailWatch() ya calcula todo lo que hace falta saber sobre el estado de la rampa. Pero nadie —ni el equipo de recommendations, ni el gerente que pregunta "¿cómo va el rollout?" a las diez de la mañana— quiere correr un archivo de Node cada vez que necesita esa respuesta. Esta lección diseña el dashboard: la superficie visual que hace visible, de un vistazo, lo que la función ya calculó. Y advierte sobre el error contrario al de no vigilar nada: un dashboard con tantas gráficas que nadie puede decir, mirándolo, si hay que actuar o no.

Conexión con el módulo. Esta lección no agrega ninguna decisión nueva —eso ya lo resolvieron guardrailWatch() (lección 3) y el criterio de halt (lección 4)—; construye dashboardSummary(), una función pequeña que reutiliza el resultado de guardrailWatch() para producir la cabecera de estado que cualquier dashboard de lanzamiento necesita mostrar primero, antes que cualquier otra gráfica.

Una analogía: la cabina del piloto, no la sala de control completa

La cabina de un avión tiene, frente al piloto, un puñado de indicadores realmente críticos —altitud, velocidad, combustible, alarmas activas— dispuestos para leerse en un segundo, sin tener que buscar. Toda la telemetría detallada del avión —cientos de sensores, cada válvula, cada temperatura de cada componente— existe y se registra, pero vive en otro lugar: en los sistemas de mantenimiento, no frente a los ojos del piloto en pleno vuelo. Un piloto que tuviera que revisar los cientos de sensores uno por uno para saber si el vuelo va bien perdería, precisamente, la capacidad de reaccionar rápido cuando algo sí importa.

El dashboard de lanzamiento de recommendations necesita ser la cabina, no la sala de mantenimiento completa. La cabecera —¿en qué etapa está la rampa, está avanzando o detenida, qué guardrail rompió si alguno lo hizo— es lo que cualquiera necesita ver en el primer segundo. El detalle completo de cada métrica, con sus series de tiempo y su desglose, existe y es útil — pero un nivel más abajo, no compitiendo por atención con la pregunta más urgente.

Ejemplo trabajado: dashboardSummary() sobre el estado del rollout

// guardrailCheck y guardrailWatch: EXACTAMENTE las mismas de la leccion 3.
function guardrailCheck(before, after, guardrails) {
  const results = guardrails.map((g) => {
    const beforeVal = before[g.metric];
    const afterVal = after[g.metric];
    let broken = false;
    let detail = '';
    if (g.type === 'ceiling') {
      broken = afterVal > g.limit;
      detail = afterVal + ' vs techo ' + g.limit;
    } else if (g.type === 'floor') {
      broken = afterVal < g.limit;
      detail = afterVal + ' vs piso ' + g.limit;
    } else if (g.type === 'maxIncrease') {
      const delta = afterVal - beforeVal;
      broken = delta > g.limit;
      detail = 'delta +' + delta.toFixed(4) + ' vs maximo permitido +' + g.limit;
    }
    return { metric: g.metric, before: beforeVal, after: afterVal, broken, detail };
  });
  const brokenGuardrails = results.filter((r) => r.broken).map((r) => r.metric);
  return { results, anyBroken: brokenGuardrails.length > 0, brokenGuardrails };
}

function guardrailWatch(stages, guardrails, baseline) {
  const log = [];
  let halted = false;
  for (const stage of stages) {
    if (halted) {
      log.push({ stage: stage.name, percent: stage.percent, status: 'not reached (ramp halted earlier)' });
      continue;
    }
    if (!stage.metrics) {
      log.push({ stage: stage.name, percent: stage.percent, status: 'not measured yet' });
      continue;
    }
    const check = guardrailCheck(baseline, stage.metrics, guardrails);
    if (check.anyBroken) {
      log.push({ stage: stage.name, percent: stage.percent, status: 'HALT', broken: check.brokenGuardrails });
      halted = true;
    } else {
      log.push({ stage: stage.name, percent: stage.percent, status: 'continue' });
    }
  }
  return { log, halted };
}

// dashboardSummary: NUEVA de esta leccion. Toma el log ya calculado por
// guardrailWatch() y arma la cabecera de estado que un dashboard de
// lanzamiento deberia mostrar arriba de todo: en que etapa esta la rampa
// AHORA MISMO, y si esta avanzando o detenida.
function dashboardSummary(watchResult) {
  const evaluated = watchResult.log.filter((e) => e.status === 'continue' || e.status === 'HALT');
  const current = evaluated[evaluated.length - 1];
  return {
    currentStage: current.stage,
    currentPercent: current.percent,
    banner: watchResult.halted ? 'HALTED' : 'IN PROGRESS',
    stagesEvaluated: evaluated.length,
  };
}

const baseline = { checkoutLatencyP95Ms: 650, complaintRate: 0.012, churnRate: 0.045, grossMargin: 0.220 };
const guardrails = [
  { metric: 'checkoutLatencyP95Ms', type: 'ceiling', limit: 800 },
  { metric: 'complaintRate', type: 'maxIncrease', limit: 0.005 },
  { metric: 'churnRate', type: 'maxIncrease', limit: 0.010 },
  { metric: 'grossMargin', type: 'floor', limit: 0.180 },
];
const rampStages = [
  { name: 'canary', percent: 1, metrics: { checkoutLatencyP95Ms: 680, complaintRate: 0.012, churnRate: 0.045, grossMargin: 0.220 } },
  { name: 'ramp-10', percent: 10, metrics: { checkoutLatencyP95Ms: 910, complaintRate: 0.013, churnRate: 0.045, grossMargin: 0.219 } },
  { name: 'ramp-50', percent: 50, metrics: null },
  { name: 'full', percent: 100, metrics: null },
];

const watch = guardrailWatch(rampStages, guardrails, baseline);
const summary = dashboardSummary(watch);
console.log('=== dashboardSummary: la cabecera del dashboard de lanzamiento ===\n');
console.log(summary);

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

=== dashboardSummary: la cabecera del dashboard de lanzamiento ===

{
  currentStage: 'ramp-10',
  currentPercent: 10,
  banner: 'HALTED',
  stagesEvaluated: 2
}

Fíjate en que dashboardSummary() no recalcula nada de lo que ya hizo guardrailWatch() — solo lee su resultado y lo resume en cuatro campos, los cuatro que alguien necesita para contestar, en un segundo, "¿cómo va el rollout de recommendations?": está en la etapa ramp-10 (10% de la base), el estado es HALTED, y se alcanzaron a evaluar 2 de las 4 etapas planeadas. Esa cabecera, convertida en un mockup visual, se vería así:

┌─────────────────────────────────────────────────────────────┐
│  recommendations — rollout                    [ HALTED ]    │
│  Etapa actual: ramp-10 (10%)     Etapas evaluadas: 2 de 4    │
│  Guardrail roto: checkoutLatencyP95Ms (910ms > techo 800ms)  │
├─────────────────────────────────────────────────────────────┤
│  checkoutLatencyP95Ms   650 → 910 ms   ROTO   ceiling 800    │
│  complaintRate          1.2% → 1.3%    OK     max +0.5pp     │
│  churnRate               4.5% → 4.5%   OK     max +1.0pp     │
│  grossMargin            22.0% → 21.9%  OK     floor 18.0%    │
├─────────────────────────────────────────────────────────────┤
│  checkoutConversionRate (referencia, NO decide el halt)      │
│  3.2% → 3.8%   lift +18.75%   p=0.0114 (significativo)       │
└─────────────────────────────────────────────────────────────┘

Las tres capas de un dashboard de lanzamiento

Este mockup tiene, a propósito, tres bloques con roles distintos, y esa separación es el diseño que importa más que cualquier detalle visual:

  1. La cabecera (lo que calcula dashboardSummary()): un banner de una sola palabra —IN PROGRESS o HALTED—, la etapa actual, y cuántas etapas se alcanzaron a evaluar. Es lo primero que cualquiera lee, y lo único que hace falta leer si la respuesta es simplemente "¿vamos bien o no?".
  2. Los guardrails, uno por fila, con su valor antes/después, su estado, y su umbral — el detalle que explica por qué el banner dice lo que dice. Esto es, literalmente, el results que guardrailCheck() ya calculaba desde la guía de métricas, mostrado como tabla.
  3. La métrica primaria, como referencia, marcada explícitamente como que NO decide el halt. Esta tercera capa es la más fácil de omitir, y la lección 4 ya explicó por qué es peligroso omitirla: si el dashboard no deja clarísimo que checkoutConversionRate es informativa pero no decide nada sobre frenar, alguien va a intentar usarla para "compensar" un guardrail roto, exactamente el error que ya nombraste.

Errores comunes

Dashboards con veinte gráficas y ninguna accionable. Qué pasa: el equipo agrega, con el tiempo, cada métrica que alguna vez le pareció interesante a alguien —latencia por región, latencia por tipo de dispositivo, tiempo de renderizado del carrusel específicamente, tasa de clics en cada posición del carrusel— hasta que el dashboard tiene veinte paneles, y nadie puede decir, en cinco segundos, si hay que actuar. Por qué pasa: cada métrica individual parece útil de agregar, y ninguna se siente lo bastante prescindible como para quitarla — el dashboard crece por acumulación, nunca por diseño. Cómo detectarlo: si alguien nuevo en el equipo no puede contestar "¿está bien el rollout ahora mismo?" mirando el dashboard por menos de diez segundos, hay demasiado ruido compitiendo con la señal. Cómo corregirlo: la cabecera de tres capas de esta lección es, a propósito, mínima — un banner, cuatro guardrails, una referencia. Cualquier gráfica adicional puede existir, pero en una vista secundaria, no compitiendo con la pregunta de si hay que frenar.

Mostrar el estado de los guardrails sin la etapa a la que corresponde. Qué pasa: el dashboard muestra "checkoutLatencyP95Ms: 910ms, ROTO" sin decir en qué etapa del rollout se midió ese número — 10%, 50%, no queda claro. Por qué pasa: una vez que el número está roto, parece que el detalle de la etapa es secundario frente a la urgencia del problema. Cómo detectarlo: si alguien pregunta "¿eso fue en canary o ya llegamos a la mitad de la base?" y el dashboard no tiene esa respuesta visible, falta contexto crítico para entender el radio de impacto real. Cómo corregirlo: como en el mockup de esta lección, la etapa (ramp-10, 10%) va en la cabecera, no enterrada en una nota al pie — es la diferencia entre "esto afectó a 125 personas" y "esto afectó a 25,000".

Diseñar el dashboard después del primer incidente, no antes del primer lanzamiento. Qué pasa: el equipo lanza recommendations sin un dashboard preparado, confiando en revisar los números "a mano" con consultas sueltas, y recién arma el dashboard completo después de que el HALT en 10% ya los tomó por sorpresa. Por qué pasa: construir el dashboard se siente como trabajo de infraestructura que puede esperar, comparado con avanzar el rollout mismo. Cómo detectarlo: si alguien pregunta "¿dónde veo el estado del rollout ahora mismo?" y la respuesta es "corre este script", el dashboard no existe todavía, solo la lógica que debería alimentarlo. Cómo corregirlo: el dashboard —incluso en su versión mínima de tres capas— se construye antes de que arranque la primera etapa del rollout, con la misma disciplina con la que se definen los umbrales de los guardrails antes de tener datos que los rompan.

Ejercicios

Ejercicio 1 — Corre dashboardSummary() sobre el escenario arreglado. Usando los datos del escenario hipotético de la lección 4 (donde la rampa llega completa a 100% sin HALT), ¿qué valores esperarías para currentStage, currentPercent, banner y stagesEvaluated?

Ver solución

currentStage: 'full', currentPercent: 100, banner: 'IN PROGRESS', stagesEvaluated: 4. Como ninguna etapa rompió un guardrail, watchResult.halted queda en false, así que el banner nunca cambia a 'HALTED' — y como las cuatro etapas tenían métricas y se evaluaron todas en orden (continue en cada una), evaluated incluye las cuatro, y la última —full, en 100%— es la que aparece como etapa actual.

Ejercicio 2 — Rediseña el mockup para una alerta de churn. Si, en una etapa más avanzada del rollout, el guardrail que se rompiera fuera churnRate en vez de checkoutLatencyP95Ms, ¿qué cambiaría en la cabecera del dashboard (la línea de "Guardrail roto")? Escribe esa línea.

Ver solución

La línea cambiaría a algo como: Guardrail roto: churnRate (delta +0.0120 > máximo permitido +0.0100) — usando el formato maxIncrease en vez de ceiling, porque churnRate en este conjunto de guardrails se define como un incremento máximo permitido respecto a la línea base, no como un techo absoluto. El resto de la cabecera —banner HALTED, etapa actual, etapas evaluadas— seguiría exactamente igual en estructura; solo cambia cuál guardrail aparece nombrado y con qué tipo de detalle, porque dashboardSummary() no distingue entre guardrails, solo reporta el estado agregado que ya calculó guardrailWatch().

Ejercicio 3 — Defiende la tercera capa ante un compañero. Un compañero de equipo propone quitar la sección de checkoutConversionRate del dashboard "para simplificarlo, ya que no decide nada sobre el halt". En 2-3 frases, explica por qué esa sección debería quedarse, aunque no decida la acción.

Ver solución

Una respuesta razonable: "Si quitamos checkoutConversionRate del dashboard, alguien que solo ve la cabecera y los guardrails va a pensar que estamos frenando un feature que no tiene ningún beneficio — sin saber que, al mismo tiempo, está ganando 18.75% en conversión. Esa información es la que explica por qué esto es una decisión difícil y no una obvia, y por qué vale la pena diagnosticar el guardrail en vez de simplemente descartar el feature. La marcamos como 'referencia, no decide el halt' precisamente para que sea informativa sin volver a abrir la discusión de si el guardrail roto 'se compensa' con ella."

Resumen y siguiente paso

En esta lección construiste dashboardSummary(), que toma el resultado ya calculado de guardrailWatch() y lo convierte en la cabecera de estado —etapa actual, banner, etapas evaluadas— que cualquier dashboard de lanzamiento necesita mostrar primero. Diseñaste las tres capas de un dashboard completo: cabecera, guardrails con su detalle, y la métrica primaria como referencia explícitamente marcada como que no decide el halt. Y viste los dos extremos a evitar: un dashboard con tanto ruido que nadie encuentra la señal, y uno construido demasiado tarde, después del primer incidente.

Antes de avanzar deberías poder: explicar las tres capas de un dashboard de lanzamiento y qué pregunta contesta cada una; identificar cuándo un dashboard tiene demasiadas gráficas para ser accionable; y argumentar por qué la métrica primaria debe seguir visible, aunque no participe en la decisión de frenar.

La lección 6 cambia de la vista pasiva —el dashboard que alguien tiene que abrir para revisar— a la vista activa: las alertas, que avisan sin que nadie tenga que estar mirando. Y ahí aparece un problema nuevo: no toda alerta que suena es útil, y algunas avisan un síntoma técnico sin decir nada sobre si a algún usuario le está pasando algo.

Recursos

  • Google SRE Book, Capítulo 6, "Monitoring Distributed Systems" — sre.google/sre-book/monitoring-distributed-systems. La sección sobre las cuatro capas de un buen sistema de monitoreo —alertas, tickets, logging, dashboards— cada una con un propósito distinto, la misma separación de capas que argumenta esta lección. En inglés.
  • LaunchDarkly, "Introducing Guardrail Metrics: best-practice metrics for every release" — launchdarkly.com/blog/introducing-guardrail-metrics. Muestra cómo una plataforma real agrupa los guardrails de un release en una sola vista consolidada, el mismo objetivo de la cabecera de tres capas de esta lección. En inglés.
  • Google SRE Book, Capítulo 4, "Service Level Objectives" — sre.google/sre-book/service-level-objectives. Argumenta por qué un dashboard debería organizarse alrededor de los SLO que importan, no de cada métrica disponible — la razón de fondo detrás de "menos gráficas, más accionables". En inglés.