Knowledge

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.

15/03/202610 min de leituraKnowledge
Feature Flags e Gestão de Features em Produção: Rollouts Graduais, Kill Switches e A/B Testing

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 FlagCaso de UsoExemploEstratégia de Rollback
BooleanHabilitar/desabilitar featuresmostrarNovoCheckoutAlternar flag para false
MultivariadaTeste de variantes A/BvarianteCheckout: ["A", "B", "C"]Mudar alocação de porcentagem
Rollout gradualRelease baseado em porcentagemnovoDashboard: 25%Reduzir porcentagem para 0%
DirecionadaTargeting baseado em usuárioacessoBeta: [userIds...]Remover da lista de permissão
Config dinâmicaConfiguração em runtimemaxItensCarrinho: 50Ajustar valor
Kill switchDesabilitação de emergênciaapiExterna: true/falseDesabilitar 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

PlataformaPontos FortesTrade-offs
LaunchDarklyFeatures enterprise, targeting detalhado, SDKsCusto, curva de aprendizado
Split.ioFerramentas de experimentação fortes, integração data scienceComplexidade para casos simples
FlagsmithOpções open source, API simplesMenos recursos enterprise
UnleashOpen source, self-hostable, tier enterpriseUI menos polida que comercial
StatsigAnalytics integrado, free tier generosoPlataforma 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 completo

Implementaçã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ário

Estraté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

  1. 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
  1. Fase de implementação:
  • Usar nomes descritivos para flags
  • Implementar valores padrão apropriados
  • Adicionar telemetria para uso de flags
  1. Fase de release:
  • Começar com rollout de baixa porcentagem
  • Monitorar métricas em cada etapa
  • Documentar decisões e descobertas
  1. 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

  1. 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
  1. Métricas de negócio:
  • Taxas de conversão por variante
  • Impacto de receita
  • Métricas de engajamento de usuário
  1. Métricas técnicas:
  • Taxas de erro por variante
  • Métricas de performance por variante
  • Latência de avaliação de flag
  1. 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

Leituras relacionadas