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.

📚 Este artigo faz parte do guia Feature Flags: o Guia Completo.
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:
- Um SDK de servidor. Ferramentas de feature flag e experimentação como GrowthBook, Statsig, LaunchDarkly e Split.io (hoje parte da Harness FME) oferecem SDKs de servidor para as linguagens mais comuns. A diferença central para o SDK client-side: a chave de servidor nunca deve rodar no navegador, porque o payload baixado contém as regras de segmentação completas, e a LaunchDarkly é explícita sobre isso em sua documentação de conceitos de SDK, tratando o ambiente client-side como não confiável por definição.
- Uma camada de avaliação clara. Você precisa decidir, antes de programar, em que ponto exato da requisição a flag vai ser lida: um middleware HTTP, uma função de borda (edge), ou uma chamada dentro do próprio handler da rota. Essa escolha determina onde o teste vive na sua stack (tratamos isso na próxima seção).
- Um atributo de hash estável. Normalmente o ID do usuário autenticado, ou um ID anônimo persistente (cookie ou header) quando o visitante ainda não fez login. Sem um identificador estável, não existe atribuição estável, e sem atribuição estável não existe teste A/B válido.
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.
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):
- 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.” - 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.
- 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. - 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
trackingCallbackchamado 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:
- Determinismo: a mesma entrada (usuário + experimento) sempre produz a mesma saída. Não há estado a guardar, não há banco de dados a consultar a cada requisição: o cálculo é reproduzível em qualquer processo, em qualquer serviço, desde que a fórmula e a seed sejam as mesmas.
- Distribuição uniforme: uma boa função de hash espalha os usuários de forma equilibrada pelas faixas, então uma divisão configurada como 50/50 realmente fica perto de 50/50 na prática, não enviesada por algum padrão no ID do usuário.
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:
- A mesma chave de experimento (o identificador único usado na fórmula de hash).
- O mesmo atributo de hash para aquele usuário (o mesmo ID, vindo da mesma fonte, por exemplo sempre o ID do usuário autenticado, nunca ora o ID de sessão, ora o ID de conta).
- A mesma configuração do experimento (o mesmo split de tráfego, as mesmas regras de segmentação), distribuída a todos os serviços pelo mesmo mecanismo, seja um payload de CDN, um arquivo de configuração versionado ou um serviço central de flags.
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:
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:
- Um endpoint (ou parâmetro) de override para o time interno. GrowthBook oferece um
qa_mode, que desliga a atribuição aleatória e só permite variações forçadas explicitamente, além de um recurso de override por URL para forçar uma variação específica durante o teste manual (com a ressalva de que esse override pula as regras de segmentação, então serve para conferir a variação em si, não para validar o direcionamento). A Statsig segue o mesmo princípio com overrides por ID de usuário: quando o ID tem um override configurado, o resultado forçado é devolvido antes de qualquer regra normal ser avaliada, permitindo testar cada variação sem afetar dados de produção nem distorcer o experimento. - Um log de debug explicando por que aquela variação foi atribuída. A extensão GrowthBook DevTools, por exemplo, mostra em tempo real quais flags e experimentos estão ativos para aquela sessão e por que cada valor foi calculado daquele jeito. Ter algo equivalente, mesmo que seja só um endpoint interno que devolve “usuário X, experimento Y, variação Z, motivo W”, economiza horas quando alguém reportar “estou vendo a variação errada” e ninguém souber dizer por quê.
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
- Teste A/B client-side x server-side: a comparação completa entre as duas arquiteturas, o problema do flicker e quando escolher cada uma.
- Guia completo de feature flags: o conceito do zero, para quem ainda não tem essa infraestrutura na stack.
- O que é teste A/B: o guia completo: a estatística por trás de qualquer teste A/B, server-side ou não, incluindo o problema do peeking e o SRM (Sample Ratio Mismatch), que também pode aparecer num pipeline server-side mal instrumentado.
Referências
- GrowthBook. Node.js SDK. docs.growthbook.io/lib/node.
- GrowthBook. Sticky Bucketing. docs.growthbook.io/app/sticky-bucketing.
- Statsig. Node.js Server SDK e Testing your Gates/Experiments. docs.statsig.com/server/nodejsServerSDK, docs.statsig.com/guides/testing.
- LaunchDarkly. Choosing an SDK type. launchdarkly.com/docs/sdk/concepts/client-side-server-side.
- Harness FME (Split.io). How does Split ensure a consistent user experience. help.split.io.
- Fowler, M. Feature Toggles (aka Feature Flags). martinfowler.com/articles/feature-toggles.html.
- Statsig. CDN Edge Testing for Cached Resources. docs.statsig.com/guides/cdn-edge-testing.
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.