Feature Flags e Gestão de Features em Produção: Rollouts Graduais, Kill Switches e A/B Testing
Implantar código em produção não significa liberar features para usuários. Feature flags desacoplam deploy de release, permitindo rollouts graduais, rollbacks instantâneos e experimentação controlada.
Resumo executivo
Implantar código em produção não significa liberar features para usuários. Feature flags desacoplam deploy de release, permitindo rollouts graduais, rollbacks instantâneos e experimentação controlada.
Ultima atualizacao: 15/03/2026
Resumo executivo
O padrão de implantação mais perigoso é acoplar implantação de código com release de feature. Quando uma nova feature é implantada e imediatamente visível para todos usuários, qualquer bug, degradação de performance ou problema de experiência do usuário afeta 100% da sua base de usuários instantaneamente. Fazer rollback significa um full code deploy, geralmente com lead time medido em horas.
Feature flags desacoplam implantação de release. Código é implantado em produção com features desabilitadas por flags, e features são liberadas gradualmente através de mudanças de configuração—não deploys de código. Isso permite rollouts graduais, rollbacks instantâneos via configuração, experimentação controlada e desativação cirúrgica de features sem reimplantação.
Em 2026, gestão de feature flags evoluiu de flags booleanos simples para regras de targeting sofisticadas, testes multivariados e monitoramento de performance em tempo real. Organizações que amadurecem o uso de feature flags reduzem o raio de explosão de incidentes, aceleram experimentação e implantam com confiança.
Taxonomia de feature flags
Tipos de flags e casos de uso
| Tipo de Flag | Caso de Uso | Exemplo | Estratégia de Rollback |
|---|---|---|---|
| Boolean | Habilitar/desabilitar features | mostrarNovoCheckout | Alternar flag para false |
| Multivariada | Teste de variantes A/B | varianteCheckout: ["A", "B", "C"] | Mudar alocação de porcentagem |
| Rollout gradual | Release baseado em porcentagem | novoDashboard: 25% | Reduzir porcentagem para 0% |
| Direcionada | Targeting baseado em usuário | acessoBeta: [userIds...] | Remover da lista de permissão |
| Config dinâmica | Configuração em runtime | maxItensCarrinho: 50 | Ajustar valor |
| Kill switch | Desabilitação de emergência | apiExterna: true/false | Desabilitar imediatamente |
Anti-padrões para evitar
1. Feature flags permanentes:
typescript// Ruim: Feature flag nunca removida
if (featureFlags.mostrarNovoCheckout) {
return <NovoCheckout />;
} else {
return <CheckoutAntigo />; // Código morto, nunca limpo
}Solução: Criar limpeza de feature flag como parte do processo de release. Flags devem ter data de expiração e plano de remoção documentado.
2. Lógica de flag espalhada pelo código:
typescript// Ruim: Verificações de flag em todos lugares
if (featureFlags.novaFeature) { /* ... */ }
if (featureFlags.novaFeature) { /* ... */ }
if (featureFlags.novaFeature) { /* ... */ }Solução: Centralizar lógica de flag atrás de interfaces bem definidas:
typescript// Bom: Flag atrás de serviço de feature
interface CheckoutFeature {
usarVariante: () => 'nova' | 'antiga';
estaHabilitada: () => boolean;
}
const checkoutFeature: CheckoutFeature = {
usarVariante: () => featureFlags.novoCheckout ? 'nova' : 'antiga',
estaHabilitada: () => featureFlags.novoCheckout
};Padrões de implementação
Padrão 1: Rollout gradual
Aumentar gradualmente a exposição do usuário a novas features:
typescriptinterface ConfigRolloutGradual {
porcentagem: number; // 0-100
identificadorUsuario: string; // ID de usuário ou similar
salt: string; // Garante alocação consistente
}
function estaNoRolloutGradual(config: ConfigRolloutGradual): boolean {
const hash = crypto
.createHash('md5')
.update(`${config.salt}:${config.identificadorUsuario}`)
.digest('hex');
const hashNumero = parseInt(hash.substring(0, 8), 16);
const porcentagem = (hashNumero / 0xFFFFFFFF) * 100;
return porcentagem < config.porcentagem;
}
// Uso
function renderizarCheckout(usuario: Usuario): JSX.Element {
const estaNoRollout = estaNoRolloutGradual({
porcentagem: featureFlags.rolloutCheckout, // e.g., 25%
identificadorUsuario: usuario.id,
salt: 'novo-checkout-v1'
});
if (estaNoRollout) {
return <NovoCheckout />;
}
return <CheckoutAntigo />;
}Padrão 2: Rollout direcionado
Habilitar features para usuários específicos, segmentos ou atributos:
typescriptinterface ConfigRolloutDirecionado {
listaPermitida: string[]; // IDs de usuário
listaBloqueada: string[]; // IDs de usuário (tem precedência)
regras: RegraTargeting[];
}
interface RegraTargeting {
atributo: string; // e.g., 'plano', 'regiao', 'email'
operador: 'igual' | 'contem' | 'corresponde' | 'em';
valor: any;
}
function estaNoRolloutDirecionado(
usuario: Usuario,
config: ConfigRolloutDirecionado
): boolean {
// Verificar lista bloqueada primeiro
if (config.listaBloqueada.includes(usuario.id)) {
return false;
}
// Verificar lista permitida
if (config.listaPermitida.length > 0) {
return config.listaPermitida.includes(usuario.id);
}
// Verificar regras de targeting
return config.regras.every(regra => {
const valorUsuario = (usuario as any)[regra.atributo];
switch (regra.operador) {
case 'igual':
return valorUsuario === regra.valor;
case 'contem':
return valorUsuario?.includes(regra.valor);
case 'corresponde':
return new RegExp(regra.valor).test(valorUsuario);
case 'em':
return Array.isArray(regra.valor) && regra.valor.includes(valorUsuario);
default:
return false;
}
});
}
// Uso
function renderizarFeaturePremium(usuario: Usuario): JSX.Element {
const temAcesso = estaNoRolloutDirecionado(usuario, {
listaPermitida: [], // Sem usuários específicos
listaBloqueada: ['usuario123', 'usuario456'], // Bloquear explicitamente
regras: [
{
atributo: 'plano',
operador: 'igual',
valor: 'premium'
}
]
});
if (!temAcesso) {
return <PromptUpgrade />;
}
return <FeaturePremium />;
}Padrão 3: Teste multivariado (A/B/n)
Testar múltiplas variantes de uma feature:
typescriptinterface ConfigMultivariada {
variantes: {
[key: string]: number; // nome variante -> porcentagem
};
identificadorUsuario: string;
salt: string;
}
function atribuirVariante(config: ConfigMultivariada): string | null {
const porcentagemTotal = Object.values(config.variantes).reduce((a, b) => a + b, 0);
if (porcentagemTotal === 0) {
return null;
}
const hash = crypto
.createHash('md5')
.update(`${config.salt}:${config.identificadorUsuario}`)
.digest('hex');
const hashNumero = parseInt(hash.substring(0, 8), 16);
const porcentagem = (hashNumero / 0xFFFFFFFF) * 100;
let acumulado = 0;
for (const [variante, porcentagemVariante] of Object.entries(config.variantes)) {
acumulado += porcentagemVariante;
if (porcentagem < acumulado) {
return variante;
}
}
return null;
}
// Uso
function renderizarHomepage(usuario: Usuario): JSX.Element {
const variante = atribuirVariante({
variantes: {
'controle': 40, // 40% veem controle
'variante-a': 40, // 40% veem variante A
'variante-b': 20 // 20% veem variante B
},
identificadorUsuario: usuario.id,
salt: 'teste-homepage-2026-03'
});
switch (variante) {
case 'controle':
return <HomepageControle />;
case 'variante-a':
return <HomepageVarianteA />;
case 'variante-b':
return <HomepageVarianteB />;
default:
return <HomepageControle />; // Fallback
}
}Padrão 4: Kill switch para dependências externas
Desabilitar instantaneamente serviços externos problemáticos:
typescriptinterface ConfigKillSwitch {
habilitado: boolean;
implementacaoFallback?: () => any;
limiarCircuitBreaker?: number;
timeoutCircuitBreaker?: number;
}
class ServicoComKillSwitch {
private falhasCircuitBreaker = 0;
private circuitBreakerAbertoAte: Date | null = null;
constructor(
private nomeServico: string,
private config: ConfigKillSwitch
) {}
async chamar<T>(fn: () => Promise<T>): Promise<T> {
// Verificar se kill switch está habilitado
if (!this.config.habilitado) {
if (this.config.implementacaoFallback) {
return this.config.implementacaoFallback();
}
throw new Error(`${this.nomeServico} está desabilitado via kill switch`);
}
// Verificar circuit breaker
if (this.circuitBreakerAbertoAte && new Date() < this.circuitBreakerAbertoAte) {
throw new Error(`Circuit breaker de ${this.nomeServico} está aberto`);
}
try {
const resultado = await fn();
this.falhasCircuitBreaker = 0;
return resultado;
} catch (error) {
this.falhasCircuitBreaker++;
// Abrir circuit breaker se limiar for atingido
if (this.config.limiarCircuitBreaker &&
this.falhasCircuitBreaker >= this.config.limiarCircuitBreaker) {
this.circuitBreakerAbertoAte = new Date(
Date.now() + (this.config.timeoutCircuitBreaker || 60000)
);
}
throw error;
}
}
}
// Uso
const servicoPagamento = new ServicoComKillSwitch('api-pagamento', {
habilitado: featureFlags.apiPagamento, // Pode ser alternado instantaneamente
implementacaoFallback: () => ({ status: 'modo_manutencao' }),
limiarCircuitBreaker: 5,
timeoutCircuitBreaker: 300000 // 5 minutos
});
async function processarPagamento(valor: number): Promise<ResultadoPagamento> {
return servicoPagamento.chamar(() =>
apiPagamentoExterna.cobrar(valor)
);
}Plataformas de feature flags
Comparação de plataformas para 2026
| Plataforma | Pontos Fortes | Trade-offs |
|---|---|---|
| LaunchDarkly | Features enterprise, targeting detalhado, SDKs | Custo, curva de aprendizado |
| Split.io | Ferramentas de experimentação fortes, integração data science | Complexidade para casos simples |
| Flagsmith | Opções open source, API simples | Menos recursos enterprise |
| Unleash | Open source, self-hostable, tier enterprise | UI menos polida que comercial |
| Statsig | Analytics integrado, free tier generoso | Plataforma mais nova, ecossistema menor |
Quando usar comercial vs. open source
Usar plataforma comercial quando:
- Você tem requisitos de compliance enterprise (SOC 2, HIPAA)
- Você precisa de targeting sofisticado e experimentação
- Você tem time grande gerenciando múltiplos produtos
- Você quer analytics integrado e integração data science
Usar open source quando:
- Orçamento é limitado
- Você tem capacidade de engenharia para self-hosting
- Você precisa de controle completo sobre dados de flags
- Você quer evitar vendor lock-in
Serviço de feature flag personalizado
Para organizações com requisitos específicos, um serviço de flag personalizado pode ser apropriado:
typescript// Interface de serviço de feature flag
interface ServicoFeatureFlag {
getFlag(nome: string, contexto: ContextoFlag): Promise<ValorFlag>;
getAllFlags(contexto: ContextoFlag): Promise<Record<string, ValorFlag>>;
trackEvent(evento: EventoAnalytics): Promise<void>;
}
interface ContextoFlag {
userId?: string;
sessionId?: string;
atributos?: Record<string, any>;
ambiente: 'development' | 'staging' | 'production';
}
interface ValorFlag {
type: 'boolean' | 'string' | 'number' | 'json';
valor: any;
variacao?: string;
}
// Implementação com Redis
class ServicoFeatureFlagRedis implements ServicoFeatureFlag {
constructor(private redis: RedisClient) {}
async getFlag(nome: string, contexto: ContextoFlag): Promise<ValorFlag> {
const configFlag = await this.redis.get(`flag:${nome}`);
if (!configFlag) {
return { type: 'boolean', valor: false }; // Default desabilitado
}
const config = JSON.parse(configFlag);
return this.evaluarFlag(config, contexto);
}
private evaluarFlag(config: any, contexto: ContextoFlag): ValorFlag {
// Aplicar regras de rollout
if (config.rollout) {
const estaNoRollout = estaNoRolloutGradual({
porcentagem: config.rollout.porcentagem,
identificadorUsuario: contexto.userId || contexto.sessionId || 'anonimo',
salt: nome
});
if (!estaNoRollout) {
return { type: 'boolean', valor: false };
}
}
// Aplicar regras de targeting
if (config.targeting) {
const estaDirecionado = estaNoRolloutDirecionado(
{ id: contexto.userId, ...contexto.atributos },
config.targeting
);
if (!estaDirecionado) {
return { type: 'boolean', valor: false };
}
}
// Aplicar alocação multivariada
if (config.variantes) {
const variante = atribuirVariante({
variantes: config.variantes,
identificadorUsuario: contexto.userId || contexto.sessionId || 'anonimo',
salt: nome
});
return {
type: config.type,
valor: variante,
variacao: variante
};
}
return {
type: config.type,
valor: config.valor
};
}
async getAllFlags(contexto: ContextoFlag): Promise<Record<string, ValorFlag>> {
const todosNomesFlags = await this.redis.keys('flag:*');
const flags: Record<string, ValorFlag> = {};
for (const nomeFlag of todosNomesFlags) {
const nome = nomeFlag.replace('flag:', '');
flags[nome] = await this.getFlag(nome, contexto);
}
return flags;
}
async trackEvent(evento: EventoAnalytics): Promise<void> {
// Enviar para plataforma de analytics
await this.redis.lpush('eventos-analytics', JSON.stringify(evento));
}
}
// Uso
const featureFlags = new ServicoFeatureFlagRedis(redis);
async function renderizarFeature(nomeFeature: string, usuario: Usuario): Promise<JSX.Element> {
const flag = await featureFlags.getFlag(nomeFeature, {
userId: usuario.id,
atributos: {
plano: usuario.plano,
regiao: usuario.regiao
},
ambiente: process.env.NODE_ENV
});
if (flag.valor) {
return <NovaFeature variante={flag.variacao} />;
}
return <FeatureAntiga />;
}Estratégias de rollout
Estratégia 1: Implantação canary
Expor gradualmente feature para porcentagem crescente de usuários:
T0: 1% → Monitorar erros
T1: 5% → Monitorar erros e performance
T2: 10% → Monitorar erros, performance e feedback de usuário
T3: 25% → Monitorar todas métricas
T4: 50% → Monitorar todas métricas
T5: 100% → Rollout completoImplementação:
typescriptasync function rolloutGradual(
nomeFeature: string,
porcentagemAlvo: number,
duracaoMinutos: number
): Promise<void> {
const etapas = [1, 5, 10, 25, 50, 100].filter(p => p <= porcentagemAlvo);
for (const porcentagem of etapas) {
await atualizarFeatureFlag(nomeFeature, { rollout: { porcentagem } });
console.log(`Rolled out ${nomeFeature} para ${porcentagem}%`);
// Aguardar e monitorar
await sleep(duracaoMinutos / etapas.length * 60 * 1000);
// Verificar problemas
const taxaErro = await getTaxaErro(nomeFeature, porcentagem);
if (taxaErro > LIMIAR_ERRO) {
console.warn(`Taxa de erro ${taxaErro}% excede limiar, pausando rollout`);
await alertarTime(nomeFeature, porcentagem, taxaErro);
return; // Pausar rollout
}
}
console.log(`Rollout gradual de ${nomeFeature} completo`);
}Estratégia 2: Rollout blue-green
Alternar todos usuários de uma vez mas com capacidade de rollback instantânea:
Fase 1: Implantar código com feature desabilitada
└─ Flag definida para 0%
└─ Validar implantação
Fase 2: Habilitar feature (100%)
└─ Flag definida para 100%
└─ Monitorar de perto
Fase 3: Se problemas, desabilitar instantaneamente
└─ Flag definida para 0%
└─ Sem deploy de código necessárioEstratégia 3: Rollout beta direcionado
Habilitar para segmentos específicos de usuário:
typescriptasync function rolloutBeta(
nomeFeature: string,
usuariosBeta: string[],
planoBeta: string
): Promise<void> {
await atualizarFeatureFlag(nomeFeature, {
targeting: {
regras: [
{
atributo: 'plano',
operador: 'igual',
valor: planoBeta
}
],
listaPermitida: usuariosBeta
}
});
}A/B testing com feature flags
Design de experimento
typescriptinterface Experimento {
nome: string;
variantes: {
[key: string]: any;
};
metricas: string[]; // Métricas para rastrear
duracaoDias: number;
tamanhoAmostra: number;
}
function criarExperimento(experimento: Experimento) {
// Criar feature flag para experimento
return atualizarFeatureFlag(experimento.nome, {
variantes: Object.fromEntries(
Object.keys(experimento.variantes).map(nome => [nome, 100 / Object.keys(experimento.variantes).length])
)
});
}
async function analisarResultadosExperimento(experimento: Experimento): Promise<ResultadosExperimento> {
const resultados: ResultadosExperimento = {
nome: experimento.nome,
variantes: {},
vencedor: null,
significancia: 0
};
for (const [nomeVariante, configVariante] of Object.entries(experimento.variantes)) {
const metricas = await getMetricasVariante(experimento.nome, nomeVariante);
resultados.variantes[nomeVariante] = {
config: configVariante,
metricas,
usuarios: await getContagemUsuarios(experimento.nome, nomeVariante)
};
}
// Análise estatística
resultados.vencedor = determinarVencedor(resultados.variantes);
resultados.significancia = calcularSignificancia(resultados.variantes);
return resultados;
}Rastreamento de métricas
typescriptasync function rastrearMetrica(
nomeEvento: string,
variante: string,
usuario: Usuario,
valor?: number
): Promise<void> {
await analytics.track({
event: nomeEvento,
properties: {
variante,
userId: usuario.id,
feature: 'nome-experimento',
valor
},
timestamp: new Date()
});
// Rastrear no serviço de feature flag
await featureFlags.trackEvent({
type: 'metrica',
event: nomeEvento,
variante,
valor,
timestamp: new Date()
});
}
// Uso
function onClickCTA(variante: string, usuario: Usuario) {
rastrearMetrica('cta_clicado', variante, usuario);
}
async function onCompra(variante: string, usuario: Usuario, valor: number) {
await rastrearMetrica('compra_completada', variante, usuario, valor);
}Melhores práticas e governança
Gestão de ciclo de vida de flags
- Fase de planejamento:
- Documentar propósito da flag e critérios de sucesso
- Definir plano de remoção e data de expiração
- Atribuir owner da flag e revisores
- Fase de implementação:
- Usar nomes descritivos para flags
- Implementar valores padrão apropriados
- Adicionar telemetria para uso de flags
- Fase de release:
- Começar com rollout de baixa porcentagem
- Monitorar métricas em cada etapa
- Documentar decisões e descobertas
- Fase de limpeza:
- Remover código de flag após rollout completo
- Deletar flag do serviço de flags
- Documentar aprendizados para flags futuras
Convenções de nomenclatura de flags
# Flags boolean
habilitar[nome-feature] (e.g., habilitarNovoCheckout)
desabilitar[nome-feature] (e.g., desabilitarAPILegada)
# Flags de configuração
config[nome-feature][parametro] (e.g., configCheckoutMaxItens)
# Flags de experimento
experimento[nome-teste] (e.g., experimentoHomepageCTA)
# Flags de kill switch
kill[nome-servico] (e.g., killAPIPagamentoExterna)Considerações de performance
Cache de avaliações de flag:
typescriptclass ServicoFeatureFlagComCache implements ServicoFeatureFlag {
private cache = new Map<string, { valor: ValorFlag; expira: number }>();
private readonly CACHE_TTL = 60000; // 1 minuto
constructor(private delegate: ServicoFeatureFlag) {}
async getFlag(nome: string, contexto: ContextoFlag): Promise<ValorFlag> {
const chaveCache = this.getChaveCache(nome, contexto);
const cached = this.cache.get(chaveCache);
if (cached && cached.expira > Date.now()) {
return cached.valor;
}
const valor = await this.delegate.getFlag(nome, contexto);
this.cache.set(chaveCache, {
valor,
expira: Date.now() + this.CACHE_TTL
});
return valor;
}
private getChaveCache(nome: string, contexto: ContextoFlag): string {
return `${nome}:${contexto.userId}:${JSON.stringify(contexto.atributos)}`;
}
}Monitoramento e observabilidade
Métricas principais para rastrear
- Métricas de uso de flags:
- Número de usuários por variante
- Porcentagem de usuários vendo cada estado de flag
- Frequência de flips de flags
- Métricas de negócio:
- Taxas de conversão por variante
- Impacto de receita
- Métricas de engajamento de usuário
- Métricas técnicas:
- Taxas de erro por variante
- Métricas de performance por variante
- Latência de avaliação de flag
- Métricas operacionais:
- Número de flags ativas
- Distribuição de idade de flags
- Taxa de conclusão de limpeza de flags
Alertas sobre problemas de flags
typescriptasync function monitorarFlags(): Promise<void> {
const todasFlags = await featureFlags.getAllFlags({ ambiente: 'production' });
for (const [nomeFlag, valorFlag] of Object.entries(todasFlags)) {
// Verificar flags antigas (> 90 dias)
const idadeFlag = await getIdadeFlag(nomeFlag);
if (idadeFlag > 90) {
await alertarFlagAntiga(nomeFlag, idadeFlag);
}
// Verificar flags em porcentagens intermediárias de rollout
if (valorFlag.variacao && valorFlag.variacao !== 'controle' && valorFlag.variacao !== 'tratamento') {
const porcentagemRollout = getPorcentagemRollout(valorFlag);
if (porcentagemRollout > 0 && porcentagemRollout < 100) {
await alertarRolloutParcial(nomeFlag, porcentagemRollout);
}
}
// Verificar altas taxas de erro
const taxaErro = await getTaxaErroParaFlag(nomeFlag);
if (taxaErro > LIMIAR_ERRO) {
await alertarAltaTaxaErro(nomeFlag, taxaErro);
}
}
}Conclusão
Feature flags em 2026 evoluíram de switches booleanos simples para plataformas sofisticadas de release e experimentação. Quando implementadas corretamente, desacoplam implantação de release, permitem experimentação segura, fornecem capacidades de rollback instantâneo e reduzem o raio de explosão de incidentes.
A organização madura trata feature flags como artefatos de primeira classe: documentados, versionados e com ciclos de vida definidos. Times que amadurecem práticas de feature flags implantam com confiança, experimentam sistematicamente e respondem a incidentes instantaneamente sem deploys de código.
Pergunta prática de encerramento: Quantas feature flags estão ativas no seu ambiente de produção hoje, e quantas têm planos de limpeza documentados?
Precisa implementar infraestrutura de feature flags ou melhorar suas práticas de implantação em produção? Fale com a Imperialis sobre arquitetura de feature flags, seleção de plataforma e implementação pronta para produção.
Fontes
- FeatureFlag.dev - Plataforma open source de feature flags
- Documentação LaunchDarkly — Documentação oficial
- Plataforma de Experimentação Split.io — Documentação oficial
- Documentação Unleash — Plataforma open source
- Progressive Delivery: Feature Flags e Mais - GitLab — Guia de progressive delivery