Módulo 2: Slis Slos And The Error Budget

4. Manos a la obra: la calculadora de error budget

Descripción

Esta es la lección donde el vocabulario se convierte en herramienta. Vas a escribir scripts/error_budget_calculator.py, correrlo de verdad con python3 sobre un dataset fijo de 30 días de tráfico de process-shipment-manifest, y obtener una salida literal: el SLI medido, la comparación contra el SLO, y los minutos exactos de presupuesto de error que quedan. Ninguna cifra de esta lección está inventada ni redondeada a mano — el resultado que vas a leer es exactamente lo que ese script produce, siempre, cada vez que lo corres.

Conexión con el módulo

La lección 3 definió, por escrito, qué cuenta como evento bueno y qué cuenta como evento válido para process-shipment-manifest. Esta lección aplica esa definición sobre 30 días de datos reales —no hipotéticos como el 99,9% de la lección 6 del Módulo 1, sino un dataset diseñado a mano con la misma disciplina que esta guía completa exige: fijo, committeado, sin random—. El resultado de esta lección es la base sobre la que las lecciones 5, 6 y 7 de este módulo construyen directamente, sin volver a escribir el script desde cero.


Paso 1 — El dataset fijo: 30 días de tráfico de process-shipment-manifest

Antes del código, el dato. Cada fila representa un día, con dos números que ya conoces de la lección 3: eventos válidos (invocaciones disparadas por subidas reales de manifiestos) y eventos buenos (las que terminaron sin excepción, dentro del timeout, con un registro correcto en Shipments). El patrón semanal es real: Andes Cargo recibe más manifiestos entre semana (día laboral de sus clientes transportistas) y menos los fines de semana — y hay un incidente deliberado en los días 17 y 18, un deterioro parcial de dos días que la lección 6 de este módulo va a usar para demostrar burn rate.

SemanaLunMarMiéJueVieSábDom
1 (días 1-7)430445452438→437 (1 error)460310295
2 (días 8-14)430445452438460310295
3 (días 15-21)430445452→446 (6 errores)438→435 (3 errores)460310295
4 (días 22-28)430445→444 (1 error)452438460310295
5 (días 29-30)430445

Cada celda es el número de eventos válidos ese día; donde hay una flecha, el segundo número es cuántos de esos eventos fueron buenos —el resto, la diferencia, son eventos malos (excepciones, timeouts o invocaciones reguladas, según la lección 3)—. Los días sin flecha tuvieron cero eventos malos: todos los eventos válidos fueron buenos.


Paso 2 — El script completo

Crea scripts/error_budget_calculator.py en la raíz de andes-cargo-infra/:

# error_budget_calculator.py
# Calculadora real de error budget: SLI, comparacion contra el SLO, minutos de presupuesto.
# Determinista: mismo dataset + mismo SLO = mismo resultado, siempre. Sin random, sin datetime.now().

# --- El dataset fijo de 30 dias de trafico de process-shipment-manifest ---
# Cada tupla: (day, valid_events, good_events)
TRAFFIC_30_DAYS = [
    (1, 430, 430), (2, 445, 445), (3, 452, 452), (4, 438, 437), (5, 460, 460),
    (6, 310, 310), (7, 295, 295), (8, 430, 430), (9, 445, 445), (10, 452, 452),
    (11, 438, 438), (12, 460, 460), (13, 310, 310), (14, 295, 295), (15, 430, 430),
    (16, 445, 445), (17, 452, 446), (18, 438, 435), (19, 460, 460), (20, 310, 310),
    (21, 295, 295), (22, 430, 430), (23, 445, 444), (24, 452, 452), (25, 438, 438),
    (26, 460, 460), (27, 310, 310), (28, 295, 295), (29, 430, 430), (30, 445, 445),
]

SLO = 0.999                 # 99.9% mensual, el mismo nivel de referencia del Modulo 1
SLO_WINDOW_DAYS = 30        # la ventana que el SLO promete
SLO_WINDOW_MINUTES = SLO_WINDOW_DAYS * 24 * 60


