Feature Flags

Como Implementar Teste A/B Server-Side (Passo a Passo)

Como implementar teste A/B server-side: SDK de servidor, hash determinístico, paridade entre serviços, QA e teste A/A antes de confiar no pipeline.

Ilustração abstrata de blocos de servidor e engrenagens conectados por linhas de rede a um fluxograma de decisão, em tons de verde escuro e teal

Implementar um teste A/B server-side significa mover a decisão de qual variação mostrar do navegador do visitante para o seu backend, antes de qualquer HTML ou resposta de API sair rumo ao cliente. Já explicamos o porquê dessa escolha e o problema do flicker que ela resolve no artigo sobre teste A/B client-side x server-side; este guia é o “como fazer” que ficou de fora de lá: o passo a passo prático para colocar um teste server-side no ar usando a mesma infraestrutura de feature flags que já existe no mercado, sem reinventar um motor de experimentação do zero.

Este não é um guia de conceito, é um guia de implementação. Vamos cobrir o que instalar, onde no código a decisão deve morar, como fazer o mesmo usuário receber sempre a mesma variação, como manter vários serviços de acordo entre si, como testar antes de confiar no resultado e as armadilhas que mais derrubam pipelines novos.

Pré-requisitos antes de começar

Antes de escrever a primeira linha de código, três peças precisam estar decididas:

Nenhum desses três itens exige construir uma calculadora de hash própria ou um servidor de configuração do zero: o SDK escolhido já resolve a avaliação, o hash e a distribuição de configuração. O trabalho de engenharia é decidir onde encaixar essas chamadas e disciplinar a equipe para não reavaliar a mesma flag de formas diferentes em pontos diferentes do código.

O passo a passo: do request à variação

O fluxo é o mesmo independente do SDK escolhido: a requisição chega, o servidor avalia a flag usando o SDK e o atributo de hash, monta a resposta já com a variação decidida, e só depois registra o evento de exposição. Nenhuma etapa depende de o navegador rodar JavaScript adicional.

Pipeline de um teste A/B server-sideA requisição chega ao servidor, o SDK avalia a flag usando o atributo de hash do usuário, a resposta sai já com a variação decidida, e só então o evento de exposição é registrado.Requisiçãochega ao servidorAvaliação da flagSDK de servidorhash(usuário + experimento)decide a variaçãoRespostajá com a variaçãoLog doeventoexposição registrada
Nenhuma etapa depende do navegador: o visitante recebe uma resposta que já nasce com a variação certa, sem uma segunda passada para reescrever nada.

Em código, os quatro passos ficam assim, usando o SDK de servidor como referência (a sintaxe muda entre GrowthBook, Statsig, LaunchDarkly ou Split.io, mas a sequência lógica é a mesma nos quatro):

  1. Inicialize o SDK uma vez, na subida do serviço, não a cada requisição. O SDK do GrowthBook, por exemplo, carrega o payload de features via rede no init() e depois responde localmente a cada chamada de avaliação, sem round-trip de rede por requisição. A documentação da Statsig descreve o mesmo padrão: “initialize performs a network request. After initialize completes, virtually all SDK operations are synchronous.”
  2. Monte o contexto do usuário com o atributo de hash. Isso normalmente é um objeto com o ID do usuário (ou um ID anônimo) e atributos de segmentação (país, plano, dispositivo). É esse objeto que o SDK usa para decidir a variação, então ele precisa estar disponível antes de qualquer avaliação, tipicamente logo após a autenticação da requisição.
  3. Avalie a flag ou o experimento e use o resultado para montar a resposta. A chamada retorna a variação já decidida (por exemplo, "controle" ou "variacao-b"), que o seu código usa para escolher qual template renderizar, qual valor de configuração aplicar, ou qual branch de lógica seguir. Não existe uma segunda etapa de “reescrever” nada depois: a resposta que sai do servidor já é a final.
  4. Registre o evento de exposição, normalmente automático: a maioria dos SDKs de servidor loga sozinha que aquele usuário “entrou” no experimento na primeira vez que a flag é avaliada, via um callback de tracking (é assim que o GrowthBook funciona, com um trackingCallback chamado automaticamente quando o resultado do experimento é calculado). O ponto de atenção, que detalhamos mais abaixo, é não disparar esse log mais de uma vez por usuário.

Atribuição estável: o hash é o que faz o teste valer

A peça que sustenta tudo isso é simples de descrever e fácil de errar na prática: o mesmo usuário precisa ver sempre a mesma variação, em toda requisição, em toda sessão, enquanto o experimento estiver no ar. Isso não é feito sorteando a cada chamada, é feito com hash determinístico.

