Módulo 3: Gradual Rollout

El canary: qué tan chico es demasiado chico

Descripción

La lección 2 mostró la rampa completa, y el canary al 1% pasó limpio — 720ms, bajo el techo. Pero "pasar limpio" y "haber dado una señal confiable" no son, necesariamente, la misma cosa. Esta lección se detiene en la primera etapa de la rampa y contesta una pregunta que la lección anterior dejó sin resolver: ¿qué tan chico puede ser un canary antes de que, aunque el problema exista, simplemente no haya suficiente volumen para verlo?

Conexión con el módulo. Esta lección profundiza en la etapa 1 de la rampa que la lección 2 dibujó completa. No cambia el modelo de rolloutPlan() — lo que agrega es un chequeo previo: antes de confiar en que una etapa "pasó", ¿tenía siquiera el tamaño necesario para que un problema real se hiciera visible? La lección 5 va a retomar el mismo tamaño del canary (1%, 2,500 usuarios) desde el ángulo del radio de impacto; esta lección lo mira desde el ángulo de la señal.

Una analogía: el canario en la mina, otra vez — pero con un solo canario en una mina enorme

Un solo canario avisa bien en una mina chica, donde el gas se concentra rápido en todo el túnel. Pero imagina la misma jaula, con el mismo canario, puesta en la entrada de una mina kilométrica, con el gas filtrándose solo en un rincón lejano. El canario puede seguir cantando perfectamente bien — no porque no haya gas en la mina, sino porque el aire que le llega a él, en la entrada, todavía no se contaminó lo suficiente para que lo note. El canario no está fallando: está haciendo exactamente su trabajo, con la información que le llega. El problema es que la muestra —el aire alrededor de una sola jaula— es demasiado chica para la mina completa.

El canary al 1% de recommendations tiene el mismo límite. 2,500 usuarios expuestos es una muestra real, no un simulacro — pero si la regresión de latencia depende de cuánto tráfico concurrente satura el recurso que la causa (una base de datos, un servicio de recomendaciones bajo presión), es perfectamente posible que 2,500 personas, dispersas en el tiempo, nunca generen la carga necesaria para que el problema se manifieste con la misma fuerza que con 25,000. El canary no mintió. Simplemente no era, todavía, lo bastante grande para esta regresión específica.

Ejemplo trabajado: canarySignal() — cuánto canary hace falta para ver el problema

Vamos a modelar cuántos eventos de guardrail (peticiones que rompen el p95) se esperan observar según el tamaño del canary, usando la misma tasa de regresión (5%) que ya conoces del módulo 1:

// canarySignal: cuantos eventos de guardrail (requests que rompen el p95) se esperan
// observar en una etapa, segun su tamano -- un canary muy chico puede pasar "limpio"
// no porque el problema no exista, sino porque no hubo volumen suficiente para verlo.
// Caso pedagogico: la tasa real de eventos que rompen el guardrail (regressionRate)
// ya se conoce (5%, medida en el experimento de la guia de metricas).
function canarySignal({ totalUsers, percent, regressionRate, minEventsForSignal }) {
  const exposedUsers = Math.round(totalUsers * percent);
  const expectedEvents = Math.round(exposedUsers * regressionRate);
  const hasSignal = expectedEvents >= minEventsForSignal;
  return { percent, exposedUsers, expectedEvents, hasSignal };
}

const totalUsers = 250000; // base de compradores alcanzable por recommendations
const regressionRate = 0.05; // tasa conocida de eventos que rompen el guardrail (guia de metricas)
const minEventsForSignal = 30; // umbral pedagogico: con menos de 30 eventos, la senal es demasiado ruidosa para confiar

const sizes = [0.0001, 0.001, 0.01, 0.10];

console.log('=== canarySignal: cuanto canary hace falta para ver el guardrail roto ===\n');
sizes.forEach((p) => {
  const r = canarySignal({ totalUsers, percent: p, regressionRate, minEventsForSignal });
  console.log((p * 100).toString().padStart(6) + '%  exposedUsers=' + r.exposedUsers.toLocaleString('en-US').padStart(6) +
    '  expectedEvents=' + String(r.expectedEvents).padStart(4) + '  hasSignal=' + r.hasSignal);
});

Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:

=== canarySignal: cuanto canary hace falta para ver el guardrail roto ===

  0.01%  exposedUsers=    25  expectedEvents=   1  hasSignal=false
   0.1%  exposedUsers=   250  expectedEvents=  13  hasSignal=false
     1%  exposedUsers= 2,500  expectedEvents= 125  hasSignal=true
    10%  exposedUsers=25,000  expectedEvents=1250  hasSignal=true

