Módulo 4: Monitoring The Launch
Error budget: cuánto te queda antes de tener que parar
Descripción
Hasta ahora, este módulo trató cada guardrail como una línea binaria: OK o ROTO. Esta lección introduce una forma más fina de leer el mismo dato: no solo si el guardrail se rompió, sino cuánto margen quedaba antes de que se rompiera, y qué fracción de ese margen ya se gastó en cada etapa. Ese margen tiene un nombre en la práctica de SRE: error budget — el presupuesto de regresión que un equipo se permite antes de tener que detenerse, sin importar qué tan bien vaya el resto del sistema.
Conexión con el módulo. El error budget no reemplaza a guardrailWatch() ni a la decisión de HALT de las lecciones 3 y 4 — les agrega una lectura complementaria, en unidades concretas (milisegundos, en el caso de la latencia), de qué tan cerca o lejos estaba cada etapa del límite. Un guardrail que pasa OK con mucho margen y uno que pasa OK casi agotado son, técnicamente, el mismo veredicto para guardrailWatch() — pero cuentan una historia muy distinta sobre qué tan cerca está la próxima etapa de romperse.
Una analogía: el saldo de la tarjeta de crédito
Una tarjeta de crédito tiene un límite. Gastar dentro de ese límite está permitido —comprar algo hoy, otra cosa la semana que viene—, y cada compra reduce el saldo disponible, aunque ninguna compra individual, por sí sola, sea un problema. El problema aparece cuando las compras acumuladas se acercan al límite: en algún punto, el saldo disponible ya no alcanza para la próxima compra, y no importa qué tan razonable sea esa compra en sí misma — el límite ya se definió, de antemano, y no negocia según qué tan buena sea la excusa del momento.
El presupuesto de regresión de checkoutLatencyP95Ms funciona igual. La línea base (650ms) es el saldo en cero — nada gastado todavía. El techo (800ms) es el límite de la tarjeta. Cada etapa del rollout que sube la latencia un poco es una compra: gasta parte del presupuesto, sin que esa etapa individual, por sí sola, tenga que ser un desastre. El problema aparece cuando el gasto acumulado se acerca, o cruza, el límite — y en ese momento, no importa qué tan bien esté yendo la conversión: el presupuesto ya se agotó.
Ejemplo trabajado: errorBudgetTracker() sobre la latencia de recommendations
// errorBudgetTracker: modelo pedagogico de error budget para UNA metrica de
// guardrail tipo "ceiling" (aqui, la latencia). El presupuesto es la
// distancia entre la linea base y el techo -- cuanta regresion se puede
// permitir antes de romper el guardrail. Cada etapa "gasta" parte de ese
// presupuesto. Es un modelo simplificado de la idea de error budget de SRE,
// no el calculo formal de SLO/SLI que usa Google internamente.
function errorBudgetTracker(baselineValue, ceiling, stages) {
const totalBudgetMs = ceiling - baselineValue;
return stages.map((s) => {
const consumedMs = s.value - baselineValue;
const remainingMs = totalBudgetMs - consumedMs;
const pctConsumed = (consumedMs / totalBudgetMs) * 100;
return {
stage: s.name,
percent: s.percent,
value: s.value,
consumedMs,
remainingMs,
pctConsumed,
status: remainingMs < 0 ? 'EXHAUSTED' : 'within budget',
};
});
}
const totalBudgetMs = 800 - 650; // techo 800, linea base 650
console.log('=== error budget de latencia: presupuesto total = ' + totalBudgetMs + 'ms de regresion ===\n');
// Las mismas dos etapas medidas del rollout real (leccion 3): canary y 10%.
const latencyStages = [
{ name: 'canary', percent: 1, value: 680 },
{ name: 'ramp-10', percent: 10, value: 910 },
];
const budget = errorBudgetTracker(650, 800, latencyStages);
budget.forEach((b) => {
console.log(b.stage + ' (' + b.percent + '%): p95=' + b.value + 'ms | consumido=' + b.consumedMs + 'ms (' +
b.pctConsumed.toFixed(1) + '% del budget) | restante=' + b.remainingMs + 'ms | ' + b.status);
});
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== error budget de latencia: presupuesto total = 150ms de regresion ===
canary (1%): p95=680ms | consumido=30ms (20.0% del budget) | restante=120ms | within budget
ramp-10 (10%): p95=910ms | consumido=260ms (173.3% del budget) | restante=-110ms | EXHAUSTED
El presupuesto total es de 150ms —la distancia entre la línea base de 650ms y el techo de 800ms—, y cada etapa lo gasta a un ritmo muy distinto. En canary, la latencia sube a 680ms: eso consume solo 30ms del presupuesto, un 20% del total, dejando 120ms de margen todavía disponible — un gasto modesto, dentro de lo razonable. En ramp-10, la latencia salta a 910ms: eso consume 260ms, un 173.3% del presupuesto original — más del doble de lo que había disponible—, dejando un saldo de -110ms, negativo. El estado pasa de within budget a EXHAUSTED exactamente en la misma etapa donde guardrailWatch(), en la lección 3, marcó HALT. No es coincidencia: son la misma línea de decisión, leída con dos instrumentos distintos — uno binario (ROTO/OK), uno de saldo (consumedMs/remainingMs).
Profundización: por qué el número negativo importa más que el "ROTO"
guardrailWatch() te dice que la etapa de 10% rompió el guardrail. errorBudgetTracker() te dice, además, por cuánto: no fue un exceso mínimo de un par de milisegundos, fue un salto de 260ms contra un presupuesto de apenas 150 — el gasto real fue más del triple de lo que el saldo permitía en un solo paso, entre canary y la siguiente etapa. Esa magnitud cambia la conversación del diagnóstico: un guardrail roto por 5ms sugiere ajustar algo pequeño; un guardrail roto por 110ms de saldo negativo, en un solo salto de etapa, sugiere que la causa técnica —la llamada bloqueante al motor de recomendaciones, según ya identificó la guía de métricas— necesita un rediseño, no un ajuste fino. El número del error budget no cambia la decisión de frenar —ya la había tomado guardrailWatch()—, pero sí cambia la urgencia y el tipo de arreglo que hace falta antes de reintentar la rampa.
Errores comunes
Tratar el error budget como un permiso para gastarlo completo a propósito. Qué pasa: alguien lee "todavía queda 120ms de presupuesto en canary" y lo interpreta como una autorización para subir la latencia deliberadamente hasta acercarse al límite, en vez de tratarlo como un margen de seguridad ante lo inesperado. Por qué pasa: un presupuesto suena, por definición, a algo que está ahí para gastarse — y es fácil olvidar que la meta no es maximizar el gasto, es minimizar el riesgo mientras se captura el beneficio del feature. Cómo detectarlo: si alguien argumenta "todavía tenemos margen, podemos permitirnos que suba un poco más" como justificación para no optimizar algo que sí se podría optimizar, está tratando el presupuesto como objetivo en vez de como límite de seguridad. Cómo corregirlo: el error budget mide cuánto margen queda, no cuánto conviene gastar — la meta técnica sigue siendo la menor regresión posible, y el presupuesto solo existe para saber cuándo detenerse si, a pesar de ese esfuerzo, la regresión aparece de todos modos.
Calcular el presupuesto después de que ya se agotó, y ajustarlo para que "cierre". Qué pasa: al ver que el consumo llegó a 173.3% del presupuesto original, alguien propone subir el techo de 800ms a, digamos, 950ms, "para que el número deje de verse tan mal". Por qué pasa: un presupuesto agotado y en números negativos se siente incómodo de reportar, y ajustar el límite después del hecho hace que el reporte se vea mejor sin haber cambiado nada real. Cómo detectarlo: si el techo de un guardrail cambia justo después de que ese guardrail se rompió, y sin ningún argumento técnico nuevo de por medio, el ajuste es cosmético, no real. Cómo corregirlo: el mismo error que ya nombró la guía de métricas sobre definir umbrales después de ver los datos — el techo de 800ms se fijó antes del lanzamiento, y cambiarlo ahora, sin una razón técnica nueva, borra la señal que el presupuesto estaba dando.
Aplicar el mismo presupuesto a guardrails que no son de tipo ceiling. Qué pasa: alguien intenta calcular un "presupuesto" para grossMargin (un guardrail de tipo floor) usando la misma resta techo - línea base que funciona para la latencia, sin notar que grossMargin no tiene un techo — tiene un piso que no se debe perforar. Por qué pasa: el modelo de presupuesto de esta lección se construyó específicamente para un guardrail ceiling, y es tentador aplicar la misma fórmula a cualquier guardrail sin revisar si el tipo coincide. Cómo detectarlo: si el "presupuesto" calculado para un guardrail de tipo floor o maxIncrease da un número que no tiene sentido intuitivo (por ejemplo, negativo desde el principio, sin que nada se haya roto todavía), la fórmula no aplica tal cual. Cómo corregirlo: errorBudgetTracker(), tal como está escrito en esta lección, es un modelo para guardrails ceiling específicamente; un presupuesto equivalente para un guardrail floor restaría en la dirección contraria (valor - piso, en vez de techo - valor), y uno para maxIncrease ya está, de hecho, expresado directamente en esas unidades desde guardrailCheck().
Ejercicios
Ejercicio 1 — Calcula el presupuesto con el techo del ejercicio 1 de la lección 3. Si el techo de checkoutLatencyP95Ms fuera 950ms en vez de 800ms (con la misma línea base de 650ms), ¿cuál sería el presupuesto total, y qué porcentaje del presupuesto consumiría la etapa de ramp-10 (910ms)?
Ver solución
El presupuesto total sería 950 - 650 = 300ms. El consumo en ramp-10 seguiría siendo 910 - 650 = 260ms (el consumo depende del valor medido y la línea base, no del techo). El porcentaje consumido sería 260 / 300 * 100 = 86.7% — todavía dentro del presupuesto (remainingMs: 40, positivo), a diferencia del escenario original donde el mismo consumo excedía el presupuesto de 150ms. Este ejercicio confirma, con números, lo que ya viste en la lección 3: con un techo de 950ms, esta etapa habría pasado OK — y ahora puedes ver, además, que habría pasado con un margen ajustado (86.7% consumido), no con margen amplio.
Ejercicio 2 — Agrega la etapa ramp-5 del ejercicio 2 de la lección 3. Con checkoutLatencyP95Ms: 790 en una etapa intermedia entre canary y 10% (presupuesto original de 150ms, techo 800), ¿cuánto presupuesto consumiría esa etapa, y cuánto quedaría?
Ver solución
consumedMs = 790 - 650 = 140ms. remainingMs = 150 - 140 = 10ms. pctConsumed = 140 / 150 * 100 = 93.3%. El estado seguiría siendo within budget porque remainingMs (10) todavía es positivo — pero por un margen mínimo, apenas 10ms, un dato que ya anticipó el ejercicio de la lección 3: con tan poco presupuesto restante, la siguiente etapa (ramp-10, con 910ms medidos en el escenario real) prácticamente garantizaba agotar el presupuesto y romper el guardrail.
Ejercicio 3 — Explica el error budget a alguien del equipo comercial. Usando la analogía de la tarjeta de crédito, escribe en 2-3 frases cómo le explicarías a alguien sin contexto técnico por qué el equipo decidió frenar en la etapa de 10%, en vez de esperar a que el problema se resolviera solo.
Ver solución
Una respuesta razonable: "Nos dimos un presupuesto de 150 milisegundos de latencia extra antes de tener que detenernos — como el límite de una tarjeta de crédito. En la primera etapa del rollout gastamos solo una quinta parte de ese presupuesto, así que seguimos con confianza. Pero en la segunda etapa, el gasto se disparó a más del triple de lo que nos quedaba disponible — como intentar una compra que excede por mucho el saldo restante de la tarjeta—, y en ese punto, seguir adelante hubiera significado exponer a más gente a un problema que ya sabíamos que existía, sin ningún control sobre qué tan grave se pondría."
Resumen y siguiente paso
En esta lección construiste errorBudgetTracker(), que convierte el veredicto binario de un guardrail (OK/ROTO) en un saldo concreto: cuánto presupuesto de regresión —150ms, en el caso de la latencia— se consumió en cada etapa, y cuánto queda. Sobre el rollout real de recommendations, viste que canary gastó apenas un 20% del presupuesto, mientras que la etapa de 10% lo agotó por completo y quedó con un saldo negativo de -110ms — el mismo punto exacto donde guardrailWatch() ya había marcado HALT, ahora con la magnitud del problema hecha explícita en milisegundos.
Antes de avanzar deberías poder: calcular un presupuesto de error para un guardrail de tipo ceiling dados su línea base y su techo; explicar por qué un saldo negativo dice más que un simple ROTO sobre la urgencia del problema; y anticipar por qué el mismo modelo de presupuesto no se aplica directamente a guardrails de tipo floor o maxIncrease sin ajustar la fórmula.
Con esto cierras el arco temático de este módulo: viste qué vigilar (lección 2), cómo vigilarlo en vivo a lo largo de la rampa (lección 3), cuándo frenar y por qué esa disciplina no negocia con la métrica primaria (lección 4), cómo mostrarlo en un dashboard (lección 5), cómo alertar sin generar ruido (lección 6), y cuánto margen había antes de que el presupuesto se agotara (esta lección). La lección 8, el proyecto, junta las piezas centrales —guardrailWatch() reutilizado sin cambios— sobre el caso completo: vigilar el rollout de recommendations etapa por etapa, y decidir formalmente frenar en 10%, en vez de seguir hasta 50%.
Recursos
- Google SRE Book, Capítulo 3, "Embracing Risk" — sre.google/sre-book/embracing-risk. El capítulo que formaliza el concepto de error budget: cuánta "falta de confiabilidad" un equipo se permite antes de frenar el ritmo de cambios, la idea que esta lección aplica a la latencia de un rollout específico. En inglés.
- Google SRE Book, Capítulo 4, "Service Level Objectives" — sre.google/sre-book/service-level-objectives. Conecta el error budget con los SLO que lo definen — el mismo tipo de umbral fijado de antemano que sostiene el techo de 800ms usado en 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 detecta cuándo una regresión supera el margen tolerado para un release — el mismo umbral de "presupuesto agotado" que modela
errorBudgetTracker(). En inglés.