Módulo 7: Observability Latency And Evals In Production
2. SLI de una carga de IA: escalamiento, latencia, tasa de bloqueo del guardrail
Descripción
Esta lección define, con la misma precisión formal que sre-and-incident-response-guide, Módulo 2, lección 3 ya aplicó a process-shipment-manifest, los tres SLI nuevos de extract-shipment-manifest-fields: la tasa de escalamiento, la latencia de inferencia, y la tasa de bloqueo del guardrail propio. Ninguno de los tres se inventa aquí — cada uno tiene una fórmula exacta, una fuente citada, y una decisión explícita de qué cuenta como numerador y qué cuenta como denominador. Y uno de los tres, con toda intención, no encaja limpiamente en el molde clásico de SLI que la guía hermana ya estableció — esta lección explica por qué, en vez de forzar la fórmula donde no corresponde.
Conexión con el módulo
La lección 1 prometió tres SLI nuevos y adelantó el ledger de honestidad completo. Esta lección cumple esa promesa con las tres definiciones formales. Las lecciones 3 a 6 construyen, una por una, el código que cada definición necesita para producir un número real — la tasa de escalamiento en la lección 4, la tasa de bloqueo en la lección 6 (como subproducto del arnés de smoke test), la latencia nunca en código ejecutado, solo documentada con precisión en la lección 7.
Analogía: tres agujas en el mismo tablero, tres tipos de pregunta distintos
Retoma el tablero del auto eléctrico de la lección 1. No todas las agujas de un tablero miden lo mismo tipo de cosa. El velocímetro contesta "¿qué tan rápido voy, ahora mismo?" — un valor instantáneo. El indicador de "kilómetros hasta quedarme sin batería" contesta una pregunta distinta: "¿cuánta capacidad me queda, dado mi ritmo actual de consumo?" — una proyección. Y una luz de advertencia binaria ("revisar motor: sí/no") contesta una tercera pregunta, todavía distinta: "¿pasó algo que requiere atención?". Las tres son información real sobre el mismo vehículo, pero ninguna es intercambiable con las otras dos. Los tres SLI de esta lección funcionan igual: la tasa de escalamiento contesta "¿qué proporción de mi tráfico total está tomando el camino caro?" — una pregunta de composición de carga, no de éxito o fracaso. La latencia de inferencia contesta "¿qué tan rápido responde el modelo cuando sí lo invoco?" — un valor de desempeño. La tasa de bloqueo del guardrail contesta "¿qué proporción de las respuestas que llegan a mi validador de esquema pasan la prueba?" — la única de las tres que sí encaja, limpiamente, en el molde clásico de "eventos buenos ÷ eventos válidos".
SLI 1 — Tasa de escalamiento: una pregunta de composición, no de éxito
La fórmula
Tasa de escalamiento = eventos ManifestParseFailed / total de manifiestos procesados
Numerador y denominador, con precisión:
| Término | Definición exacta |
|---|---|
| Total de manifiestos procesados | Toda invocación de process-shipment-manifest disparada por una subida real de un manifiesto a andes-cargo-shipment-docs — la misma exclusión exacta que sre-and-incident-response-guide, Módulo 2, lección 3 ya estableció para invocaciones manuales de prueba: nunca cuentan |
Eventos ManifestParseFailed | El subconjunto de esos manifiestos donde parse_manifest() (Módulo 1, lección 3) no produjo los cinco campos de SHIPMENT_FIELDS_SCHEMA (Módulo 4, lección 6) — el criterio exacto que dispara la publicación del evento en andes-cargo-events |
Por qué esta fórmula NO es "eventos buenos ÷ eventos válidos"
Aquí está la distinción que vale la pena desarrollar con cuidado, porque es fácil forzarla sin darse cuenta. La fórmula de SLI que sre-and-incident-response-guide citó de Google es "the proportion of valid events that were good" — la proporción de eventos válidos que fueron buenos. Esa fórmula asume que existe una noción clara de "bueno" frente a "malo": una invocación que termina bien es buena, una que falla es mala. Aplicada a process-shipment-manifest, eso funciona perfectamente — la guía hermana ya lo demostró.
Pero un ManifestParseFailed no es un evento malo. Es, literalmente, el sistema funcionando exactamente como se diseñó: process-shipment-manifest detectó, correctamente, que este manifiesto no tiene el formato clave=valor esperado, y publicó un evento honesto para que el camino de escalamiento lo intente. Si tratáramos cada ManifestParseFailed como un "evento malo" en una fórmula de SLI clásica, estaríamos midiendo, sin darnos cuenta, algo que suena a "confiabilidad" pero que en realidad es "qué tan predecible es el formato de entrada de nuestros socios logísticos" — una pregunta de negocio, no de ingeniería de sistemas. La tasa de escalamiento es, en cambio, un SLI de composición de tráfico: te dice cuánta exposición tiene Andes Cargo al camino caro y no determinista, sin juzgar si eso es "bueno" o "malo" en sí mismo.
SLI CLÁSICO (disponibilidad) SLI DE ESTE MÓDULO (composición)
¿Qué proporción de invocaciones ¿Qué proporción del tráfico total
terminó BIEN? tomó el camino CARO?
"Bueno" y "malo" tienen un No hay "bueno" ni "malo" -- hay
significado claro de antemano "barato" y "caro", y ambos son
resultados válidos del sistema
Un SLO típico: 99.9% bueno Un SLO típico: un TECHO --
"no más del X% debería escalar",
una señal de alerta si se cruza,
no una meta de perfección
Por qué esto igual importa para confiabilidad, sin ser un SLI de confiabilidad
Vale la pena no llevar esta distinción demasiado lejos: aunque la tasa de escalamiento no mide "bueno ÷ malo", sí es una señal operativa real, con las mismas implicaciones prácticas que cualquier SLI. Un salto repentino —del 12% habitual al 60% de un día para otro— es exactamente el tipo de anomalía que ameritaría una alerta, la misma disciplina que sre-and-incident-response-guide, Módulo 4 ya construyó para burn rate. La diferencia no es "esto importa menos" — es que la razón de un valor alto no es "el sistema está roto", es "algo en el mundo real cambió" (un socio logístico grande cambió de formato, por ejemplo) — la misma distinción que el Módulo 1, lección 3, Ejercicio 3 de esta guía ya adelantó.
SLI 2 — Latencia de inferencia: el molde clásico, sin datos que lo llenen
La fórmula, citada de la fuente
"The proportion of requests that were faster than some threshold."
Traducido a extract-shipment-manifest-fields:
SLI de latencia = invocaciones con InvocationLatency <= umbral declarado
---------------------------------------------------
total de invocaciones que llegaron a invocar Bedrock
Esta fórmula sí encaja, limpiamente, en el molde clásico — es un SLI de "proporción de eventos buenos" en el sentido estricto: un "evento bueno" es una invocación que responde dentro del presupuesto de tiempo declarado. El problema no es la fórmula. Es que InvocationLatency — la métrica real que Amazon Bedrock publica en CloudWatch, bajo el namespace AWS/Bedrock, con dimensión ModelId — solo tiene un valor cuando una invocación real ocurrió. Sin una sola invocación, no hay ningún dato con el cual calcular ni el numerador ni el denominador de esta fórmula. La lección 7 de este módulo desarrolla esto con precisión completa, incluidas las métricas reales que Bedrock publica y por qué ninguna de ellas tiene un valor en este laboratorio $0.
SLI 3 — Tasa de bloqueo del guardrail propio: el otro SLI que sí encaja en el molde clásico
La fórmula
Tasa de bloqueo = candidatos que fallan validate_shipment_fields()
------------------------------------------------
total de candidatos evaluados por post_invoke_checks.py
validate_shipment_fields() (guardrails/post_invoke_checks.py, Módulo 4, lección 6) ya produce, para cualquier candidato —real o representativo—, un resultado binario: is_valid True o False. Esto significa que este SLI sí encaja en la fórmula clásica de Google: un "evento bueno" es un candidato que pasa la validación (PASS, escribible en Shipments); un "evento válido" es cualquier candidato que llegó al validador, sin importar de dónde vino. A diferencia de la tasa de escalamiento (SLI 1), aquí "bueno" y "malo" sí tienen un significado inequívoco: un candidato que falla la validación de esquema, por definición, no puede escribirse en Shipments sin corromper el contrato que write_shipment_record() espera.
Tasa de bloqueo = 1 - (candidatos PASS / candidatos evaluados)
La diferencia con el SLI 2 (latencia) es igual de importante: este SLI sí es calculable sin invocar Bedrock, porque validate_shipment_fields() no le importa de dónde vino el candidato que evalúa — le importa, exclusivamente, si tiene la forma correcta. Cualquier conjunto de candidatos representativos, construidos a mano con precisión (como los que el M7.6 va a construir), produce un número real de tasa de bloqueo, con la misma honestidad de etiqueta que cada dato representativo de esta guía ya lleva: el número es real sobre esos candidatos específicos, nunca una medición de qué tan seguido Bedrock, en producción, produciría una respuesta incompleta.
Los tres SLI, en una tabla de referencia rápida
| SLI | Fórmula | ¿Encaja en "bueno/válido"? | ¿Calculable sin Bedrock? | Dónde se calcula en esta guía |
|---|---|---|---|---|
| Tasa de escalamiento | ManifestParseFailed / total procesado | No — es composición, no éxito/fracaso | Sí, siempre | M7.4 (literal) |
| Latencia de inferencia | invocaciones bajo el umbral / total invocado | Sí, en teoría | No — depende de una invocación real | M7.7 (nombrado, sin dato) |
| Tasa de bloqueo del guardrail | candidatos FAIL / candidatos evaluados | Sí | Sí, sobre cualquier candidato, real o representativo | M7.6 (sobre el arnés) |
Errores comunes
Tratar un ManifestParseFailed como un "error" en el mismo sentido que un KeyError sin capturar (de confundir composición con fallo). Qué pasa: alguien, al ver la tasa de escalamiento subir, reacciona como si el sistema estuviera fallando, de la misma forma que reaccionaría ante un salto en AWS/Lambda/Errors. Cómo detectarlo: si tu primera reacción ante un aumento en la tasa de escalamiento es "hay que arreglar el bug". Cómo corregirlo: un ManifestParseFailed es, por diseño, el comportamiento correcto del sistema frente a un manifiesto que no tiene el formato esperado — la sección de esta lección sobre "por qué esta fórmula no es 'eventos buenos ÷ eventos válidos'" desarrolla exactamente esta distinción. Un aumento sostenido merece investigación (¿cambió algo en el mundo real?), no una corrección de código como primera hipótesis.
Intentar calcular la tasa de bloqueo del guardrail sobre invocaciones reales de Bedrock, en vez de sobre cualquier candidato disponible (de subestimar lo que validate_shipment_fields() necesita). Qué pasa: alguien concluye que este SLI, como la latencia, también necesita una invocación real para calcularse. Cómo detectarlo: si tu plan para medir la tasa de bloqueo incluye la frase "primero necesito que Bedrock responda de verdad". Cómo corregirlo: validate_shipment_fields() es código Python puro, sin ninguna dependencia de Bedrock — acepta cualquier dict, venga de donde venga. El M7.6 de este módulo va a construir un conjunto de candidatos representativos, precisamente para demostrar que este SLI sí produce un número real sin necesitar ninguna invocación.
Buscar un SLO explícito para los tres SLI de esta lección, y frustrarse al no encontrarlo (de expectativa de alcance). Qué pasa: alguien, acostumbrado a que sre-and-incident-response-guide, Módulo 2 defina tanto el SLI como el SLO para cada métrica, busca en esta lección un número objetivo ("la tasa de escalamiento debería ser menor a X%"). Cómo detectarlo: si tu pregunta después de esta lección es "¿y cuál es el SLO?". Cómo corregirlo: este módulo, deliberadamente, se detiene en la definición del SLI — fijar un SLO con sentido para una carga con tan pocos datos reales (esta guía nunca invoca Bedrock) sería inventar un número sin la evidencia que sre-and-incident-response-guide, Módulo 2, lección 5 exigió antes de elegir cualquier SLO real. El M7.4 sí produce un número real de tasa de escalamiento — el primer paso honesto hacia, eventualmente, poder fijar un SLO defendible, no el SLO en sí.
Ejercicios
Ejercicio 1 — Clasifica cada uno de los tres SLI de esta lección según si "más alto" es mejor, peor, o ninguna de las dos cosas por sí solo. Justifica cada clasificación con la definición exacta de esta lección.
Ver solución
Tasa de escalamiento: ninguna de las dos cosas por sí solo — no es un SLI de "más alto es mejor" ni "más bajo es mejor" en el sentido moral; es una señal de composición de carga. Un valor estable es lo esperado; un salto repentino es la señal, no el valor absoluto. Latencia de inferencia: más bajo (más rápido, más invocaciones bajo el umbral) es mejor, exactamente como cualquier SLI clásico de latencia. Tasa de bloqueo del guardrail: más bajo es mejor — un bloqueo significa que una extracción no pudo escribirse en Shipments, así que menos bloqueos, sobre el mismo volumen, es una señal positiva. Nota que dos de los tres sí encajan en la intuición simple "más bajo es mejor"; solo la tasa de escalamiento rompe esa intuición, exactamente el punto central de esta lección.
Ejercicio 2 — Un compañero propone: "midamos la tasa de escalamiento con la misma fórmula de disponibilidad de process-shipment-manifest: consideremos ManifestParseFailed como 'evento malo' y calculemos 1 menos esa proporción como nuestro SLI de 'salud del parser'." ¿Qué problema tiene esa propuesta, más allá de la etiqueta?
Ver solución
El problema no es solo de nombre — es que esa fórmula mediría, con precisión matemática perfecta, algo que no le corresponde medir a process-shipment-manifest: la variabilidad del formato de entrada de los socios logísticos de Andes Cargo, un factor externo, fuera del control del código. Un SLI de "salud" implícitamente sugiere que un valor bajo requiere una corrección del sistema — pero la corrección real, si la tasa de escalamiento sube, casi nunca es "arreglar parse_manifest()" (esa función cumple su contrato perfectamente, según el Módulo 1, lección 3), es investigar qué cambió del lado del socio logístico, o decidir, con datos, si vale la pena ampliar lo que el parser reconoce directamente. Nombrar esto como "salud" sesgaría la investigación hacia la respuesta equivocada desde el primer momento.
Ejercicio 3 — Explica por qué la tasa de bloqueo del guardrail (SLI 3) sí puede calcularse hoy, con datos reales, mientras que la latencia de inferencia (SLI 2) no puede, aunque ambas usen la misma fórmula clásica de "eventos buenos ÷ eventos válidos". ¿Qué distingue a los datos que cada una necesita?
Ver solución
La diferencia está en qué produce el numerador y el denominador de cada fórmula. La tasa de bloqueo necesita candidatos —diccionarios de Python con las claves de ShipmentFields— y validate_shipment_fields() es indiferente a la procedencia de esos diccionarios: puede evaluar uno escrito a mano exactamente igual que evaluaría uno que viniera de una invocación real, porque su trabajo es verificar forma, no origen. La latencia, en cambio, mide el tiempo que tomó una invocación específica — un dato que, por definición, solo existe si esa invocación ocurrió de verdad; no hay ninguna forma de "construir a mano" un valor de latencia sin inventarlo, y esta guía, con la misma disciplina de toda la sección de honestidad, se niega a presentar un número inventado como si fuera una medición real.
Resumen y siguiente paso
Esta lección definió, con fórmula y fuente citada, los tres SLI nuevos de este módulo: la tasa de escalamiento (composición de tráfico, no bueno/malo, calculable sin Bedrock), la latencia de inferencia (el molde clásico de Google SRE, sin datos reales que lo llenen en este laboratorio), y la tasa de bloqueo del guardrail propio (el molde clásico también, y sí calculable, porque validate_shipment_fields() no depende de ninguna invocación real). Viste, con cuidado, por qué la tasa de escalamiento es la única de las tres que rompe la intuición simple de "eventos buenos ÷ eventos válidos" — y por qué eso no la hace menos importante como señal operativa.
Antes de avanzar deberías poder: escribir la fórmula exacta de los tres SLI sin ayuda; explicar por qué un ManifestParseFailed no es un "evento malo"; y predecir cuáles de los tres son calculables hoy, sin ninguna invocación real de Bedrock.
La lección 3 vuelve a terreno ejecutable: el logging estructurado real de extract-shipment-manifest-fields, y las métricas de CloudWatch —reales en su definición, representativas en este entorno específico— que alimentarían, en producción, cada uno de los tres SLI de esta lección.
Recursos
- Google — The Art of SLOs (Participant Handbook) — la fórmula clásica de SLI, la misma fuente que
sre-and-incident-response-guide, Módulo 2, lección 3 ya citó. - Google — SRE Workbook, Implementing SLOs — fuente exacta de "the proportion of requests that were faster than some threshold", la definición de SLI de latencia usada en el SLI 2 de esta lección.
- AWS Docs — Monitor
bedrock-runtimeinference using CloudWatch metrics — fuente deInvocationLatency, el nombre exacto de la métrica que el SLI 2 de esta lección necesitaría, desarrollada con precisión completa en la lección 7. - Este mismo curso, Módulo 1, lección 3 — el origen de
ManifestParseFailedyparse_manifest(), la fuente completa del SLI 1 de esta lección. - Este mismo curso, Módulo 4, lección 6 — el origen de
validate_shipment_fields()ySHIPMENT_FIELDS_SCHEMA, la fuente completa del SLI 3 de esta lección.