⚠️ minEventsForSignal: 30 es un umbral pedagógico para razonar sobre el problema, no una fórmula estadística formal (eso —calcular el tamaño de muestra necesario con rigor— es territorio de product-metrics-and-experimentation-guide). La idea que importa aquí es cualitativa: con muy pocos eventos esperados, cualquier resultado —bueno o malo— es más ruido que señal.

Fíjate en las dos primeras filas: con un canary de 0.01% (25 usuarios), el modelo espera apenas 1 evento de guardrail — y con un solo evento, no hay forma de distinguir "el problema no existe" de "existe, pero tuviste mala o buena suerte con esos 25 usuarios". Con 0.1% (250 usuarios), 13 eventos esperados siguen siendo pocos para confiar. Recién en 1% —la etapa elegida para el canary de recommendations en la lección 2— el modelo cruza el umbral de 30 eventos con margen (125). Ese 1% no se eligió al azar: es, aproximadamente, el punto donde el canary deja de ser demasiado chico para dar señal confiable sobre esta regresión específica.

Por qué el canary "limpio" de la lección 2 no fue una señal completa

Esto conecta directo con lo que viste en la lección 2: el canary al 1% dio 720ms, bajo el techo — y aun así, el 10% reveló el problema. canarySignal() explica por qué eso no es una contradicción. 1% genera señal suficiente sobre el conteo de eventos (125 esperados, muy por encima del umbral de 30), pero eso no garantiza que la magnitud del problema sea la misma a esa escala. Si la regresión depende de la carga concurrente sobre un recurso compartido —no solo de cuántos usuarios individuales pasan por el código—, un canary chico puede tener volumen suficiente para "ver algo" y aun así no alcanzar el nivel de estrés que sí alcanza una etapa más grande. Esta es la razón práctica, más allá de la teoría, de por qué la rampa tiene varias etapas y no se detiene en "el canary pasó": cada etapa nueva no solo agrega gente, agrega carga, y la carga es, muchas veces, la variable que de verdad importa para este tipo de guardrail.

Errores comunes

Elegir un canary tan chico que, aunque el problema exista, no da señal. Qué pasa: un equipo, ansioso por minimizar el radio de impacto, configura el canary en 0.01% en vez de 1%, razonando que "mientras más chico, más seguro". Por qué pasa: la lección 1 del módulo 1 (radio de impacto) enseña que menos exposición es más seguro, y es fácil extrapolar esa lógica hasta el extremo sin notar que hay un piso mínimo de tamaño necesario para que el canary sirva de algo. Cómo detectarlo: con canarySignal(), el expectedEvents de ese tamaño de canary está muy por debajo del umbral mínimo — como viste en la fila de 0.01% de este ejemplo (1 evento esperado). Cómo corregirlo: el tamaño del canary tiene dos límites en tensión: lo bastante chico para contener el radio de impacto (módulo 1), lo bastante grande para generar señal confiable (esta lección). 1% en el caso de recommendations está pensado para balancear los dos.

Confundir "el canary no reportó ningún problema" con "el problema no existe". Qué pasa: al ver que el canary al 0.1% pasó sin ninguna alerta, alguien concluye que recommendations está lista para el 100%, sin considerar que ese tamaño de canary nunca tuvo volumen suficiente para detectar nada. Por qué pasa: la ausencia de una alerta se lee, por defecto, como "todo bien" — y falta el paso adicional de preguntar si la ausencia de alerta es evidencia real o simplemente falta de datos. Cómo detectarlo: nadie puede contestar "¿cuántos eventos de guardrail esperábamos ver, con este tamaño de canary, si el problema existiera?" — sin ese número, "no vimos nada" no significa nada. Cómo corregirlo: antes de confiar en el silencio de un canary, calcula (como en el ejemplo de hoy) cuántos eventos esperarías ver si el problema existiera — si ese número ya es bajo de por sí, el silencio del canary no es información útil.

Tratar el tamaño del canary como un valor fijo, universal, que sirve igual para cualquier feature. Qué pasa: un equipo reutiliza "siempre empezamos con 1%" para todas las features, sin ajustar según qué tan raro es el problema que intentan detectar. Por qué pasa: tener un número de referencia (1%) es cómodo y evita tener que recalcular cada vez, y la mayoría de las veces funciona razonablemente bien. Cómo detectarlo: si la tasa de regresión esperada de una nueva feature es mucho más baja que el 5% de recommendations —digamos, 0.5%—, el mismo canary de 1% daría muchos menos eventos esperados de guardrail para esa feature. Cómo corregirlo: usa canarySignal() con la tasa de regresión conocida (o estimada) de cada caso concreto — un problema más raro necesita, matemáticamente, un canary más grande para generar la misma cantidad de señal.

Ejercicios