def compute_sli(dataset):
    """SLI = eventos buenos / eventos validos, sumados sobre todo el dataset."""
    total_valid = sum(row[1] for row in dataset)
    total_good = sum(row[2] for row in dataset)
    return total_good / total_valid, total_good, total_valid


def error_budget_minutes(slo=SLO, window_minutes=SLO_WINDOW_MINUTES):
    """error budget = (1 - SLO) x minutos de la ventana que el SLO promete."""
    return (1 - slo) * window_minutes


def budget_report(dataset, slo=SLO, window_minutes=SLO_WINDOW_MINUTES):
    sli, total_good, total_valid = compute_sli(dataset)
    observed_error_rate = 1 - sli
    budget_minutes = error_budget_minutes(slo, window_minutes)
    minutes_consumed = observed_error_rate * window_minutes
    minutes_remaining = budget_minutes - minutes_consumed
    consumed_pct = (minutes_consumed / budget_minutes) * 100
    return {
        "total_valid": total_valid,
        "total_good": total_good,
        "sli": sli,
        "slo": slo,
        "observed_error_rate": observed_error_rate,
        "budget_minutes": budget_minutes,
        "minutes_consumed": minutes_consumed,
        "minutes_remaining": minutes_remaining,
        "consumed_pct": consumed_pct,
    }


def print_report(report, label):
    print(f"--- {label} ---")
    print(f"Eventos validos:         {report['total_valid']}")
    print(f"Eventos buenos:          {report['total_good']}")
    print(f"SLI medido:              {report['sli']:.4%}")
    print(f"SLO objetivo:            {report['slo']:.1%}")
    print(f"Presupuesto (mensual):   {report['budget_minutes']:.1f} minutos")
    print(f"Consumido en el periodo: {report['minutes_consumed']:.2f} minutos ({report['consumed_pct']:.1f}% del presupuesto)")
    print(f"Presupuesto restante:    {report['minutes_remaining']:.2f} minutos")


if __name__ == "__main__":
    report = budget_report(TRAFFIC_30_DAYS)
    print_report(report, "process-shipment-manifest -- 30 dias de trafico")

Cada función tiene un solo trabajo, deliberadamente: compute_sli implementa exactamente la fórmula de la lección 3 (buenos ÷ válidos); error_budget_minutes es la misma fórmula del Módulo 1, lección 6, ahora como función reutilizable en vez de constantes sueltas; budget_report es la pieza nueva de esta lección —traduce un SLI medido con eventos (una proporción) a minutos consumidos sobre una ventana de tiempo (el mismo tipo de número que ya usaste en el Módulo 1)—. Esta traducción es el puente entre "cuántos manifiestos fallaron" y "cuántos minutos de indisponibilidad permitida se gastaron", y es exactamente lo que hace que esta calculadora generalice el cálculo manual del Módulo 1 en vez de repetirlo.


Paso 3 — Corriendo la calculadora

python3 scripts/error_budget_calculator.py

Qué esperar (literal — corre este script exactamente como está, y este es el resultado, siempre, sin variación):

--- 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

Leyendo el resultado: por qué 4,23 minutos es la cifra que importa

De 12.195 invocaciones válidas en 30 días, 11 terminaron mal —los errores de los días 4, 17, 18 y 23 que ya viste en la tabla del dataset—. Eso da un SLI de 99,9098%, apenas por encima del SLO de 99,9%. Traducido a minutos sobre una ventana mensual: de los 43,2 minutos de presupuesto que el SLO permite, este mes consumió 38,97 —el 90,2% del presupuesto completo— y le quedan 4,23 minutos para los cuatro días que faltan hasta el cierre del mes.

