Módulo 3: Gradual Rollout
El radio de impacto crece con cada etapa
Descripción
El módulo 1 midió el radio de impacto (blastRadius()) comparando tres formas distintas de lanzar recommendations: 100% de golpe, 10%, y un canary al 1%. Esta lección toma esa misma función, sin cambiarla, y la aplica a las cuatro etapas de la rampa completa a la vez — no como alternativas que se comparan entre sí, sino como pasos consecutivos de un mismo rollout. Y le agrega una pregunta nueva que el módulo 1 no contestó: cuando pasas de una etapa a la siguiente, ¿cuánta gente nueva queda expuesta en esa transición específica?
Conexión con el módulo. Esta lección conecta directamente el módulo 1 con este módulo 3: reutiliza blastRadius() exactamente como quedó en la lección 4 de aquel módulo, y la envuelve en blastRadiusPerStage(), aplicada a la misma rampa de cuatro etapas que la lección 2 de este módulo definió. No introduce ningún concepto de rollout nuevo — pone número exacto a algo que la lección 2 mencionó de pasada: que la rampa existe, en parte, porque los saltos entre etapas no son todos del mismo tamaño.
Una analogía: abrir las compuertas de una represa, una a la vez
Una represa no libera toda el agua contenida abriendo todas sus compuertas al mismo tiempo — las abre una por una, y cada compuerta que se abre libera más caudal que la anterior, no porque las compuertas sean más grandes, sino porque el volumen acumulado detrás de ellas ya es mayor. Abrir la primera compuerta, con la represa casi llena, libera un caudal manejable. Abrir la última, cuando ya queda poca agua contenida, apenas cambia nada. La secuencia importa, y el tamaño de cada "salto" de caudal liberado no es uniforme — depende de cuánto había contenido hasta ese punto.
El rollout de recommendations tiene la misma forma, con la exposición de usuarios en el lugar del caudal. Pasar de 1% a 10% libera cierta cantidad de exposición nueva. Pasar de 10% a 50% libera muchísima más — no porque esa etapa sea "más peligrosa" en sí misma, sino porque el salto en usuarios totales es simplemente mayor. Entender dónde están los saltos grandes es tan importante como entender el tamaño total de la base.
Ejemplo trabajado: blastRadiusPerStage() sobre la rampa completa
Aplicamos blastRadius() —sin ningún cambio respecto al módulo 1— a las cuatro etapas de la rampa, y calculamos cuánta gente nueva se expone en cada transición:
// blastRadiusPerStage: reusa blastRadius() exactamente como quedo en el modulo 1
// (leccion 4), pero aplicado a las CUATRO etapas de la rampa completa a la vez, y
// le agrega el INCREMENTO de exposicion entre una etapa y la siguiente -- cuanta
// gente NUEVA queda expuesta al pasar de una etapa a la otra.
function blastRadius({ totalUsers, rolloutPercent, regressionRate }) {
const exposedUsers = Math.round(totalUsers * rolloutPercent);
const affectedUsers = Math.round(exposedUsers * regressionRate);
return { rolloutPercent, exposedUsers, affectedUsers };
}
function blastRadiusPerStage({ totalUsers, regressionRate, stages }) {
let prevExposed = 0;
return stages.map((s) => {
const { exposedUsers, affectedUsers } = blastRadius({ totalUsers, rolloutPercent: s.percent, regressionRate });
const newlyExposed = exposedUsers - prevExposed;
prevExposed = exposedUsers;
return { label: s.label, percent: s.percent, exposedUsers, affectedUsers, newlyExposed };
});
}
const totalUsers = 250000; // misma base de compradores del modulo 1
const regressionRate = 0.05; // misma tasa de regresion conocida
const stages = [
{ label: 'canary 1%', percent: 0.01 },
{ label: 'rollout 10%', percent: 0.10 },
{ label: 'rollout 50%', percent: 0.50 },
{ label: 'rollout 100%', percent: 1.00 },
];
console.log('=== blastRadiusPerStage: la rampa completa de recommendations en Mercado ===\n');
const perStage = blastRadiusPerStage({ totalUsers, regressionRate, stages });
perStage.forEach((s) => {
console.log(s.label.padEnd(14) + 'exposedUsers=' + s.exposedUsers.toLocaleString('en-US').padStart(7) +
' affectedUsers=' + s.affectedUsers.toLocaleString('en-US').padStart(6) +
' newlyExposed=+' + s.newlyExposed.toLocaleString('en-US').padStart(7));
});
const biggestJump = perStage.reduce((max, s) => (s.newlyExposed > max.newlyExposed ? s : max));
console.log('\nEl salto mas grande de exposicion nueva ocurre en "' + biggestJump.label + '" (+' +
biggestJump.newlyExposed.toLocaleString('en-US') + ' personas nuevas) -- no en el primer canary.');
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== blastRadiusPerStage: la rampa completa de recommendations en Mercado ===
canary 1% exposedUsers= 2,500 affectedUsers= 125 newlyExposed=+ 2,500
rollout 10% exposedUsers= 25,000 affectedUsers= 1,250 newlyExposed=+ 22,500
rollout 50% exposedUsers=125,000 affectedUsers= 6,250 newlyExposed=+100,000
rollout 100% exposedUsers=250,000 affectedUsers=12,500 newlyExposed=+125,000
El salto mas grande de exposicion nueva ocurre en "rollout 100%" (+125,000 personas nuevas) -- no en el primer canary.
Mira la columna newlyExposed de arriba abajo: 2,500, luego 22,500, luego 100,000, luego 125,000. Cada salto es más grande que el anterior — y no por poco. El salto de 50% a 100% (+125,000 personas) es cincuenta veces más grande que el salto del canary inicial (+2,500). Esto tiene una consecuencia directa sobre cómo deberías pensar el riesgo de cada etapa: la etapa más delicada de la rampa no es la primera —el canary, con la exposición más chica de todas— es la última, precisamente porque es donde se libera la mayor parte del "caudal" contenido.
Por qué esto cambia cómo piensas los criterios de avance de cada etapa
Esta observación tiene una implicación práctica directa para la lección 4: si el salto de exposición crece con cada etapa, el criterio de avance —y la confianza que necesitas antes de aplicarlo— debería volverse más estricto, no más relajado, a medida que subes por la rampa. Es tentador razonar al revés: "ya llegamos al 50%, ya confirmamos varias veces que todo está bien, avancemos más rápido a partir de aquí" — pero el salto de 50% a 100% es, en términos de gente nueva expuesta, el más grande de toda la rampa. La confianza acumulada en etapas anteriores es información real y valiosa (por algo llegaste hasta ahí), pero no debería traducirse en menos cuidado exactamente en el momento donde el radio de impacto de un error crece más.
Este mismo patrón —saltos que crecen, no que se mantienen constantes— es también un argumento a favor de agregar más etapas intermedias en la parte alta de la rampa, no solo en la parte baja. Una rampa como 1% → 10% → 30% → 60% → 100%, con un escalón adicional entre el 50% y el 100% original, reduciría el tamaño del salto final más grande — al costo, como viste en la lección 2, de una etapa más que coordinar.
Errores comunes
Asumir que el radio de impacto crece de forma pareja entre etapas, en vez de acelerarse. Qué pasa: alguien planea el mismo nivel de revisión y cuidado para el salto de 1% a 10% que para el salto de 50% a 100%, tratando las cuatro transiciones como si fueran equivalentes. Por qué pasa: la rampa se ve, en la lista de porcentajes, como una progresión pareja (1, 10, 50, 100) — no salta a la vista, sin calcularlo, que el número de personas nuevas detrás de cada salto crece de forma muy desigual. Cómo detectarlo: compara la columna newlyExposed entre etapas, como en el ejemplo de hoy — si el equipo no puede decir de memoria cuál transición expone a más gente nueva, probablemente está tratando todas las etapas como iguales. Cómo corregirlo: usa blastRadiusPerStage() para ver el incremento real de cada transición, y ajusta el nivel de cuidado (revisión, dwell time de la lección 7) para que sea mayor, no menor, en los saltos más grandes.
Relajar la vigilancia a medida que se sube por la rampa, por la confianza acumulada en etapas anteriores. Qué pasa: después de que el 10% y el 50% pasan sin problemas, el equipo revisa el guardrail de forma menos rigurosa antes de avanzar al 100%, razonando que "si aguantó hasta acá, va a aguantar el resto". Por qué pasa: la confianza acumulada es una señal real, y es psicológicamente natural relajarse a medida que un plan se va cumpliendo sin sobresaltos. Cómo detectarlo: el tiempo o el esfuerzo dedicado a revisar el guardrail en la última etapa es menor que el dedicado en el canary inicial, a pesar de que la última etapa expone a muchísima más gente nueva. Cómo corregirlo: recuerda que el salto de 50% a 100% es, en este ejemplo, el más grande de toda la rampa (+125,000) — el rigor de revisión debería, si acaso, aumentar en las etapas finales, no disminuir.
Ignorar el radio de impacto de una etapa porque ya se calculó una vez, en la etapa anterior, y "no debería cambiar mucho". Qué pasa: alguien calcula blastRadius() para el canary al 1% al inicio del rollout, y no vuelve a calcularlo para las etapas siguientes, asumiendo que la lógica ya quedó demostrada. Por qué pasa: recalcular en cada etapa se siente redundante si el modelo (blastRadius()) ya se validó una vez. Cómo detectarlo: si preguntas "¿cuántas personas nuevas se exponen en la etapa que estamos por iniciar?" y la respuesta es un encogimiento de hombros o un número de una etapa anterior, el cálculo no se está repitiendo donde importa. Cómo corregirlo: como hace blastRadiusPerStage() en el ejemplo de hoy, calcula el radio de impacto —total y el incremento— de cada etapa específica, cada vez, antes de decidir avanzar a ella.
Ejercicios
Ejercicio 1 — Calcula el incremento de una rampa con más etapas. Retoma la rampa de cinco etapas del ejercicio 2 de la lección 2: 1% → 5% → 10% → 50% → 100%. Calcula exposedUsers para la nueva etapa del 5% (con los mismos totalUsers: 250000 y regressionRate: 0.05), y compara su newlyExposed (respecto al 1% anterior) contra el salto original de 1% a 10% de la rampa de cuatro etapas.
Ver solución
exposedUsers al 5% = round(250000 * 0.05) = 12,500. newlyExposed respecto al 1% (2,500) = 12,500 - 2,500 = 10,000. Comparado con el salto original de 1% a 10% en la rampa de cuatro etapas (+22,500), agregar la etapa intermedia del 5% divide ese salto grande en dos más chicos: +10,000 (de 1% a 5%) y +12,500 (de 5% a 10%, ya que 25,000 - 12,500 = 12,500). Ningún salto individual desaparece —la exposición total al llegar al 10% sigue siendo la misma—, pero cada transición individual queda más contenida, dándole al equipo una oportunidad más de detectar un problema antes de que crezca.
Ejercicio 2 — Encuentra el salto más grande con una regressionRate distinta. Si regressionRate cambiara de 0.05 a 0.02 (una regresión más rara), ¿cambiaría cuál transición tiene el newlyExposed más grande? Justifica tu respuesta sin necesidad de recalcular todo.
Ver solución
No cambiaría. newlyExposed se calcula sobre exposedUsers (que depende solo de totalUsers y rolloutPercent), no sobre affectedUsers (que sí depende de regressionRate). Como regressionRate no aparece en absoluto en el cálculo de exposedUsers, la transición con el salto más grande de exposición —de 50% a 100%, en esta rampa— sigue siendo la misma sin importar qué tan común o rara sea la regresión. Lo que sí cambiaría, proporcionalmente, es affectedUsers en cada etapa: con regressionRate: 0.02 en vez de 0.05, todos los valores de affectedUsers bajarían a un 40% de los originales (125 → 50, 12,500 → 5,000, etc.), pero el patrón de dónde está el salto más grande de exposición no cambia.
Ejercicio 3 — Conecta esta lección con la lección 4. En 3-4 frases, explica por qué el hallazgo de esta lección —que el salto de 50% a 100% es el más grande de la rampa— es un argumento a favor de tener un criterio de avance especialmente estricto (lección 4) justo antes de esa transición, y no solo antes del canary inicial.
Ver solución
El criterio de avance de la lección 4 es lo único que se interpone entre "el guardrail está roto" y "exponer a más gente" en cada etapa. Como el salto de 50% a 100% expone, de una sola vez, a 125,000 personas nuevas —la mitad de toda la base de Mercado—, un criterio débil o mal verificado justo en esa transición tiene el peor costo posible si falla: es exactamente el momento donde una decisión equivocada (avanzar cuando no debería) afecta a la mayor cantidad de gente nueva de toda la rampa. Por eso el rigor del criterio —qué tan bien medido está el guardrail, cuánta evidencia se exige antes de confiar en él— debería ser, si acaso, mayor en esa transición final, no menor solo porque "ya llegamos hasta acá sin problemas".
Resumen y siguiente paso
En esta lección reutilizaste blastRadius() del módulo 1, envuelta en blastRadiusPerStage(), sobre las cuatro etapas completas de la rampa de recommendations: viste que el radio de impacto no crece de forma pareja —el salto de 50% a 100% (+125,000 personas nuevas) es cincuenta veces más grande que el del canary inicial (+2,500)— y por qué eso implica que el cuidado con el que se aplica el criterio de avance debería aumentar, no disminuir, a medida que subes por la rampa.
Antes de avanzar deberías poder: calcular el incremento de exposición (newlyExposed) entre dos etapas cualesquiera de una rampa; y explicar por qué la última etapa de una rampa, no la primera, suele ser la que expone al radio de impacto más grande.
La lección 6 agrega una dimensión que el porcentaje, por sí solo, no cubre: quién, específicamente, ve la feature primero en cada etapa — los deployment rings, del equipo interno al público general.
Recursos
- AWS Well-Architected Framework, Pilar de Confiabilidad, "Implement Change" — docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/implement-change.html. Documentación oficial de AWS que recomienda explícitamente reducir el radio de impacto ("blast radius") a través de despliegues incrementales — la base de por qué cada etapa de la rampa importa por separado. En inglés.
- Google SRE Workbook, Capítulo 16, "Canarying Releases" — sre.google/workbook/canarying-releases. Recomienda que cada etapa sucesiva de un canary tenga una población "más grande" que la anterior — la referencia formal de por qué los saltos de exposición de esta lección crecen por diseño. En inglés.