O princípio, usado (com pequenas variações de implementação) por GrowthBook, Statsig, LaunchDarkly e Split.io: você combina o identificador do usuário com uma chave fixa do experimento (a “seed”) e passa o resultado por uma função de hash. A documentação da GrowthBook descreve exatamente esse mecanismo: “hasheamos juntos o ID do usuário e a chave do experimento, o que produz um número entre 0 e 1”, e cada variação recebe uma faixa desse intervalo. O Split.io (hoje Harness FME) segue o mesmo princípio com outro algoritmo: o ID do usuário e a semente do flag passam por um hash determinístico (Murmur hash), normalizado em 100 baldes, e o usuário sempre cai no mesmo balde para aquele flag.

Duas propriedades importam aqui:

Como o hash determinístico decide a variaçãoO hash do usuário mais o experimento produz um número entre 0 e 1. A faixa de 0 a 0,5 é a variação A, a de 0,5 a 1 é a variação B. Um usuário cujo hash resulta em 0,62 cai na faixa de B e sempre vai receber B enquanto o experimento durar.A · Controle (0 a 0,5)B · Variação (0,5 a 1)00,51hash = 0,62hash(usuario_123 + “checkout-v2”)recalculado, sempre dá o mesmo número
O mesmo usuário e a mesma chave de experimento sempre produzem o mesmo hash, então o cálculo é reproduzível sem guardar estado, seja na primeira visita ou na centésima.

Um detalhe que costuma gerar confusão: hash determinístico já resolve a estabilidade enquanto os parâmetros não mudam. Se você altera o split de tráfego no meio do experimento (de 50/50 para 90/10, por exemplo), o hash de cada usuário continua igual, mas a fronteira entre as faixas se move, e usuários perto da nova fronteira podem trocar de variação. Para esse caso específico, a GrowthBook oferece sticky bucketing: uma camada adicional que grava a variação já atribuída (em cookie, Redis ou outro armazenamento) e a mantém fixa mesmo quando a configuração do experimento muda por baixo, incluindo suporte a um atributo de hash primário (o ID logado) com um atributo de fallback (um ID anônimo), para o usuário manter a mesma variação mesmo trocando de dispositivo depois do login.

Onde a decisão acontece na sua stack

O hash resolve “qual variação”, mas não resolve “em que camada do código isso é calculado”. Existem três padrões comuns, cada um com uma troca diferente entre velocidade, controle e esforço de operação:

Abordagem Onde a decisão acontece Latência típica Quando faz sentido
SDK direto no serviço Dentro do próprio backend que já processa a requisição (o handler da rota, o serviço de produto) Nenhuma latência extra: é uma chamada de função local, dado que o SDK já carregou o payload em memória Quando o teste afeta uma lógica que já vive nesse serviço (preço, permissão, regra de negócio) e não faz sentido introduzir uma camada nova só para isso
Edge function / middleware Numa camada antes do handler principal, geralmente numa função de borda (Cloudflare Workers, Vercel Edge, Fastly Compute, Akamai EdgeWorkers) ou num middleware do framework Baixa, e menor quanto mais perto do visitante a borda estiver geograficamente Quando a variação afeta o HTML renderizado (SSR) e você quer decidir antes de montar a página, sem esperar o backend de origem responder
Proxy dedicado Um serviço interno separado, cuja única função é avaliar flags e devolver a decisão para quem chamar Adiciona uma chamada de rede interna a mais, a não ser que seja colocalizado Quando vários serviços diferentes (às vezes em linguagens diferentes) precisam da mesma decisão e você quer centralizar a lógica de avaliação num único lugar, em vez de replicar o SDK em cada linguagem

Nenhuma linha da tabela é universalmente “melhor”. A Statsig documenta a opção de edge (via integração com Cloudflare Workers, Fastly Compute, AWS CloudFront/Lambda@Edge e Akamai EdgeWorkers) justamente para reduzir a distância entre a decisão e o visitante quando o teste afeta o que é renderizado na primeira resposta. Já quando a flag decide uma regra de produto que só um serviço específico executa (por exemplo, o serviço de cobrança decidindo qual tabela de preço aplicar), colocar o SDK direto ali, sem proxy, costuma ser mais simples e mais rápido de manter.

Paridade entre múltiplos serviços