Esto no es un resultado cómodo, y esa incomodidad es exactamente el punto de esta lección: un SLI de 99,91% "suena" bien —es, después de todo, más alto que el SLO—, pero mirado a través del presupuesto de error, este mes estuvo a un solo día malo más de agotar el presupuesto completo. Es la misma distinción que la lección 1 de este módulo ya anticipó con la analogía del plan de datos: el saldo todavía es positivo, pero positivo no es lo mismo que cómodo. La lección 6 de este módulo va a mostrar, con la misma exactitud, cuándo durante el mes ese presupuesto se gastó más rápido — información que "SLI: 99,9098%" por sí solo nunca revela.


Errores comunes

Leer "99,9098% > 99,9%" y concluir que el mes fue completamente saludable (de mirar solo el SLI, no el presupuesto). Qué pasa: alguien ve que el SLI medido superó el SLO y da por cerrado el análisis, sin fijarse en el porcentaje de presupuesto consumido. Cómo detectarlo: si tu conclusión sobre este resultado es "todo bien, superamos el SLO" sin mencionar el 90,2% consumido. Cómo corregirlo: un SLI apenas por encima del SLO puede, al mismo tiempo, representar un presupuesto casi agotado — son dos formas de leer el mismo número, y la segunda (minutos restantes) es la que de verdad importa para decidir si hay margen para tomar riesgo el resto del mes. budget_report calcula ambas cosas a propósito, para que ninguna de las dos se lea sola.

Modificar TRAFFIC_30_DAYS para "probar con otros números" antes de correr el script tal como está (de saltarse la verificación). Qué pasa: alguien, antes de confirmar que el script produce exactamente la salida de esta lección, ya está experimentando con valores propios. Cómo detectarlo: si tu primera corrida de este script no coincide con el bloque "Qué esperar" de esta lección. Cómo corregirlo: corre el script exactamente como está primero — si tu salida no coincide dígito por dígito con la de esta lección, hay un error de transcripción en tu copia del dataset o del código, no una variación esperada (este script no tiene ninguna fuente de variación posible). Confirma la coincidencia exacta antes de modificar cualquier cosa.

Confundir SLO_WINDOW_DAYS (la ventana que el SLO promete) con la cantidad de días del dataset (de asumir que siempre son el mismo número). Qué pasa: alguien asume que SLO_WINDOW_DAYS siempre tiene que coincidir con len(dataset). Cómo detectarlo: si tu mental model de esta función no puede explicar qué pasaría si le pasaras un dataset de solo 7 días. Cómo corregirlo: budget_report calcula el presupuesto sobre la ventana que el SLO promete (30 días, mensual) sin importar cuántos días de datos reales le pases — esta separación es intencional, y es exactamente lo que la lección 7 de este módulo va a aprovechar, al medir una sola semana mala contra el presupuesto mensual completo, no contra un presupuesto de siete días.


Ejercicios

Ejercicio 1 — Verifica a mano el cálculo de minutes_consumed para un solo día, sin ejecutar el script. Usando solo el día 17 del dataset (452 eventos válidos, 446 buenos), calcula qué porcentaje de error tuvo ese día específico, y qué fracción del presupuesto mensual (43,2 minutos) representaría si todo el mes hubiera tenido esa misma tasa de error.

Ver solución

Tasa de error del día 17: (452 − 446) / 452 = 6/452 ≈ 1,327%. Si esa tasa se sostuviera los 30 días completos, los minutos consumidos serían: 1,327% × 43.200 minutos (la ventana completa en minutos) ≈ 573,5 minutos — más de 13 veces el presupuesto mensual completo de 43,2 minutos. Esto confirma, con un cálculo manual, la intuición de la sección "Leyendo el resultado": un solo día como el 17, sostenido, agotaría el presupuesto del mes muchas veces — la misma idea de burn rate que la lección 6 de este módulo formaliza con una fórmula y una función dedicada.

