Skip to content

English · Español

02 — Estrategias de tráfico: A/B vs Shadow vs Canary

🇪🇸 Tres estrategias, tres preguntas distintas. A/B = ¿cuál corrige mejor en producción? Shadow = ¿la nueva versión es segura sin que el aprendiz lo note? Canary = ¿puedo desplegar progresivamente con rollback rápido? Confundirlas envía correcciones gramaticales no validadas a aprendices reales — y para un tutor de gramática, una corrección equivocada es peor que ninguna.


Las tres estrategias, en una tabla

Estrategia Lo que ve el aprendiz Lo que se loggea Criterio de decisión Perfil de riesgo
A/B A o B (split aleatorio por hash) Ambas respuestas + grading offline después Test estadístico sobre el grading Alto — ambas versiones tocan aprendices reales
Shadow Siempre A (producción) A servida + B computada pero descartada; diff offline Comparación offline; sin métrica online Bajo — el aprendiz nunca ve la corrección de B
Canary A o B (X% a B, escalando) Todas las respuestas con su assignment Métrica online sobre el subconjunto de B; auto-rollback si la métrica cae Medio — radio de impacto limitado

Cada estrategia responde a una pregunta distinta. Borja debe internalizar que las estrategias no son intercambiables — elegir la equivocada es una clase-de-incidente en la industria.

A/B testing

Pregunta: "Dados dos candidatos a tutor de gramática A y B en producción, ¿cuál produce un mejor resultado?"

Mecanismo: cada frase entrante en inglés se asigna a A o B hasheando (request_id, salt) mod 2. Se sirven ambas respuestas (una a cada chunk de usuarios). Alguna métrica del lado del usuario (si se aceptó la corrección, si el usuario reenvió una frase revisada, si descartó la sugerencia) se loggea. Tras suficientes muestras, un test estadístico (test z de dos proporciones para resultados binarios, test t para continuos) decide.

Fortalezas: señal causal inequívoca. La aleatorización es el diseño.

Debilidades:

  1. Ambas versiones se envían. Bugs en B los observan aprendices reales. No es apropiado para validar un tutor nuevo — es apropiado para elegir entre dos tutores ya validados.
  2. Prerequisitos del test estadístico. La métrica tiene que ser medible por petición, el tamaño de muestra suficientemente grande y el análisis pre-especificado (no p-hacked).
  3. Problemas de tests múltiples. Si haces A/B de muchas cosas a la vez, la tasa de falsos positivos se compone. Hace falta corrección de Bonferroni o tests secuenciales.

Para el tutor de gramática, la métrica de usuario es difícil de definir online — "¿la corrección fue correcta?" requiere referenciar la tabla canónica de conjugación, no un click del aprendiz. Un aprendiz podría descartar una corrección correcta porque no esté de acuerdo, o aceptar una incorrecta porque se fíe de la herramienta. A/B es por tanto útil aquí sobre todo para comparaciones de latencia y disponibilidad, no de corrección.

Tráfico shadow

Pregunta: "¿Es seguro promocionar al tutor candidato B? ¿Están sus correcciones a la par con producción A, más o menos algún diff tolerable?"

Mecanismo: cada frase entrante va a A y también es computada por B en paralelo. La corrección de A se sirve al aprendiz. La corrección de B se loggea para análisis offline. El aprendiz nunca ve B.

Fortalezas:

  1. Riesgo cero de cara al usuario. B puede estar arbitrariamente roto — el aprendiz solo ve A jamás.
  2. Diffabilidad. Ves exactamente dónde discrepan A y B. Para corrección gramatical, eso es una lista: "en estas 47 frases de entrada, A dijo 'goed → went', B dijo 'goed → goen'." Más fácil de triagear que una caída de métrica.
  3. No requiere maquinaria de tests estadísticos. Solo diff exacto + grading contra la tabla canónica de conjugación de la Fase 20.

Debilidades:

  1. Cómputo 2×. Cada petición pasa por ambos modelos. Para un stack de inferencia ya cerca de su capacidad, hacer shadow duplica el coste.
  2. Sin señal online. Puedes verificar que B discrepa de A el 7% del tiempo, pero no puedes decir si esas discrepancias son mejoras o regresiones sin comparación offline contra referencia (cosa que el eval set de la Fase 20 provee — siempre tenemos ground truth para gramática).
  3. Drift de estado. Si A y B tienen tokenizadores distintos (p. ej., B es un LoRA sobre un tokenizador base distinto), el diff incluye ruido por la tokenización, no solo por el comportamiento del modelo. Imponemos mismo tokenizador para los pares A/B/shadow.

