Feature flags en producción: gestión de riesgo sin frenar desarrollo
El problema que los feature flags resuelven
Hay una tensión fundamental en el desarrollo de software: el equipo quiere desplegar código frecuentemente (idealmente, varias veces al día), pero el negocio quiere controlar cuándo y a quién se expone una funcionalidad nueva. Sin feature flags, estas dos necesidades son incompatibles. O despliegas y todo el mundo ve la funcionalidad nueva, o mantienes una rama de larga duración que diverge del main y genera conflictos de merge que nadie quiere resolver un viernes a las cinco.
Los feature flags desacoplan el despliegue del código de la activación de la funcionalidad. Es una práctica fundamental del desarrollo a medida moderno. Puedes mergear código a main, desplegarlo a producción, y mantenerlo invisible hasta que decidas activarlo. Esto parece simple, y a nivel conceptual lo es. En la práctica, la mayoría de las implementaciones de feature flags acaban siendo una fuente de complejidad, deuda técnica y bugs sutiles.
Llevamos usando feature flags en todos nuestros proyectos de producción desde hace tres años. Estas son las lecciones.
Taxonomía de flags
No todos los feature flags sirven para lo mismo, y tratarlos de forma homogénea es el primer error. Distinguimos cuatro tipos:
Release flags. Controlan la visibilidad de una funcionalidad nueva. Tienen una vida útil corta: se crean cuando la funcionalidad empieza a desplegarse y se eliminan cuando la funcionalidad está disponible para todos los usuarios. Vida típica: 2-6 semanas.
Experiment flags. Sirven para A/B testing. Dividen el tráfico entre variantes y recopilan datos para tomar una decisión. Vida típica: 2-8 semanas. Requieren integración con el sistema de analytics.
Ops flags. Interruptores operativos que permiten desactivar funcionalidades o degradar servicios en caso de incidente. El clásico: un kill switch para una integración con un servicio externo que se cae. Vida típica: indefinida, pero deben revisarse periódicamente.
Permission flags. Controlan acceso a funcionalidades por segmento de usuarios (plan de pago, rol, geografía). Son los que más se parecen a configuración de negocio y los que tienen la vida útil más larga.
Cada tipo tiene requisitos diferentes de evaluación, almacenamiento y lifecycle. Tratar un experiment flag con la misma infraestructura que un ops flag es como usar la misma herramienta para atornillar y para martillar. Funciona, pero mal.
Patrones de implementación
El if/else ingenuo
La implementación más básica es un condicional:
if feature_flags.is_enabled("new_checkout"):
return render_new_checkout(request)
else:
return render_old_checkout(request)
Funciona para un flag. Para cincuenta, el código se vuelve ilegible, imposible de testear y frágil. Cada flag añade una bifurcación que multiplica los caminos de ejecución. Diez flags binarios crean 1.024 combinaciones posibles. No vas a testear las mil veinticuatro.
Strategy pattern
Una implementación más limpia usa el patrón strategy:
class CheckoutStrategy:
def render(self, request): ...
class NewCheckout(CheckoutStrategy): ...
class OldCheckout(CheckoutStrategy): ...
strategy = flag_router.get_strategy("checkout", request.user)
return strategy.render(request)
El flag determina qué estrategia se inyecta, y el código de negocio no sabe que hay un flag. Esto hace que las funcionalidades sean testables de forma independiente y que eliminar el flag sea trivial: borras la estrategia antigua y haces que la nueva sea el default.
Trunk-based development con flags
El workflow que mejor funciona con feature flags es trunk-based development: todo el mundo trabaja en main (o en ramas muy cortas que se mergean a main diariamente), y las funcionalidades incompletas se ocultan detrás de flags.
Las reglas prácticas:
- Nunca despliegues un flag activado por defecto. Si la funcionalidad no está lista, el flag está off.
- El flag se crea antes de escribir la primera línea de código. No retrofittear flags en código existente.
- Cada flag tiene un owner y una fecha de expiración. Si no tiene owner, nadie lo eliminará.
- El flag se prueba en ambas posiciones. Tu CI debe ejecutar tests con el flag on y con el flag off.
Despliegue progresivo
Los feature flags habilitan estrategias de despliegue progresivo que reducen drásticamente el riesgo:
Canary release. Activas la funcionalidad para el 1% del tráfico. Monitorizas error rate, latencia, métricas de negocio. Si todo está bien, subes a 5%, luego 25%, luego 100%. Si algo falla en el 1%, revertir es desactivar el flag. Tiempo de reversión: segundos, no los 15-30 minutos de un rollback de despliegue.
Targeted rollout. Activas la funcionalidad para un segmento específico: usuarios internos, beta testers, clientes de un plan concreto, usuarios en una geografía. Esto permite validar en producción con usuarios reales antes del lanzamiento general.
Scheduled activation. Marketing quiere lanzar la funcionalidad el martes a las 10:00. Con un flag programado, el equipo de ingeniería despliega el código el viernes (cuando tiene tiempo para verificar), y el flag se activa automáticamente el martes. Zero-stress launch.
Hemos usado canary releases en un sistema de procesamiento de envíos que gestionaba 3.000 operaciones diarias. La nueva lógica de cálculo de rutas se desplegó activándola primero solo para envíos nacionales (que representaban el 60% del volumen pero eran más simples). Dos semanas después, activamos para internacionales. Los tres bugs que detectamos en la primera fase nunca llegaron a afectar al tráfico internacional.
La deuda de flags obsoletos
Y aquí está el problema que nadie quiere afrontar. Cada feature flag que no se elimina después de cumplir su función es deuda técnica. No deuda teórica. Deuda real que causa bugs.
Un caso que vivimos en 2024: un flag de release que llevaba 9 meses activo para el 100% del tráfico. Nadie lo había eliminado. Un desarrollador nuevo cambió la lógica del módulo sin darse cuenta de que había un camino de ejecución alternativo (el flag off) que seguía en el código. Seis meses después, alguien desactivó el flag accidentalmente durante un cambio de configuración. El sistema revirtió a una lógica de 15 meses atrás. Tardamos 3 horas en diagnosticarlo.
Políticas de lifecycle
La única defensa contra la deuda de flags es una política de lifecycle estricta:
TTL obligatorio. Cada flag tiene una fecha de expiración. Para release flags, 30 días después de activación al 100%. Para experiment flags, 14 días después de la decisión. El sistema de flags debe alertar (o directamente fallar) cuando un flag supera su TTL.
Conteo y límite. Monitorizamos el número total de flags activos. Nuestro límite interno es 25 flags concurrentes. Cuando nos acercamos, hay que limpiar antes de crear nuevos. Suena arbitrario, pero sin un límite explícito, el número crece indefinidamente.
Flag removal como tarea de engineering. No es tech debt backlog. Es parte de la definición de done del feature: el flag se elimina en el sprint siguiente al rollout completo. Si el flag no se elimina, el feature no está terminado.
Dead code detection. Herramientas como Piranha (de Uber, open source) analizan el código y detectan flags que están siempre en la misma posición. Si un flag lleva 60 días evaluándose como true, el condicional puede eliminarse y el código simplificarse automáticamente.
Herramientas: LaunchDarkly y alternativas
LaunchDarkly es el líder del mercado y tiene una razón para serlo: evaluación en el edge con latencia de microsegundos, targeting complejo, integración con todo, audit trail completo. Para equipos grandes con cientos de flags y necesidades de compliance, es la referencia.
El problema es el precio. LaunchDarkly cuesta a partir de 10 USD por seat/mes en el plan Pro, pero el coste real para un equipo de 20 personas con uso intensivo puede superar los 1.000 USD/mes. Para startups y pymes, esto no siempre encaja.
Alternativas que hemos evaluado:
Unleash. Open source, self-hosted. Evaluación del lado del servidor con SDKs para los lenguajes principales. UI funcional para gestionar flags. La opción pragmática si ya tienes infraestructura propia y no necesitas evaluación en el edge. Coste: hosting de un contenedor.
Flipt. Open source, escrito en Go. Extremadamente ligero. Evaluación server-side. Sin dependencias externas. Ideal para equipos que quieren flags simples sin operación compleja. Carece de targeting avanzado.
GrowthBook. Open source con plan hosted. Orientado a A/B testing con feature flags. Buena integración con warehouses de datos para análisis de experimentos. La mejor opción si tu caso de uso principal son experiments.
OpenFeature. No es una herramienta sino un estándar (CNCF). Define una API común para feature flags que permite cambiar de provider sin modificar el código de la aplicación. Piensa en OpenTelemetry pero para flags. Si no quieres vendor lock-in, implementar contra la API de OpenFeature y conectar el provider que elijas es la estrategia más futura-proof.
Para nuestros proyectos, usamos Unleash en producción con la API de OpenFeature en el código de aplicación. Esto nos da la flexibilidad de cambiar a cualquier otro provider (incluyendo LaunchDarkly) si las necesidades crecen, sin tocar la lógica de la aplicación.
Antipatrones
Tres antipatrones que hemos visto repetidamente:
Flags como configuración de negocio permanente. Un flag creado para “activar el módulo premium para el cliente X” que se queda para siempre porque es cómodo. Esto no es un feature flag; es configuración de la aplicación. Debe vivir en un sistema de configuración, no en el sistema de flags.
Flags anidados. Un flag que solo tiene efecto si otro flag está activado. Las dependencias entre flags crean una complejidad combinatorial que es imposible de testear y difícil de razonar. Regla: si un flag depende de otro, uno de los dos sobra.
Evaluación de flags en hot paths. Evaluar un flag en cada iteración de un bucle que procesa 100.000 registros. La evaluación de un flag debería hacerse una vez por request o por operación, no una vez por registro. Cache la decisión al inicio del proceso.
Feature flags como cultura de ingeniería
Los feature flags no son una herramienta. Son una práctica de ingeniería que refleja una cultura de despliegue seguro y experimentación controlada. Usarlos bien requiere disciplina: crear con intención, revisar con frecuencia, eliminar con urgencia.
La tentación de dejar flags “por si acaso” es real. Resiste esa tentación. Cada flag activo es un condicional en tu código que alguien tendrá que entender, testear y mantener. El número de flags no debería crecer con el tiempo; debería mantenerse estable, con flags nuevos reemplazando a flags retirados en un flujo continuo.
Un sistema de flags bien gestionado es invisible: nadie piensa en él porque simplemente funciona. Un sistema de flags mal gestionado es una mina de bugs impredecibles, combinaciones no testeadas y lógica que nadie entiende. La diferencia está en el lifecycle management, no en la herramienta. Si quieres profundizar en estrategias de despliegue y cloud, complementa este artículo con nuestro enfoque de DevOps.
