Módulo 2: Slis Slos And The Error Budget
6. Manos a la obra: *burn rate* — la tasa de consumo, no solo el saldo
Descripción
La lección 4 midió cuánto presupuesto de error quedó al final de 30 días: 4,23 minutos, un saldo positivo pero ajustado. Esa cifra, sin embargo, esconde algo que solo se ve mirando el mes día por día: hubo momentos en los que ese presupuesto se gastó muchísimo más rápido de lo que el saldo final sugiere, y momentos en los que casi no se tocó. Un saldo positivo al cierre del mes no distingue entre "se gastó parejo, sin sobresaltos" y "casi se agota en dos días, y el resto del mes compensó por pura suerte de bajo tráfico". Esta lección extiende la calculadora con la pieza que hace esa distinción posible: burn rate, la tasa a la que un sistema consume su presupuesto de error, medida contra la tasa que el SLO permite.
Conexión con el módulo
Esta lección no cambia el dataset ni el SLO de las lecciones 4 y 5 —sigue siendo TRAFFIC_30_DAYS contra 99,9% mensual—. Lo que cambia es la pregunta: en vez de "¿cuánto presupuesto queda al final?", burn rate pregunta "¿a qué velocidad se está gastando, ahora, en esta ventana reciente?". La respuesta, corrida sobre los mismos 30 días, va a mostrar algo que ni la lección 4 ni la 5 revelaron: exactamente cuándo, durante el mes, el presupuesto estuvo en riesgo real de agotarse — información que el Módulo 4 de esta guía convierte, más adelante, en una alerta real de Prometheus/Alertmanager.
Retomando la analogía: el plan de datos móviles, con números
La lección 1 de este módulo introdujo la idea: un plan de datos de 20 GB mensuales no te dice si vas bien solo con el saldo acumulado — te dice si vas bien con la velocidad a la que lo gastas. Un error budget funciona igual. El SLO de 99,9% mensual permite, en total, 43,2 minutos de indisponibilidad al mes — eso es el "20 GB" de esta analogía—. Pero un mes de 30 días no gasta ese presupuesto a un ritmo parejo garantizado: puede gastarse uniformemente (un poco cada día), o puede gastarse casi todo en un par de días malos, con el resto del mes casi sin tocarlo. Burn rate es, literalmente, el medidor de "GB gastados hoy" de esa analogía, aplicado al presupuesto de error: cuántas veces más rápido de lo permitido se está consumiendo el presupuesto, en una ventana de tiempo específica.
La fórmula, citada de la fuente
"Burn rate is how fast, relative to the SLO, the service consumes the error budget."
"With an SLO of 99.9% over a time window of 30 days, a constant 0.1% error rate uses exactly all of the error budget: a burn rate of 1."
Traducido a una fórmula concreta: burn rate es la tasa de error observada, dividida entre la tasa de error que el SLO permite.
burn_rate = tasa_de_error_observada / tasa_de_error_permitida
tasa_de_error_permitida = 1 - SLO
Un burn rate de 1 significa "exactamente al ritmo que el SLO permite" — sostenido todo el mes, agotaría el presupuesto justo al final, ni antes ni después. Un burn rate de 2 significa el doble de esa velocidad: sostenido, agotaría el presupuesto mensual completo en la mitad del tiempo, 15 días. Un burn rate de 14,4 —una cifra que vas a ver otra vez en un momento— significa que, sostenido, el presupuesto de un mes completo se agotaría en poco más de 50 horas.
El patrón multi-window, multi-burn-rate, y por qué existe
Medir burn rate con una sola ventana de tiempo tiene un problema práctico que la propia fuente explica:
"We can enhance the multi-burn-rate alerts [...] to notify us only when we're still actively burning through the budget—thereby reducing the number of false positives. To do this, we need to add another parameter: a shorter window to check if the error budget is still being consumed as we trigger the alert."
La idea, en una frase: una ventana larga por sí sola detecta un consumo elevado, pero puede seguir "encendida" mucho después de que el problema ya se resolvió —el promedio de varios días sigue viéndose mal aunque el día de hoy ya esté sano—. Una ventana corta, evaluada junto a la larga, confirma si el consumo elevado sigue activo ahora mismo. Solo cuando ambas señalan consumo elevado a la vez, la alerta tiene sentido. Google SRE publica una tabla de referencia con tres niveles de severidad, cada uno con su propio par de ventanas y su propio umbral de burn rate:
| Severidad | Ventana larga | Ventana corta | Burn rate | % del presupuesto que consume |
|---|---|---|---|---|
| Page (urgente) | 1 hora | 5 minutos | 14,4x | 2% |
| Page (urgente) | 6 horas | 30 minutos | 6x | 5% |
| Ticket (no urgente) | 3 días | 6 horas | 1x | 10% |
— Google SRE Workbook — Alerting on SLOs, Tabla 5-8.
Esta guía tiene una limitación honesta que vale la pena declarar antes de seguir: el dataset de esta lección tiene granularidad diaria —un solo par de números por día—, no por hora ni por minuto. Las dos filas "Page" de la tabla necesitan telemetría fina (una lectura cada pocos minutos) que este dataset fijo, por diseño, no tiene — esa granularidad llega recién en el Módulo 3, con datos reales de CloudWatch/Prometheus, y es exactamente lo que scripts/burn_rate_evaluator.py del Módulo 4 va a implementar con las ventanas literales de horas y minutos. Lo que esta lección sí puede reproducir con exactitud, porque su ventana larga ya es de escala de días, es la fila Ticket: ventana larga de 3 días, umbral de burn rate de 1x. La ventana corta original de esa fila es de 6 horas; con datos diarios, esta lección adapta esa ventana corta a 1 día —el intervalo más fino que el dataset permite— manteniendo intacta la lógica de dos ventanas del mecanismo original.
Extendiendo la calculadora
Agrega estas funciones a scripts/error_budget_calculator.py, después de print_report:
# --- Nuevo en esta leccion: burn rate ---
# burn_rate = tasa de error observada / tasa de error permitida por el SLO
def window_slice(dataset, end_day, length_days):
return [row for row in dataset if end_day - length_days < row[0] <= end_day]
def burn_rate_of(window, slo=SLO):
if not window:
return 0.0
valid = sum(row[1] for row in window)
good = sum(row[2] for row in window)
error_rate = 1 - (good / valid)
allowed_error_rate = 1 - slo
return error_rate / allowed_error_rate
# Tabla 5-8 del workbook de Google SRE, fila "Ticket": ventana larga 3 dias,
# burn rate >= 1x, 10% del presupuesto. La ventana corta original es 6 horas;
# este dataset es diario, asi que la ventana corta se adapta a 1 dia.
TICKET_LONG_DAYS = 3
TICKET_SHORT_DAYS = 1
TICKET_THRESHOLD = 1.0
def ticket_tier_fires(dataset, end_day, slo=SLO):
long_window = window_slice(dataset, end_day, TICKET_LONG_DAYS)
short_window = window_slice(dataset, end_day, TICKET_SHORT_DAYS)
long_br = burn_rate_of(long_window, slo)
short_br = burn_rate_of(short_window, slo)
fires = long_br >= TICKET_THRESHOLD and short_br >= TICKET_THRESHOLD
return fires, long_br, short_br
Y reemplaza el bloque if __name__ == "__main__": para que, además del reporte de la lección 4, imprima el análisis de burn rate:
if __name__ == "__main__":
report = budget_report(TRAFFIC_30_DAYS)
print_report(report, "process-shipment-manifest -- 30 dias de trafico")
print()
print("--- Burn rate diario (solo dias con errores) ---")
for row in TRAFFIC_30_DAYS:
br = burn_rate_of([row])
if br > 0:
print(f"Dia {row[0]:>2}: eventos validos={row[1]:>3} eventos buenos={row[2]:>3} burn rate={br:.2f}x")
print()
print(f"--- Ticket tier (ventana larga {TICKET_LONG_DAYS}d / ventana corta {TICKET_SHORT_DAYS}d, umbral {TICKET_THRESHOLD}x) ---")
for end_day in range(3, 31):
fires, long_br, short_br = ticket_tier_fires(TRAFFIC_30_DAYS, end_day)
if fires or long_br >= 0.3:
marca = "DISPARA" if fires else "no dispara"
print(f"Dia {end_day:>2}: ventana larga={long_br:.2f}x ventana corta={short_br:.2f}x -> {marca}")
python3 scripts/error_budget_calculator.py
Qué esperar (literal — corrido dos veces produce, dígito por dígito, el mismo resultado; así se confirma su determinismo):
--- process-shipment-manifest -- 30 dias de trafico ---
Eventos validos: 12195
Eventos buenos: 12184
SLI medido: 99.9098%
SLO objetivo: 99.9%
Presupuesto (mensual): 43.2 minutos
Consumido en el periodo: 38.97 minutos (90.2% del presupuesto)
Presupuesto restante: 4.23 minutos
--- Burn rate diario (solo dias con errores) ---
Dia 4: eventos validos=438 eventos buenos=437 burn rate=2.28x
Dia 17: eventos validos=452 eventos buenos=446 burn rate=13.27x
Dia 18: eventos validos=438 eventos buenos=435 burn rate=6.85x
Dia 23: eventos validos=445 eventos buenos=444 burn rate=2.25x
--- Ticket tier (ventana larga 3d / ventana corta 1d, umbral 1.0x) ---
Dia 4: ventana larga=0.75x ventana corta=2.28x -> no dispara
Dia 5: ventana larga=0.74x ventana corta=0.00x -> no dispara
Dia 6: ventana larga=0.83x ventana corta=0.00x -> no dispara
Dia 17: ventana larga=4.52x ventana corta=13.27x -> DISPARA
Dia 18: ventana larga=6.74x ventana corta=6.85x -> DISPARA
Dia 19: ventana larga=6.67x ventana corta=0.00x -> no dispara
Dia 20: ventana larga=2.48x ventana corta=0.00x -> no dispara
Dia 23: ventana larga=0.85x ventana corta=2.25x -> no dispara
Dia 24: ventana larga=0.75x ventana corta=0.00x -> no dispara
Dia 25: ventana larga=0.75x ventana corta=0.00x -> no dispara
Leyendo el resultado: lo que el saldo final nunca hubiera mostrado
Tres lecturas de este resultado merecen atención, en orden de lo más simple a lo más revelador:
1. El día 17, aislado, casi cruza el umbral "Page". Un burn rate de 13,27x en un solo día está a menos de un punto del umbral de 14,4x que Google usa para su alerta más urgente (ventana de 1 hora). Este dataset no tiene granularidad horaria para confirmar si, dentro de ese día, hubo una hora específica que sí cruzó ese umbral —es exactamente el tipo de detalle que solo telemetría real, del Módulo 3, puede revelar—, pero el dato diario ya deja claro que el día 17 no fue un mal día cualquiera: fue, en la escala de Google SRE, un día que estuvo a un pelo de la severidad más alta.
2. El Ticket tier dispara exactamente en los días 17 y 18 — ningún otro día del mes. De los 30 días del dataset, solo dos cumplen simultáneamente el umbral en la ventana larga y en la ventana corta. Esto es información que ni la lección 4 ni la 5 dieron: no solo "el mes estuvo ajustado" (4,23 minutos de margen), sino exactamente cuándo ese ajuste ocurrió — información directamente accionable para un equipo real, que sabría, sin adivinar, en qué dos días revisar los logs primero.
3. El día 19 es el resultado más importante de esta lección. La ventana larga (días 17-19) todavía muestra 6,67x —parece, a primera vista, que el problema sigue activo—, pero la ventana corta (el día 19 solo) muestra 0,00x: cero errores ese día. La regla de dos ventanas, exactamente como la cita de Google SRE explica, evita que la alerta siga "encendida" el día 19 solo porque el promedio de los últimos tres días todavía carga el peso de los días 17 y 18. El sistema ya se recuperó el día 19; una alerta de una sola ventana (solo la larga) habría seguido disparando ese día, generando ruido sobre un problema que ya terminó — exactamente el "falso positivo" que la cita de esta lección nombra como la razón de ser del diseño de dos ventanas.
Errores comunes
Confundir el 90,2% de presupuesto consumido (lección 4, acumulado del mes) con el burn rate (esta lección, velocidad puntual) como si fueran el mismo número. Qué pasa: alguien usa "90,2%" y "13,27x" en la misma frase como si midieran lo mismo con distinta notación. Cómo detectarlo: si no puedes explicar por qué un mes con 90,2% de presupuesto consumido al final tuvo, en algún día específico, un burn rate de más de 13 veces el permitido. Cómo corregirlo: son dos preguntas distintas sobre el mismo dataset — "¿cuánto en total?" (90,2%, acumulado sobre todo el mes) y "¿qué tan rápido en este momento?" (13,27x, medido en la ventana de un solo día). Un burn rate alto en pocos días puede coexistir perfectamente con un consumo acumulado que, al final, sigue siendo positivo — es exactamente lo que pasó en este dataset.
Asumir que el Ticket tier debería haber disparado también el día 19, porque "el problema fue grave" (de ignorar la ventana corta a propósito). Qué pasa: alguien, viendo que el día 19 tuvo ventana larga=6.67x, argumenta que la alerta debió seguir activa ese día. Cómo detectarlo: si tu razonamiento sobre cuándo debería disparar una alerta usa solo la ventana larga, ignorando la corta. Cómo corregirlo: esa es exactamente la trampa que el diseño de dos ventanas evita — sin la ventana corta, cualquier promedio de varios días sigue "contaminado" por un incidente ya resuelto durante días después de que termine, generando alertas para un problema que ya no existe. La ventana corta del día 19 (0,00x) es la evidencia de que el sistema ya se recuperó; ignorarla sería reintroducir el problema de falsos positivos que este diseño existe para resolver.
Tratar el resultado de esta lección como "la alerta real" de esta guía (de anticipar el Módulo 4). Qué pasa: alguien asume que ticket_tier_fires ya es la alerta de producción que Andes Cargo va a usar. Cómo detectarlo: si esperas que este código se conecte a Alertmanager o dispare una notificación real. Cómo corregirlo: esta función corre sobre un dataset fijo, en un script que un humano ejecuta manualmente — es la demostración de la lógica del patrón multi-ventana, no la infraestructura de alerta en sí. El Módulo 4 construye scripts/burn_rate_evaluator.py, un artefacto separado, que sí evalúa esta lógica de forma continua sobre métricas reales y la conecta a Alertmanager y a una alarma real de CloudWatch — con las ventanas literales de horas y minutos que este dataset diario no puede reproducir.
Ejercicios
Ejercicio 1 — Calcula a mano el burn rate del día 4, y verifica que coincide con el resultado del script. El día 4 tuvo 438 eventos válidos y 437 buenos.
Ver solución
Tasa de error del día 4: (438 − 437) / 438 = 1/438 ≈ 0,2283%. Tasa de error permitida por el SLO de 99,9%: 1 − 0,999 = 0,1%. Burn rate = 0,2283% / 0,1% ≈ 2,28x — coincide exactamente con la salida del script (burn rate=2.28x). Este día, con un solo evento malo, ya duplica la velocidad de consumo permitida — una confirmación de que incluso un único error, en un día de tráfico moderado, puede empujar el burn rate por encima de 1x sin que haga falta un incidente grande.
Ejercicio 2 — Explica, sin ejecutar nada, por qué el día 20 aparece en la salida del script (ventana larga=2.48x) aunque el día 20 en sí no tuvo ningún evento malo. Usa la definición de window_slice.
Ver solución
window_slice(dataset, end_day=20, length_days=3) selecciona los días donde 20 - 3 < day <= 20, es decir, los días 18, 19 y 20 — no solo el día 20 en sí. El día 18 todavía tiene 3 eventos malos (parte del incidente de los días 17-18), así que, aunque los días 19 y 20 estén completamente limpios, la ventana larga que termina en el día 20 todavía incluye el "peso" del día 18 dentro de su cálculo. Esto es, otra vez, la misma razón por la que existe la ventana corta: una ventana de 3 días sigue mostrando actividad elevada varios días después de que el problema real terminó, precisamente porque promedia sobre un rango que todavía contiene el incidente — solo la ventana corta, evaluada en conjunto, confirma que la actividad ya no está activa.
Ejercicio 3 — Diseña, en prosa (sin escribir código), cómo adaptarías las dos filas "Page" de la Tabla 5-8 (14,4x/1h/5min y 6x/6h/30min) si esta lección tuviera datos por hora en vez de por día. ¿Qué cambiaría en window_slice y en las constantes de umbral?
Ver solución
Con datos por hora, window_slice funcionaría de forma idéntica —sigue siendo una función genérica que filtra un rango de "unidades de tiempo" alrededor de un punto final—, solo que la unidad pasaría de días a horas: window_slice(dataset, end_hour, length_hours). Las constantes cambiarían a PAGE_FAST_LONG_HOURS = 1, PAGE_FAST_SHORT_MINUTES = 5 (que en un dataset horario tendría que aproximarse, o requeriría datos por minuto para ser literal), PAGE_FAST_THRESHOLD = 14.4, y de forma análoga para la fila de 6 horas/30 minutos con umbral 6x. La lógica de ticket_tier_fires —ambas ventanas deben cruzar el umbral a la vez— se reutilizaría sin cambios para las dos filas nuevas, solo cambiando qué función de ventana y qué umbral reciben. Esta es, en esencia, exactamente la generalización que el Módulo 4 de esta guía construye en scripts/burn_rate_evaluator.py, con datos reales de CloudWatch/Prometheus que sí tienen granularidad de minutos.
Resumen y siguiente paso
En esta lección extendiste scripts/error_budget_calculator.py con burn rate —la tasa de error observada, dividida entre la tasa que el SLO permite—, citada de sre.google/workbook/alerting-on-slos/. Sobre el mismo dataset de 30 días, encontraste que el día 17 llegó a un burn rate de 13,27x —a un paso del umbral "Page" de Google de 14,4x—, que el patrón de dos ventanas (Ticket tier, 3 días/1 día) disparó exactamente en los días 17 y 18, y que ese mismo patrón correctamente no disparó el día 19, aunque la ventana larga siguiera elevada, porque la ventana corta confirmó que el sistema ya se había recuperado. Verificaste que el script sigue siendo determinista tras la extensión.
Antes de avanzar deberías poder: explicar la diferencia entre error budget (saldo acumulado) y burn rate (velocidad puntual) sin usar la palabra "presupuesto" dos veces; calcular a mano el burn rate de un solo día a partir de sus eventos válidos y buenos; y explicar, con el ejemplo del día 19, por qué una alerta de una sola ventana genera falsos positivos que el patrón de dos ventanas evita.
La lección 7 corre esta misma calculadora —sin ningún cambio de código— sobre un segundo dataset, diseñado a mano para representar una semana genuinamente mala de process-shipment-manifest. La herramienta ya está completa; lo que queda es practicar leer lo que dice.
Recursos
- Google SRE Workbook — Alerting on SLOs — fuente exacta de la definición de burn rate, la Tabla 5-8, y la justificación del patrón de dos ventanas.
- Este mismo repositorio, Módulo 2, lección 1 (
01-module-introduction-2.md) — la analogía del plan de datos móviles que esta lección desarrolla con números reales. - Este mismo repositorio, Módulo 2, lección 4 (
04-hands-on-the-error-budget-calculator.md) — el dataset y las funciones base que esta lección extiende sin modificar.