Para el tutor de gramática, shadow es el default para evaluación de modelo nuevo. Servir una corrección de conjugación errónea a un aprendiz que intenta aprender es un daño real; el cómputo 2× es barato en comparación.

Deploys canary

Pregunta: "¿Puedo desplegar progresivamente el tutor B con un rollback rápido si algo va mal?"

Mecanismo: empezar con 99% A, 1% B. Tras T₁ minutos sin regresión en la métrica, mover a 95/5. Luego 80/20, luego 50/50, luego 100% B. En cada paso, una guarda automática de métrica (¿sube la refusal rate? ¿sube la p99 de latencia? ¿sube la tasa de errores?) dispara un rollback automático — vuelta a 99% A.

Fortalezas:

  1. Radio de impacto acotado. Al 1%, un bug catastrófico en B afecta al 1% de aprendices. Para el 50%, el bug debería haberse detectado ya.
  2. Reversibilidad. El rollback es un flip de configuración, no un redeploy.
  3. Señal online. A diferencia de shadow, el rollout produce métricas reales de cara al usuario sobre B (refusal rate, latencia, tasa de errores).

Debilidades:

  1. Requiere métricas online que no necesiten ground truth. Para el tutor de gramática, las métricas online son: refusal rate, latencia, tasa de errores, longitud media de respuesta. La corrección sigue requiriendo grading offline. Canary captura regresiones operacionales, no regresiones de corrección.
  2. Lento. Un canary típico toma 30–120 minutos por etapa. Para deploys frecuentes, esto se convierte en un cuello de botella.
  3. Confundido con el tiempo. Un pico de latencia durante el canary podría deberse a B o a la carga de esa hora. Mantener A como control durante el canary mitiga esto.

Para el tutor de gramática, canary es el mecanismo de rollout de producción después de que shadow haya validado B. No es un sustituto de shadow.

El playbook combinado

El flujo previsto para el tutor de gramática (y la mayoría de sistemas reales) es:

1. Offline: entrenamiento + eval de la Fase 20. Pasa → candidato.
2. Gate de deploy en CI: la precisión de conjugación por bucket no regresa vs eval_baseline.json. Pasa → promociona a semver "candidate".
3. Shadow: el 100% del tráfico de producción se espeja a B durante ≥ 1 semana. Diff offline contra la tabla canónica de conjugación.
4. Revisión del diff offline. Pasa → listo-para-canary.
5. Canary: 1% → 5% → 20% → 50% → 100% durante horas/días, con guardas automáticas de métrica (latencia, refusal, tasa de errores).
6. Promoción: B es ahora producción. La antigua A entra al registry como histórica.
7. A/B (opcional): para *elegir* entre dos tutores igualmente validados, corre un A/B sobre una métrica online clara (latencia, refusal rate, o proxy de aceptación del lado del aprendiz).

Esta pipeline captura modos de fallo distintos en cada etapa:

  • Etapa 1 captura regresiones del eval set.
  • Etapa 2 captura regresiones por bucket invisibles a las métricas agregadas ("la precisión global se mantuvo, pero la precisión en past participle de tercera persona singular cayó 8pp").
  • Etapa 3 captura regresiones de distribución de producción invisibles al eval (el eval set tiene los 20 verbos por igual; el tráfico real puede estar sesgado hacia "go", "have", "be").
  • Etapa 5 captura regresiones operacionales (memory leaks, picos de latencia, incompatibilidades de infraestructura).
  • Etapa 7 captura diferencias de preferencia de usuario.

Saltarse cualquier etapa significa que un modo de fallo queda sin monitorización.

Mecánica de enrutamiento

Las tres estrategias necesitan un router — código que decide, por petición, qué modelo la maneja. Lo añadimos en src/miniserve/traffic.py (extendiendo el módulo de la Fase 33 — ver PHASE_38_PLAN.md §3 sobre la restricción de "sin módulo nuevo"). Restricciones de diseño:

  1. Stateless. La decisión del router es hash(request_id, salt) mod denominador. Sin base de datos, sin estado de sesión. Esto hace al router trivialmente replicable a través de N workers de inferencia.
  2. Sticky. Un request_id dado siempre se enruta al mismo modelo dentro de una sesión. De lo contrario las comparaciones A/B se contaminan por usuarios que ven ambas.
  3. Configurable por ruta. Distintos endpoints pueden tener estrategias distintas. /v1/grammar/correct corre shadow durante la ventana de rollout; /v1/latency-test corre A/B para estudios de benchmark.
  4. Observable. Cada decisión del router se loggea con el trace ID. El miniobserve de la Fase 34 añade un atributo de span traffic.arm y un counter traffic_assignments_total{arm,strategy}.

