¿Cómo permiten los feature flags la entrega progresiva, y cómo se implementa uno de forma económica?
Los feature flags separan el despliegue del código de su liberación a los usuarios, y un flag de rollout por porcentaje sólido no es más que un hash determinista y una comparación.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo permiten los feature flags la entrega progresiva, y cómo se implementa uno de forma económica?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Desplegar no es lo mismo que liberar
La mayoría de los equipos aprende a repetir la frase "los feature flags separan el despliegue de la liberación" mucho antes de entender qué gana con eso. Desplegar es un acto puramente técnico: el código nuevo llega a las máquinas y empieza a ejecutarse. Liberar es una decisión de negocio: qué usuarios pueden ver o usar ese código, y cuándo. Sin un flag, esos dos eventos están obligados a ocurrir en el mismo instante exacto: en el momento en que termina tu pipeline de CI/CD, todos los usuarios del planeta reciben el comportamiento nuevo, esté terminado o no, esté despierto tu ingeniero de guardia o no, y haya anunciado algo marketing o no. Un feature flag no es más que un if respaldado por un valor configurable en tiempo de ejecución, pero esa costura es justo lo que te permite desplegar código de forma continua mientras controlas la exposición de manera independiente.
La entrega progresiva es lo que se construye sobre esa costura. En lugar de un simple encendido/apagado, expones el código nuevo a una porción pequeña y creciente de tráfico real, y observas métricas reales antes de avanzar. Las formas más comunes son:
- Rollout por porcentaje — exponer el flag al 1%, luego al 5%, luego al 25%, luego al 100% de los usuarios, vigilando la tasa de error y la latencia en cada paso.
- Segmentación por anillos — primero empleados internos, luego usuarios beta que se apuntaron voluntariamente, luego una región o plan específico, y finalmente todos.
- Canary por infraestructura — un número reducido de servidores corre la versión nueva, en lugar de un porcentaje de usuarios; útil cuando el riesgo es operativo (fuga de memoria, regresión de CPU) y no de comportamiento.
- Interruptor de emergencia (kill switch) — un flag sin ningún rollout gradual, cuya única función es poder apagarse en segundos si algo sale mal, sin necesidad de un redeploy.
Vale la pena detenerse en ese último caso, porque es el flag más barato y de mayor apalancamiento que puedes agregar: envolver una integración nueva y riesgosa, o una llamada a una API de terceros poco confiable, en un flag que por defecto está "encendido" pero puede forzarse a "apagado", convierte un incidente que exigiría un deploy de emergencia en un incidente que solo exige cambiar un booleano. Los entrevistadores prestan atención a si entiendes la diferencia entre los flags de liberación (temporales, pensados para borrarse una vez que la funcionalidad llega al 100%) y los flags operativos o de permisos (de larga vida, parte real de la superficie de control del sistema).
#Construir uno sin comprar una plataforma
No necesitas LaunchDarkly, Unleash ni Split para obtener valor real de los flags: necesitas una propiedad que muchas implementaciones caseras arruinan: la persistencia (stickiness). Si un flag decide en cada petición si un usuario ve el checkout nuevo, ese usuario puede alternar entre el viejo y el nuevo en cada carga de página, lo cual corrompe tus métricas y produce una experiencia genuinamente rota. La solución es hacer que la asignación al bucket sea una función determinista de algo estable —el ID del usuario— en lugar de un volado nuevo en cada petición:
import { createHash } from "crypto";type FlagConfig = {key: string;enabled: boolean;rolloutPercentage: number; // 0-100allowList?: string[];denyList?: string[];};class FeatureFlagService {private flags = new Map<string, FlagConfig>();constructor(flags: FlagConfig[]) {for (const flag of flags) this.flags.set(flag.key, flag);}isEnabled(flagKey: string, userId: string): boolean {const flag = this.flags.get(flagKey);if (!flag || !flag.enabled) return false;if (flag.denyList?.includes(userId)) return false;if (flag.allowList?.includes(userId)) return true;return this.bucketFor(flagKey, userId) < flag.rolloutPercentage;}// El mismo usuario + el mismo flag siempre caen en el mismo// bucket de 0 a 99. Ese determinismo es lo que hace que un// rollout sea monótono: subir rolloutPercentage de 10 a 20 solo// puede AGREGAR usuarios al grupo "encendido", nunca devolver// a "apagado" a un usuario que ya estaba habilitado.private bucketFor(flagKey: string, userId: string): number {const digest = createHash("sha256").update(`${flagKey}:${userId}`).digest();return digest.readUInt32BE(0) % 100;}updateRollout(flagKey: string, percentage: number): void {const flag = this.flags.get(flagKey);if (flag) flag.rolloutPercentage = Math.max(0, Math.min(100, percentage));}}const flags = new FeatureFlagService([{ key: "new-checkout-flow", enabled: true, rolloutPercentage: 10 },]);if (flags.isEnabled("new-checkout-flow", "user-8842")) {// renderiza el checkout nuevo} else {// renderiza el checkout heredado}
Este es todo el mecanismo detrás del rollout por porcentaje de cualquier plataforma comercial de flags, más una tabla de configuración y una interfaz de administración. Una función de hash, un módulo y una comparación: lo bastante barato como para escribirlo en una tarde, y lo bastante correcto como para confiar en él en producción, siempre que la entrada del hash incluya tanto la clave del flag (para que un usuario no caiga en el mismo bucket relativo en todos los flags, lo cual correlacionaría distintos rollouts entre sí) como el ID del usuario (para que la asignación sea estable por usuario).
#El flag que se te olvida borrar
Los flags son baratos de agregar y caros de dejar atrás. Cada condicional que introduces duplica el número de rutas de código que tu equipo debe razonar, y una base de código con doscientos flags de larga vida es una base de código donde nadie está seguro de qué combinación de flags corre realmente en producción. La disciplina que separa a los equipos que se benefician de los flags de los que se ahogan en ellos es tratar los flags de liberación como temporales por construcción: el flag se despliega junto con la funcionalidad, y eliminarlo (junto con la ruta de código vieja, ya muerta) es una tarea de seguimiento registrada, no una limpieza opcional que ocurre "eventualmente".
| Tipo de flag | Propósito | Vida típica |
|---|---|---|
| Flag de liberación | Controlar una funcionalidad durante el rollout | Días a semanas — se borra al llegar al 100% |
| Operativo / interruptor de emergencia | Desactivación de urgencia para una dependencia riesgosa | Larga vida, parte del sistema |
| Permiso / entitlement | Controlar una funcionalidad según plan o rol | Larga vida, lógica de negocio |
| Experimento (A/B) | Comparar dos variantes sobre una métrica | Semanas — se borra al concluir el experimento |
🧠 Check your understanding
0/2 · 0/2 answered1. ¿Por qué se prefiere el hashing determinista (ID de usuario + clave del flag) sobre Math.random() para los rollouts por porcentaje?
2. ¿Cuál es el principal riesgo de nunca borrar los flags de liberación después de que una funcionalidad llega al 100% de rollout?