Um teste server-side raramente vive num único processo. Um checkout, por exemplo, pode passar pelo serviço de catálogo, pelo serviço de preço e pelo serviço de pagamento, e todos os três precisam concordar sobre qual variação aquele usuário está vendo, ou o teste mistura variações no mesmo fluxo e o resultado vira lixo.

A boa notícia: como o hash é determinístico, a paridade não depende de os serviços conversarem entre si em tempo real. Ela depende de três coisas estarem idênticas em todos os serviços que avaliam aquele experimento:

Paridade de variação entre múltiplos serviçosA mesma definição de experimento, com a mesma chave e o mesmo atributo de hash, é distribuída para três serviços diferentes: catálogo, preço e pagamento. Como o cálculo de hash é determinístico, os três chegam à mesma variação para o mesmo usuário, sem precisar se comunicar entre si em tempo real.Definição do experimentomesma chave, mesmo hash attributeServiço catálogovariação: BServiço preçovariação: BServiço pagamentovariação: Bos três calculam o mesmo hash, de forma independente,e chegam à mesma variação sem se comunicar entre si
Paridade não é sincronização em tempo real, é consistência de configuração: todos os serviços lendo a mesma definição de experimento chegam ao mesmo resultado sozinhos.

Martin Fowler descreve essa mesma exigência em termos de arquitetura de toggles: um “toggle de experimento” precisa de um “algoritmo de cohort consistente baseado no ID do usuário” para garantir que a pessoa experimente sempre o mesmo caminho de código nas requisições seguintes, e recomenda concentrar a lógica de decisão num ponto único (o que ele chama de um objeto de decisões de feature), em vez de espalhar a checagem da flag solta pelo código de cada serviço. Na prática, isso quer dizer: escreva a checagem da flag uma vez, encapsulada, e reuse essa mesma chamada em todos os serviços, em vez de cada equipe reimplementar sua própria versão da lógica de avaliação.

QA: teste A/A antes de confiar no pipeline

Antes de rodar o primeiro teste A/B de verdade, rode um teste A/A: as duas variações mostram exatamente a mesma coisa, e você mede se a diferença entre os grupos fica dentro do que o acaso explicaria. Se o A/A “acusar” uma diferença significativa, o defeito está na sua atribuição ou na sua coleta, não no produto, e nenhum resultado de A/B feito com esse pipeline antes da correção deve ser levado a sério.

Um A/A precisa de amostra suficiente para não dar um veredito preguiçoso, “não achei diferença” só porque poucos visitantes passaram por ele. Estime o tamanho de amostra e a duração para o seu tráfego real antes de declarar o pipeline confiável:

Calculadora de tamanho de amostra
-Visitantes por variação
-Total (2 variações)
-Duração estimada

Cálculo por aproximação normal de duas proporções, 2 variações (50/50). Mexa nos campos e veja o impacto ao vivo.

Além do A/A, duas ferramentas de QA reduzem o risco de publicar um pipeline quebrado:

Armadilhas comuns

Armadilha Sintoma Correção
Dessincronia entre serviços Um serviço mostra a variação A, outro mostra B, para o mesmo usuário na mesma sessão Auditar se todos os serviços usam a mesma chave de experimento, o mesmo atributo de hash e a mesma versão da configuração; nunca deixar um serviço em cache de uma config antiga
Cache de CDN servindo a variação errada Um usuário vê a variação de outro usuário, geralmente em respostas cacheadas por URL sem levar em conta o cookie de atribuição Incluir o atributo de hash (ou a variação já decidida) na chave de cache, ou desabilitar cache para rotas com decisão de experimento, dependendo da CDN
Duplicidade de eventos de exposição O mesmo usuário aparece contado duas ou mais vezes no mesmo braço do experimento, inflando artificialmente a amostra Logar a exposição uma única vez por usuário/experimento, idealmente no ponto único de avaliação da flag, nunca em cada lugar do código que consulta o resultado já calculado
Reavaliar a flag em vários pontos do código Resultados inconsistentes dentro da mesma requisição, e exposições duplicadas como efeito colateral Avaliar a flag uma vez por requisição, guardar o resultado no contexto da requisição, e reusar esse valor já calculado no resto do código

O item do cache merece atenção redobrada porque é silencioso: a variação é calculada certinho no servidor, mas se a resposta cacheada é servida para um segundo visitante sem considerar que a variação depende do usuário, o segundo visitante recebe a resposta calculada para o primeiro. A saída depende da CDN, mas o princípio é sempre o mesmo: cache e decisão personalizada por usuário não coexistem sem uma chave de cache que leve em conta essa personalização.

