Este projeto é uma implementação técnica que demonstra uma abordagem reativa para gerenciar Feature Flags em Elixir, inspirada no Paradigma Orientado a Notificações (PON) de Jean Marcelo Simão.
O objetivo é eliminar a sobrecarga de verificações condicionais (if/else) em tempo de execução, comuns em implementações tradicionais. Em vez de verificar o estado de uma flag a cada chamada, a aplicação se recompila dinamicamente em resposta a uma mudança de configuração, removendo efetivamente o desvio condicional do código em produção.
A implementação tradicional de Feature Flags, embora útil para CI/CD, introduz uma dívida técnica silenciosa na forma de redundância temporal: a reavaliação repetitiva de uma condição que raramente muda. Em sistemas de alta performance, isso se traduz em milhões de ciclos de CPU e energia desperdiçados.
Este projeto propõe uma alternativa: tratar a configuração não como um dado a ser consultado, mas como um evento que molda a própria estrutura do software. Usando a metaprogramação do Elixir (Code.compile_quoted) e o Hot Code Swapping da BEAM, transformamos uma flag de configuração em uma recompilação reativa.
O resultado é um sistema que não "pergunta" se uma feature está ativa, mas é estruturalmente alterado para executar apenas o código necessário.
A solução é dividida em três componentes principais, mapeando os conceitos do PON:
- O Fato (A Configuração): A fonte da verdade sobre o estado da feature flag (neste exemplo, simulada em memória).
- A Entidade Instigadora (O Watcher): O
PonFeatureFlag.FlagWatcheré umGenServerque monitora mudanças na configuração e dispara a recompilação. - A Entidade Regra (O Compilador Dinâmico): O
PonFeatureFlag.FeatureCompileré responsável por gerar a Árvore de Sintaxe Abstrata (AST) otimizada e usarCode.compile_quotedpara recompilar o módulo de negócio em tempo de execução.
- Na inicialização,
FlagWatcherlê a configuração e compila a primeira versão doPonFeatureFlag.PaymentProcessor. - O módulo
PonFeatureFlag.PaymentProcessorpassa a conter apenas o código para o estado atual da flag (ex: a versão legada). Não háifno código compilado. - Quando a configuração muda (simulado via
FlagWatcher.update_flag/1), oWatchernotifica oFeatureCompiler. - O
FeatureCompilergera uma nova AST para oPaymentProcessorcom a lógica da nova versão e a carrega na VM (Hot Swap). - Novas chamadas a
PaymentProcessor.process/1executam imediatamente a nova versão do código, ainda sem nenhumif.
Para testar o comportamento, inicie uma sessão iex:
iex -S mix
Primeiro, chame o processador de pagamento. Por padrão, a feature flag está desabilitada, executando a lógica "legada".
iex> PonFeatureFlag.PaymentProcessor.process(100)
Executing Legacy Flow
{:legacy, 100}Agora, ative a nova feature. Isso acionará a recompilação em segundo plano.
iex> PonFeatureFlag.FlagWatcher.update_flag(true)
:okAguarde um instante para a recompilação e chame o mesmo módulo novamente. O comportamento mudou sem que o código cliente precisasse de alteração.
iex> PonFeatureFlag.PaymentProcessor.process(100)
Executing New Flow
{:new, 100}Para desativar a flag:
iex> PonFeatureFlag.FlagWatcher.update_flag(false)
:ok
iex> PonFeatureFlag.PaymentProcessor.process(100)
Executing Legacy Flow
{:legacy, 100}Este relatório investiga a ineficiência estrutural inerente às implementações tradicionais de Feature Flags em sistemas de alta performance. A análise desafia o consenso de que verificações condicionais em tempo de execução (runtime checks) são um custo aceitável, demonstrando que a redundância temporal é uma fonte significativa de desperdício computacional.
Utilizando o Paradigma Orientado a Notificações (PON) e a BEAM, eliminamos custos de branch prediction e reduzimos a pegada de carbono do processamento digital.
No desenvolvimento moderno, Feature Flags são pilares de CI/CD. Contudo, a implementação padrão carrega um custo oculto. Tipicamente, uma flag é um if inserido no fluxo de execução:
# Implementação Típica (e Ineficiente)
def processar_pagamento(dados) do
if FeatureFlags.enabled?(:novo_gateway) do
NovoGateway.processar(dados)
else
GatewayLegado.processar(dados)
end
endIsso introduz a Redundância Temporal: a reavaliação repetitiva de uma expressão cujo resultado permanece inalterado por longos períodos. Em sistemas de alta frequência, o software "pergunta" milhões de vezes a mesma questão, recebendo a mesma resposta.
O título atua como uma crítica à implementação preguiçosa. A ineficiência reside em tratar a configuração como dado passivo (pull) em vez de evento ativo (push). Flags frequentemente tornam-se "dívida técnica fossilizada", criando labirintos condicionais no binário executável para decisões que já foram tomadas logicamente.
Baseado no trabalho do Prof. Jean Marcelo Simão (UTFPR), o PON critica os paradigmas Imperativo e Orientado a Objetos por suas redundâncias de busca (searches).
- Redundância Temporal: Reavaliação de uma expressão lógica inalterada.
- Redundância Estrutural: Repetição de lógica de avaliação em múltiplos pontos ou verificação de condições irrelevantes para o contexto atual.
O PON inverte o controle através de entidades reativas:
- Fato/Atributo: Detém o estado.
- Premissa: Avalia a condição apenas quando o Atributo notifica mudança.
- Regra: Dispara a Ação se as Premissas forem satisfeitas.
- Ação: Executa o efeito colateral.
Se a configuração não mudou, o código não deve gastar ciclos verificando-a. O código deve ser a manifestação da configuração atual.
A remoção de um if é uma otimização de microarquitetura.
Processadores modernos usam pipelines profundos e execução especulativa. Um if obriga a CPU a "adivinhar" o caminho via Branch Prediction.
- Cenário Ideal: Custo baixo, mas ocupa Instruction Cache e largura de banda de decodificação.
- Cenário Crítico (Misprediction): Ocorre Pipeline Flush, descartando trabalho especulativo e desperdiçando energia.
- Poluição da BTB: Flags estáticas poluem a Branch Target Buffer, expulsando dados de previsão de branches que são realmente críticos e aleatórios.
A técnica tradicional viola princípios de eficiência energética ao:
- Executar instruções inúteis (Load, Compare, Jump).
- Aumentar o tamanho do binário com "código morto" (o ramo
elsenão utilizado).
A abordagem PON elimina bilhões de micro-operações, traduzindo-se em economia de watts em escala de data center.
Elixir permite compilar AST em bytecode BEAM durante a execução. Diferente de eval, Code.compile_quoted gera módulos compilados com performance nativa, permitindo que o software reescreva suas regras de negócio.
A BEAM suporta o carregamento de novas versões de módulos sem parada. A notificação de mudança (PON) dispara a recompilação, substituindo o código com if por código linear.
Este módulo gera a versão otimizada onde o if foi resolvido em tempo de compilação.
defmodule FeatureCompiler do
require Logger
def recompile_module(feature_enabled?) do
# O 'if' é executado apenas UMA VEZ, durante a recompilação.
function_body =
if feature_enabled? do
quote do
Logger.info("Executando Fluxo V2 (Otimizado)")
NewPaymentProcessor.process(amount)
end
else
quote do
Logger.info("Executando Fluxo V1 (Legado)")
LegacyPaymentProcessor.process(amount)
end
end
module_ast =
quote do
defmodule PaymentProcessor do
# Função gerada contendo APENAS o corpo selecionado.
def process(amount) do
unquote(function_body)
end
end
end
# Gerenciamento do Code Server
:code.purge(PaymentProcessor)
[{module, binary}] = Code.compile_quoted(module_ast)
{:module, ^module} = :code.load_binary(module, ~c"nofile", binary)
{:ok, module}
end
endImplementa a reatividade. Ao receber {:update_flag, new_value}, compara com o estado atual. Se houver mudança, invoca FeatureCompiler.recompile_module/1.
| Abordagem | Pseudo-Código Runtime | Custo de Hardware |
|---|---|---|
| Tradicional | call check -> test -> jump_if_false -> call |
Alto: Salto condicional, poluição de BTB. |
| PON / Simão | call new_flow -> return |
Mínimo: Execução linear. |
Embora :persistent_term ofereça leitura rápida, ainda exige uma chamada de função e um desvio condicional no código do usuário. A recompilação PON elimina ambos, atingindo custo zero de leitura.
| Método | Custo Leitura (Hot Path) | Custo Atualização |
|---|---|---|
| Banco de Dados | Muito Alto | Baixo |
| ETS | Médio | Baixo |
:persistent_term |
Baixo | Alto (Global GC) |
| PON (Recompilação) | Zero (Inlinado) | Alto (CPU Compilação) |
Em um sistema com 1 bilhão de requisições/dia e 50 flags, a abordagem tradicional desperdiça 150 bilhões de instruções diárias apenas para decidir "não mudar nada". O PON elimina esse desperdício.
O uso de metaprogramação em tempo de execução exige rigor:
- Exaustão de Átomos: Nunca gere nomes de módulos dinamicamente. Reutilize sempre o mesmo átomo (ex:
PaymentProcessor) para evitar estouro da tabela de átomos da VM. - Consistência em Cluster: A recompilação é local. Em ambientes distribuídos, use mecanismos de Broadcast (PubSub) para que todos os nós apliquem a mudança.
- Code Server Locking: Ideal para flags estruturais de baixa frequência de escrita. Para flags de usuário (A/B testing por sessão), esta técnica é inadequada devido ao overhead de compilação.
A tese "Feature Flag é besteira" alerta contra a complacência. Aplicando o PON e Code.compile_quoted, transformamos configurações passivas em arquitetura reativa. Removemos redundância temporal e estrutural, aliviamos o branch predictor e contribuímos para o Green Coding, representando o estado da arte em performance na BEAM.
Abaixo, os dados brutos da execução comparando a abordagem PON (Dynamic) contra a tradicional.
Operating System: Linux
CPU Information: AMD Ryzen 5 3500U with Radeon Vega Mobile Gfx
Elixir 1.19.2 | Erlang 27.3.4.2 | JIT enabled: true
Benchmark suite configuration:
warmup: 2 s | time: 10 s | parallel: 1
Inputs: 50 workers, 10000 calls per worker
*** Concurrency Benchmark (50 workers, static flag ON) ***
Name ips average deviation median 99th %
Dynamic PON (flag ON) 311.99 3.21 ms ±38.58% 2.93 ms 8.85 ms
Traditional Ifs (flag ON) 32.11 31.14 ms ±21.84% 28.78 ms 66.23 ms
Comparison:
Dynamic PON (flag ON) 311.99
Traditional Ifs (flag ON) 32.11 - 9.72x slower +27.94 ms
Memory usage statistics:
Dynamic PON: 57.84 KB avg
Traditional Ifs: 58.64 KB avg
Os resultados confirmam que a abordagem PON é aproximadamente 10 vezes mais rápida neste cenário de alta concorrência, validando a eliminação do overhead de verificação.