Módulo 2: Feature Flags
El kill switch: apagar al instante, sin redeploy
Descripción
isEnabled(userId, flag) ya sabe exponer recommendations a exactamente un 10% de compradores, de forma estable. Pero un porcentaje bien calculado no sirve de nada si, cuando algo sale mal —el guardrail de latencia se rompe, un error empieza a afectar checkouts reales—, no existe una forma de apagar todo de inmediato. Esta lección construye esa pieza: el kill switch, el mecanismo dentro del mismo flag que apaga a absolutamente todos los usuarios al instante, sin importar el valor de rolloutPercent, y sin un nuevo deploy.
Conexión con el módulo. El kill switch no es una función nueva — es una propiedad que isEnabled() ya tenía desde la lección 4, en la primera línea: if (!flag.enabled) return false. Esta lección le da nombre a esa línea, explica por qué va primero, y la ejecuta en el escenario donde de verdad importa: en medio de un incidente. La lección 6 va a nombrar el tipo de flag —operacional— que existe específicamente para esto.
Una analogía: el botón rojo, contra desconectar el enchufe
Una máquina industrial peligrosa tiene, típicamente, dos formas de apagarla. Una es el procedimiento normal: seguir la secuencia correcta, esperar a que cada parte se detenga en orden, un proceso que puede tomar varios minutos y que está diseñado para el funcionamiento cotidiano. La otra es el botón rojo de emergencia: una sola acción, sin secuencia, sin esperar nada — presionarlo corta la energía de inmediato, sin importar en qué paso del proceso normal estaba la máquina en ese momento.
rolloutPercent es el procedimiento normal: sube y baja con cuidado, en pasos calculados, parte del funcionamiento cotidiano de un lanzamiento. enabled: false es el botón rojo: no negocia con el porcentaje, no espera a que termine ningún proceso, no le importa si rolloutPercent está en 1% o en 90% — corta la exposición para todos, en el instante en que se activa. Ningún operador de una máquina industrial confundiría el botón rojo con "bajar la velocidad un poco"; de la misma forma, el kill switch de un feature flag no es "bajar rolloutPercent" — es apagarlo por completo.
Ejemplo trabajado: el kill switch en medio de un rollout real
Vamos a simular la mañana de un rollout de recommendations, con el mismo isEnabled() de la lección 4, sin cambiarle una sola línea:
function hashUserId(userId) {
let hash = 0;
for (let i = 0; i < userId.length; i++) {
hash = (hash * 31 + userId.charCodeAt(i)) % 100;
}
return hash;
}
function isEnabled(userId, flag) {
if (!flag.enabled) return false; // el kill switch se revisa PRIMERO, antes que rolloutPercent
const bucket = hashUserId(userId + flag.name);
return bucket < flag.rolloutPercent;
}
const buyers = ['buyer-ana', 'buyer-bruno', 'buyer-carla', 'buyer-diego', 'buyer-elena',
'buyer-fabio', 'buyer-gina', 'buyer-hugo', 'buyer-irene', 'buyer-julio'];
const recommendationsFlag = { name: 'recommendations', enabled: true, rolloutPercent: 10 };
console.log('=== 09:00 -- rollout normal al 10% ===\n');
buyers.forEach((id) => console.log(id.padEnd(14) + '-> ' + isEnabled(id, recommendationsFlag)));
console.log('\n=== 09:14 -- el guardrail de latencia se rompe, el equipo activa el kill switch ===');
recommendationsFlag.enabled = false; // UN SOLO cambio, sin deploy: enabled pasa de true a false
console.log('\n=== 09:14:01 -- los mismos 10 compradores, un segundo despues ===\n');
buyers.forEach((id) => console.log(id.padEnd(14) + '-> ' + isEnabled(id, recommendationsFlag)));
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== 09:00 -- rollout normal al 10% ===
buyer-ana -> false
buyer-bruno -> false
buyer-carla -> false
buyer-diego -> false
buyer-elena -> false
buyer-fabio -> false
buyer-gina -> false
buyer-hugo -> false
buyer-irene -> true
buyer-julio -> false
=== 09:14 -- el guardrail de latencia se rompe, el equipo activa el kill switch ===
=== 09:14:01 -- los mismos 10 compradores, un segundo despues ===
buyer-ana -> false
buyer-bruno -> false
buyer-carla -> false
buyer-diego -> false
buyer-elena -> false
buyer-fabio -> false
buyer-gina -> false
buyer-hugo -> false
buyer-irene -> false
buyer-julio -> false
Fíjate en buyer-irene, la única que veía recommendations a las 09:00. Un segundo después de activar el kill switch, su respuesta cambió de true a false — sin que nadie tocara hashUserId(), sin que nadie recalculara su bucket, sin ningún deploy entre las dos corridas. Lo único que cambió en todo el ejemplo fue una sola línea: recommendationsFlag.enabled = false. Ese es el kill switch funcionando exactamente como debe: una sola acción, con efecto inmediato sobre absolutamente todos los usuarios, sin importar cuál era su rolloutPercent en ese momento.
Por qué el orden de las líneas en isEnabled() no es casualidad
Vuelve al cuerpo de isEnabled(): la primera línea revisa flag.enabled, y solo si esa condición pasa se llega a calcular el bucket y compararlo con rolloutPercent. Ese orden es la razón exacta por la que el kill switch funciona como un botón rojo y no como "bajar un poco la velocidad": si enabled fuera false y la función siguiera calculando el bucket de todos modos, tendrías que confiar en que cada llamada en cada parte del código respeta ese resultado correctamente — un lugar más donde un error de programación podría dejar pasar a alguien que no debería pasar. Con el chequeo de enabled como la primera línea, sin excepciones, no existe ningún camino dentro de la función que pueda devolver true cuando el flag está apagado. El kill switch no es una sugerencia que el resto del código tiene que respetar — es una garantía estructural, construida en el punto de entrada de la función misma.
Esto también explica por qué el kill switch tiene que vivir en el mismo campo (enabled) que ya controlaba el todo-o-nada de la lección 2, y no en un campo aparte como killed: true. Si el apagado de emergencia dependiera de revisar dos campos distintos (enabled y killed), habría dos formas de que algo saliera mal: alguien podría activar killed: true sin darse cuenta de que enabled seguía en true, o viceversa. Un solo campo, revisado en una sola línea, al principio de la función, es lo que hace que "apagar todo" sea, literalmente, imposible de hacer a medias.
Errores comunes
Un flag sin kill switch real: apagar significa "bajar rolloutPercent a 0". Qué pasa: un equipo construye isEnabled() sin el chequeo de enabled al principio, y cuando necesita apagar la feature de emergencia, la única palanca disponible es poner rolloutPercent: 0 — lo que técnicamente funciona, pero deja a cualquier código que dependa de la lógica exacta de comparación (bucket < rolloutPercent) expuesto a errores sutiles, como un bucket calculado como número negativo por un bug en otra parte del sistema. Por qué pasa: si el kill switch nunca se diseñó como una pieza aparte, "bajar el porcentaje a cero" se siente como una forma equivalente de apagar todo, sin serlo estructuralmente. Cómo detectarlo: la pregunta correctora es "¿el apagado depende de la misma lógica de comparación que el rollout normal, o de un chequeo separado e incondicional?" Si es lo primero, no hay garantía real. Cómo corregirlo: como en el ejemplo de esta lección, el kill switch tiene que ser un chequeo que se resuelve antes, e independientemente, de cualquier cálculo de porcentaje — exactamente el if (!flag.enabled) return false que abre la función.
Tener el kill switch construido, pero nunca haberlo probado en un escenario real. Qué pasa: el código de isEnabled() tiene el chequeo de enabled correctamente escrito, pero nadie en el equipo alguna vez lo activó fuera de un ambiente de pruebas — y cuando hace falta usarlo de verdad, a las tres de la mañana, aparece un detalle no anticipado: el cambio tarda varios minutos en propagarse a todos los servidores, o nadie recuerda en qué panel se activa. Por qué pasa: construir el mecanismo se siente como el trabajo terminado, sin considerar que un botón nunca presionado es, en la práctica, una promesa sin verificar — el mismo error que ya nombró el módulo 1 sobre el rollback en general. Cómo detectarlo: si nadie en el equipo puede decir la última vez que se activó el kill switch de recommendations en un ambiente parecido a producción, la respuesta a "¿está probado?" es no. Cómo corregirlo: probar el kill switch —como hace el mini-proyecto de este módulo— antes de que haga falta usarlo en un incidente real, no la primera vez que las cosas ya salieron mal.
Confundir "activar el kill switch" con "arreglar el problema". Qué pasa: después de apagar recommendations con el kill switch, el equipo respira aliviado y no vuelve a tocar la latencia rota que causó el problema en primer lugar, tratando el apagado como si fuera la solución final. Por qué pasa: el kill switch resuelve la emergencia inmediata —nadie más está expuesto—, y esa sensación de "ya está resuelto" es fácil de confundir con que el problema de fondo también quedó resuelto. Cómo detectarlo: si después de activar el kill switch nadie tiene asignado investigar la causa raíz, el problema sigue exactamente donde estaba, solo que sin usuarios expuestos por ahora. Cómo corregirlo: el kill switch compra tiempo —exactamente como el canary del módulo 1 contenía el radio de impacto sin arreglar el bug—; la investigación completa del incidente, con su propio proceso, es exactamente el territorio del módulo 5 de esta guía.
Ejercicios
Ejercicio 1 — Verifica el orden. Reescribe mentalmente isEnabled() intercambiando el orden de las dos líneas —primero calculando bucket y comparándolo con rolloutPercent, y solo al final revisando flag.enabled—. ¿El resultado final para cada usuario cambia? ¿Qué sí cambia, aunque el resultado sea el mismo?
Ver solución
Si la lógica se escribe con cuidado (devolviendo false en cualquiera de las dos condiciones que falle), el resultado final para cada usuario puede terminar siendo el mismo. Lo que cambia es la garantía estructural: con el chequeo de enabled al final, existe una ruta en el código donde se calcula el bucket sin necesidad —trabajo de más en cada request— y, más importante, cualquier futura modificación de la función corre el riesgo de que alguien agregue una nueva condición antes del chequeo de enabled y accidentalmente la salte. Poner enabled primero, como en el ejemplo de esta lección, no es solo una cuestión de resultado — es una cuestión de que sea estructuralmente imposible ignorar el kill switch, sin importar qué se agregue después a la función.
Ejercicio 2 — Diseña la prueba del kill switch. Escribe, en pseudocódigo o JavaScript, una función testKillSwitch(flag, userIds) que reciba un flag ya enabled: true con un rolloutPercent cualquiera, lo apague, y devuelva true solo si absolutamente ningún usuario de la lista userIds queda con isEnabled() === true después de apagarlo.
Ver solución
function testKillSwitch(flag, userIds) {
const killed = { ...flag, enabled: false };
return userIds.every((id) => isEnabled(id, killed) === false);
}
console.log(testKillSwitch(recommendationsFlag, buyers)); // true
Nota el uso de { ...flag, enabled: false } en vez de mutar flag directamente — así la prueba no deja el flag original en un estado apagado después de correr, algo importante si esta prueba se ejecuta como parte de una suite automatizada junto con otras pruebas que esperan el flag en su estado normal. Esta es, en esencia, la prueba que cualquier sistema de flags en producción debería correr regularmente, para confirmar que el kill switch sigue funcionando antes de necesitarlo de verdad.
Ejercicio 3 — El botón rojo, en tus palabras. Sin usar la palabra "kill switch" ni "flag", explica en dos o tres frases a un compañero de otro equipo por qué Mercado necesita una forma de apagar recommendations que sea completamente independiente de cuánta gente lo estaba viendo en ese momento.
Ver solución
Un ejemplo de respuesta: "Necesitamos un botón que apague la feature al instante para todos, sin importar si estaba mostrándose al 1% o al 90% de la gente en ese momento — porque en una emergencia real no hay tiempo para calcular con cuidado un nuevo porcentaje más bajo, y cualquier lógica adicional en el camino es una oportunidad más de que algo salga mal justo cuando menos podemos permitírnoslo." La idea central: la emergencia exige simplicidad total —una sola acción, sin condiciones intermedias— precisamente porque el momento en que hace falta usarlo es el peor momento posible para que algo en el mecanismo mismo falle.
Resumen y siguiente paso
En esta lección nombraste y probaste el kill switch: el mismo campo enabled que ya usaba isEnabled(), pero entendido ahora como el botón rojo que apaga a todos al instante, sin importar rolloutPercent, y verificado primero en la función, antes que cualquier otra condición. Con el ejemplo de esta lección viste a buyer-irene pasar de true a false con una sola línea de cambio, sin ningún deploy de por medio.
Antes de avanzar deberías poder: explicar por qué el kill switch tiene que revisarse primero, y no al final, dentro de isEnabled(); distinguir "apagar el kill switch" de "bajar rolloutPercent", aunque ambos reduzcan la exposición; y explicar por qué probar el kill switch, antes de necesitarlo, es tan importante como haberlo construido.
La lección 6 da un paso atrás y clasifica: recommendations no es el único tipo de flag que existe en Mercado — hay flags de experimento, flags puramente operacionales como el que acabas de usar hoy, y flags permanentes. Cada tipo tiene un propósito distinto, y confundirlos tiene consecuencias reales sobre cuánto tiempo debería vivir cada uno.
Recursos
- LaunchDarkly, "What Is a Kill Switch in Software Development?" — launchdarkly.com/blog/what-is-a-kill-switch-software-development. El artículo dedicado por completo al concepto de esta lección: un feature flag usado específicamente para desactivar una funcionalidad al instante, sin rollback ni redeploy. En inglés.
- Pete Hodgson (con Martin Fowler), "Feature Toggles (aka Feature Flags)" — martinfowler.com/articles/feature-toggles.html. Describe los "Ops Toggles" —de los que el kill switch es el ejemplo más claro— como mecanismos operacionales de largo plazo, distintos de los toggles temporales de rollout. En inglés.