Como medir sem duplicar eventos

A regra prática, coerente com o que a Statsig documenta sobre logging automático de exposição: cada SDK de servidor já loga um evento de exposição sozinho, na primeira vez que aquele usuário é avaliado para aquele experimento, através de um callback de tracking. O erro que gera duplicidade não costuma estar no SDK, está no seu próprio código: se você chama a função de avaliação da flag em três lugares diferentes da mesma requisição (um middleware, um componente de página e uma chamada de API auxiliar), e cada chamada dispara o callback de tracking de novo, você loga três exposições para um único visitante.

A correção é sempre a mesma: avalie a flag uma vez por requisição (ou por sessão de usuário, dependendo do seu caso), guarde o resultado, e reuse esse valor calculado em qualquer outro ponto do código que precise saber a variação, sem chamar a avaliação de novo. Quando o SDK permitir desativar o log automático (a Statsig oferece isso explicitamente, com métodos como manuallyLogGateExposure()), centralizar o disparo do evento no ponto único de decisão é a forma mais segura de garantir uma exposição por usuário, não uma por chamada.

Faça isso automático na Donnu

Tudo o que você acabou de ler, SDK de servidor, hash determinístico, paridade entre serviços, override de QA e deduplicação de eventos, é infraestrutura real para construir, e ela custa engenharia mesmo quando você usa uma ferramenta pronta como base. Sendo direto sobre o que a Donnu A/B é hoje: somos uma ferramenta client-side, pensada para instalar em minutos com um snippet leve, sem exigir esse tipo de trabalho de backend. Se o seu teste é uma mudança visual de página (título, CTA, layout, oferta), é exatamente esse o caso de uso que resolvemos sem você precisar montar nada do que descrevemos aqui.

Se o seu próximo teste é assim, comece um teste grátis de 14 dias e veja o snippet em ação. Se o seu caso realmente pede a robustez server-side (preço, permissão de acesso, lógica de produto), este guia é o mapa do que construir, e vale revisar o guia completo de feature flags antes de escolher qual ferramenta de servidor usar como base.

Leia também

Referências

Perguntas frequentes

Preciso trocar de ferramenta de teste A/B para implementar server-side?
Não necessariamente. Ferramentas de feature flag e experimentação como GrowthBook, Statsig, LaunchDarkly e Split.io já oferecem SDKs de servidor prontos para Node.js, Python, Go, Java e outras linguagens, então o trabalho normalmente é de integração, não de construir um motor de avaliação do zero. O esforço real está em decidir onde no seu backend a avaliação vai acontecer e em manter a atribuição do visitante estável entre chamadas.
Onde a decisão de variação deve acontecer: middleware, edge function ou dentro do backend?
Depende de quanto a variação muda o HTML ou a resposta final. Se a mudança afeta o layout ou o conteúdo de uma página renderizada no servidor, avaliar no middleware ou numa edge function antes do render evita reprocessamento e mantém a decisão perto do visitante. Se a mudança é uma regra de produto (preço, permissão, lógica de negócio), a avaliação pode acontecer dentro do próprio serviço de backend que já processa aquela regra, sem precisar de uma camada extra.
Como garantir que dois microsserviços deem a mesma variação para o mesmo usuário?
Usando a mesma chave de experimento, o mesmo atributo de hash (normalmente o ID do usuário) e a mesma configuração de payload em todos os serviços que avaliam aquele experimento. Como o hash é determinístico, dois serviços com a mesma entrada sempre calculam a mesma saída, então a paridade não depende de comunicação entre eles em tempo real, depende de todos lerem a mesma definição do experimento.
O que é um teste A/A e por que rodar um antes de confiar no pipeline?
Um teste A/A mostra a mesma versão para os dois grupos e mede se a diferença entre eles fica dentro do esperado pelo acaso. Se um teste A/A acusar diferença significativa, o problema está na coleta ou na atribuição, não no produto, e nenhum resultado de A/B depois disso é confiável até o defeito ser encontrado e corrigido.
Como evitar contar o mesmo evento de conversão duas vezes?
Registrando o evento de exposição (o usuário entrou no experimento) separado do evento de conversão, e emitindo cada um exatamente uma vez por unidade de decisão, geralmente com deduplicação por ID de evento ou por combinação usuário mais experimento. A maioria dos SDKs de servidor já loga a exposição automaticamente na primeira avaliação; o erro comum é reavaliar a flag em vários pontos do código e disparar o mesmo log de exposição várias vezes para o mesmo usuário.