Ejercicio 1 — Calcula el punto de cruce. Con regressionRate: 0.05 y minEventsForSignal: 30 (los mismos del ejemplo), ¿cuál es, aproximadamente, el porcentaje de canary más chico que ya cruza el umbral de señal? Usa canarySignal() mentalmente con un par de valores entre 0.1% y 1% para acotar la respuesta.

Ver solución

Buscamos el percent donde round(250000 * percent * 0.05) >= 30, es decir, 250000 * percent * 0.05 >= 30percent >= 30 / 12500 = 0.0024 (0.24%). Con percent: 0.003 (0.3%): exposedUsers = round(250000 * 0.003) = 750, expectedEvents = round(750 * 0.05) = 38hasSignal: true. Con percent: 0.002 (0.2%): exposedUsers = 500, expectedEvents = 25hasSignal: false. El punto de cruce está entre 0.2% y 0.3% — bastante por debajo del 1% elegido para recommendations, lo que confirma que 1% no es el mínimo posible: tiene margen de sobra (125 eventos esperados contra un umbral de 30), razón de más para confiar en la señal de esa etapa.

Ejercicio 2 — Aplica el modelo a una regresión más rara. Un segundo feature de Mercado, priceAlerts, tiene una tasa de regresión conocida mucho más baja: 0.5% (un evento raro, comparado con el 5% de recommendations). Con el mismo minEventsForSignal: 30, calcula expectedEvents para un canary de 1% sobre la misma base de 250,000 usuarios. ¿Sigue siendo 1% un canary suficientemente grande para esta feature?

Ver solución

exposedUsers = round(250000 * 0.01) = 2,500 (igual que antes). expectedEvents = round(2500 * 0.005) = 1313 < 30hasSignal: false. Para priceAlerts, un canary de 1% no da señal suficiente — la misma etapa que funcionó bien para recommendations (regresión más común) se queda corta para un problema diez veces más raro. Esto confirma el tercer error común de esta lección: el tamaño del canary no es un número universal, depende de la tasa de regresión esperada de cada caso. Para priceAlerts, haría falta subir el canary a, aproximadamente, 10% (expectedEvents = round(25000 * 0.005) = 125, con señal de sobra) — un canary mucho más grande solo para poder confiar en la ausencia de señal.

Ejercicio 3 — Defiende el tamaño del canary por escrito. Un ejecutivo de Mercado pregunta por qué el canary de recommendations no empieza en 0.1%, "para arriesgar todavía menos gente". Escribe la respuesta (60-90 palabras) que le darías, usando los números de este ejemplo.

Ver solución

Una respuesta posible: "Con 0.1% esperaríamos ver apenas 13 eventos de guardrail, muy por debajo de los 30 que consideramos el mínimo para confiar en la señal — si el canary pasara 'limpio' a ese tamaño, no sabríamos si es porque el problema no existe o porque no tuvimos suficiente volumen para verlo. El 1% que elegimos genera 125 eventos esperados, con margen de sobra, y sigue exponiendo a solo 2,500 personas — una fracción mínima de nuestra base de 250,000. Es el punto donde arriesgamos poco Y aprendemos algo real."

Resumen y siguiente paso

En esta lección viste que el canary tiene dos límites en tensión: lo bastante chico para contener el radio de impacto (módulo 1), lo bastante grande para generar señal confiable. Con canarySignal() calculaste que, con la tasa de regresión conocida de recommendations (5%), tamaños de canary por debajo de 0.24% aproximadamente no darían señal suficiente — y que el 1% elegido en la lección 2 tiene margen de sobra (125 eventos esperados contra un umbral de 30).

Antes de avanzar deberías poder: explicar por qué "el canary no reportó nada" no siempre significa "no hay problema"; y calcular, dado un tamaño de canary y una tasa de regresión, si esperas suficiente señal para confiar en el resultado.

La lección 4 toma el criterio que rolloutPlan() usó de forma simple en la lección 2 (p95Latency <= ceiling) y lo examina de cerca: ¿qué hace que un criterio de avance sea real, y qué formas comunes de "criterio" en realidad no lo son?

Recursos

  • Martin Fowler (bliki), "CanaryRelease" — martinfowler.com/bliki/CanaryRelease.html. Nota, en la sección sobre duración, que un canary suele completarse "en minutos u horas" — a diferencia de un A/B test, que puede tardar días en juntar significancia estadística — un matiz que conecta con por qué el tamaño (y no solo el tiempo) importa para la señal. En inglés.
  • LaunchDarkly, "7 Reasons Percentage Rollouts Reduce Deployment Risk" — launchdarkly.com/blog/how-percentage-rollouts-minimize-deployment-risks. Explica, desde la práctica de la industria, cómo un rollout por porcentaje deja "atrapar problemas temprano" — la misma lógica de señal temprana que desarrolla esta lección, aplicada al negocio de feature flags. En inglés.