Ejercicio 2 — Modifica el script, sin ejecutarlo todavía, para que también imprima cuántos días del dataset tuvieron al menos un evento malo. Escribe la línea o líneas de código que agregarías, usando las estructuras ya presentes en el script.

Ver solución
bad_days = [row[0] for row in TRAFFIC_30_DAYS if row[2] < row[1]]
print(f"Dias con al menos un evento malo: {len(bad_days)} -> {bad_days}")

Con el dataset de esta lección, esa línea imprimiría Dias con al menos un evento malo: 4 -> [4, 17, 18, 23] — los mismos cuatro días que la tabla del Paso 1 ya marcó con una flecha. El ejercicio practica leer el dataset como una lista de tuplas y filtrar sobre la diferencia entre row[1] (válidos) y row[2] (buenos), la misma operación que compute_sli ya hace a nivel agregado.

Ejercicio 3 — Explica por qué budget_report recibe slo y window_minutes como parámetros con valor por defecto, en vez de usar directamente las constantes SLO y SLO_WINDOW_MINUTES dentro de la función. ¿Qué te permite hacer este diseño que no podrías hacer si la función usara las constantes globales directamente?

Ver solución

Usar parámetros con valor por defecto (slo=SLO, window_minutes=SLO_WINDOW_MINUTES) permite llamar a budget_report(dataset) sin especificar nada —usa los valores de referencia de esta lección, 99,9% sobre 30 días—, pero también permite llamar a budget_report(dataset, slo=0.99) para comparar un SLO distinto, sin tener que editar ninguna constante global ni duplicar la función. Si la función leyera SLO directamente del ámbito global en vez de recibirlo como parámetro, la única forma de probar un SLO distinto sería cambiar la constante global —lo cual afectaría a cualquier otra llamada a la función en el mismo programa, no solo a la que quieres probar—. Este diseño es, literalmente, lo que hace posible la lección 5 de este módulo: correr la misma calculadora, sobre el mismo dataset, con tres SLOs distintos, sin ningún cambio al código salvo el argumento que se le pasa.


Resumen y siguiente paso

En esta lección construiste y corriste, de verdad, la primera versión de scripts/error_budget_calculator.py: sobre 30 días de tráfico fijo de process-shipment-manifest, con 11 eventos malos de 12.195 válidos, el SLI medido fue 99,9098% — apenas por encima del SLO de referencia de 99,9%, pero con solo 4,23 minutos de un presupuesto de 43,2 restantes, el 90,2% ya consumido. Confirmaste que el script es completamente determinista —el mismo dataset y el mismo SLO producen, siempre, exactamente el mismo resultado— y entendiste por qué budget_report separa la ventana del SLO de la cantidad de días del dataset, una decisión de diseño que las lecciones 5 y 7 de este módulo van a aprovechar directamente.

Antes de avanzar deberías poder: correr el script y obtener exactamente 99.9098% y 4.23 minutos; explicar la diferencia entre "el SLI superó el SLO" y "queda margen real en el presupuesto"; y modificar el script para agregar una métrica derivada simple, como la del Ejercicio 2.

La lección 5 usa esta misma calculadora, sin cambiar el dataset, para responder una pregunta distinta: ¿qué hubiera pasado con este mismo tráfico real si el SLO elegido hubiera sido 99% en vez de 99,9%? ¿Y si hubiera sido 99,99%?

Recursos

  1. Google — The Art of SLOs (Participant Handbook) — la fórmula de SLI que compute_sli implementa.
  2. Google SRE Workbook — Implementing SLOs — la fórmula de error budget que error_budget_minutes implementa.
  3. Este mismo repositorio, Módulo 1, lección 6 (06-hands-on-the-error-budget-the-claude-code-incident-burned.md) — el cálculo manual de un solo uso que esta calculadora generaliza.
  4. Este mismo repositorio, Módulo 2, lección 3 (03-hands-on-your-first-sli-definition.md) — la definición exacta de "bueno" y "válido" que el dataset de esta lección aplica día por día.