El router no hace load balancing, retries ni circuit-breaking — eso pertenece al pipeline de peticiones existente de src/miniserve/ (Fase 33). El router solo decide qué modelo maneja una petición que ya ha sido enrutada a un worker.

Antipatrón: "soft launch"

Un error frecuente en la industria: "vamos a desplegar B a todo el mundo durante 5 minutos a ver si rompe algo". Esto no es ni A/B (sin comparación), ni shadow (los usuarios ven B), ni canary (sin rollout progresivo). Es una plegaria. El playbook de la Fase 38 lo descarta explícitamente. El gate de CI (theory/05) es la defensa estructural — la promoción sucede a través de CI, no a través de un deploy manual. No hay un camino "deploy y observar".

Qué significan en realidad las "guardas de métrica"

El rollback automático para canary requiere guardas — predicados sobre un stream de métrica que, si se violan, disparan un flip. Ejemplos para el tutor de gramática:

  • p99_latency_increase > 30% sobre una ventana de 5 minutos vs el baseline de A → rollback.
  • error_rate_5xx > 0.5% sobre una ventana de 1 minuto → rollback inmediato.
  • refusal_rate_delta > 5pp (puntos porcentuales) sobre 10 minutos → rollback. Un pico de respuestas "no puedo determinar la forma correcta" es señal de que B ha perdido capacidad.
  • avg_response_length_delta > 50% sobre 10 minutos → rollback. Respuestas salvajemente más largas pueden indicar un bucle de generación.

Las guardas se codifican en un canary_config.yaml (cargado por src/miniserve/) y no se negocian a mitad del rollout. Se versionan antes de que empiece el rollout y se revisan en el mismo PR que sube el semver del candidato.

Stickiness y experiencia del aprendiz

Un tutor de gramática a menudo ve al mismo aprendiz enviar varias frases en una sesión. La asignación sticky asegura que un aprendiz siempre vea el mismo tutor — de lo contrario podría ver "I went" aceptado por A en la frase 1, luego "I goed" aceptado por B en la frase 2, luego "I went" otra vez en la frase 3. La inconsistencia es peor que cualquiera de los tutores por sí solo.

El salt es por deploy: prod-2026-MM. Subir el salt vuelve a barajar la asignación, lo cual a veces se desea (p. ej., para romper la correlación entre un aprendiz específico y un brazo específico en A/Bs repetidos). No se desea durante un único experimento.

Drill problems (resuélvelos antes del lab 01)

Soluciones en solutions/02-traffic-ref.md — escritas en la apertura de la fase.

  1. Un nuevo tutor de gramática LoRA (Fase 28) tiene 5pp mejor precisión de conjugación sobre el eval de la Fase 20. ¿Qué estrategia usas para validarlo en producción, y en qué secuencia? Justifica.
  2. Un bug en src/miniserve/traffic.py invierte el hash, así que los aprendices ven a veces A y a veces B en la misma sesión. ¿Qué métrica queda corrupta y cómo lo detectas desde los logs?
  3. Un canary al 5% reporta una caída de 2pp en refusal rate. ¿Es una regresión o una mejora? ¿Qué datos adicionales necesitas para decidir?
  4. Estás corriendo shadow con A como producción y B como candidato. En una frase particular "She has went to the store", A dice "correcto" (mal — debería marcar "has went" → "has gone"), B dice "She has gone to the store" (bien). El log del diff lo captura. ¿Qué cambia en tu decisión de promoción respecto a un caso donde ambos modelos están de acuerdo?

Recap de un párrafo

A/B responde "¿cuál es mejor?"; shadow responde "¿es seguro el candidato?"; canary responde "¿puedo rollar con rollback?". No son intercambiables. El playbook combinado es gate de CI → shadow → canary → promoción, opcionalmente seguido de A/B para elecciones. El router (en src/miniserve/traffic.py) es stateless, sticky y configurable por ruta. Las guardas de métrica en canary son predicados pre-comprometidos, no negociaciones durante el rollout. La señal de corrección del tutor de gramática es fundamentalmente offline (contra la tabla canónica de conjugación) — canary captura regresiones operacionales, shadow captura regresiones de corrección.

Siguiente: theory/03-drift-detection.md.