Teste A/B em Go: Dividindo o tráfego como um profissional

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: Por que seu serviço Go precisa de experimentos

\n\n

Imagine: você reescreveu a página de pagamento em uma nova stack. Tudo funciona, o código em Go voa, os testes estão verdes. Mas a conversão caiu 15%. Por mais que você tente, em condições de produção é impossível prever o comportamento dos usuários. É aqui que entra o teste A/B em Go — a única maneira de tomar decisões baseadas em dados, e não na intuição.

\n\n

Neste artigo, vamos abordar como implementar o teste de divisão Golang na sua aplicação. Você aprenderá como implementar um sistema de buckets, evitar a maldição da comparação múltipla e não quebrar a produção. Vamos lá!

\n\n

1. Arquitetura básica de um teste A/B em Go

\n\n

Qualquer teste A/B Golang consiste em três pilares: o divisor de tráfego, o sistema de logging e o motor estatístico. Vamos começar pelo primeiro.

\n\n

1.1. Distribuição determinística: algoritmo de buckets

\n\n

Regra chave: um mesmo usuário deve sempre cair no mesmo grupo. Usamos hashing consistente baseado no ID do usuário:

\n\n
package abtest\n\nimport (\n    "crypto/sha256"\n    "encoding/binary"\n    "fmt"\n)\n\n// Experiment define a estrutura do experimento\ntype Experiment struct {\n    Name   string\n    Buckets []Bucket\n}\n\ntype Bucket struct {\n    Name  string\n    Ratio float64 // 0.0 - 1.0\n}\n\n// AssignGroup retorna o nome do grupo para o userID\nfunc (e *Experiment) AssignGroup(userID string) string {\n    hash := sha256.Sum256([]byte(fmt.Sprintf("%s:%s", e.Name, userID)))\n    value := binary.BigEndian.Uint64(hash[:8]) % 1000\n    threshold := uint64(0)\n    \n    for _, b := range e.Buckets {\n        threshold += uint64(b.Ratio * 1000)\n        if value < threshold {\n            return b.Name\n        }\n    }\n    return "control" // fallback\n}\n
\n\n

Por que sha256? Ele fornece uma distribuição uniforme e é resistente a colisões. O teste de bucket Go com essa abordagem garante que o usuário não mude entre grupos ao reiniciar o servidor.

\n\n

1.2. Middleware para servidor HTTP

\n\n

Integramos o teste A/B Go no seu roteador. Exemplo com net/http:

\n\n
func ABMiddleware(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        userID := r.Header.Get("X-User-ID") // ou de um cookie\n        if userID == "" {\n            next.ServeHTTP(w, r)\n            return\n        }\n        \n        exp := &Experiment{\n            Name: "checkout_v2",\n            Buckets: []Bucket{\n                {Name: "control", Ratio: 0.5},\n                {Name: "treatment", Ratio: 0.5},\n            },\n        }\n        \n        group := exp.AssignGroup(userID)\n        ctx := context.WithValue(r.Context(), "ab_group", group)\n        next.ServeHTTP(w, r.WithContext(ctx))\n    })\n}\n
\n\n

Agora, nos handlers, você pode ler o grupo através de r.Context().Value("ab_group"). Simples e eficiente.

\n\n

2. Técnicas avançadas: Feature Flags e bandidos multi-armados

\n\n

Um A/B simples é bom, mas em experimentos reais em Golang muitas vezes é necessária flexibilidade.

\n\n

2.1. Feature Flags com ativação dinâmica

\n\n

Às vezes, é necessário ativar um recurso para 10% dos usuários e, em seguida, aumentar gradualmente para 100%. Implementamos via Redis:

\n\n
type DynamicExperiment struct {\n    Client *redis.Client\n    Name   string\n}\n\nfunc (d *DynamicExperiment) IsEnabled(userID string) bool {\n    // Obtém a porcentagem atual do Redis (pode ser alterada sem deploy)\n    percent, err := d.Client.Get(context.Background(), d.Name).Float64()\n    if err != nil {\n        return false // desligado por padrão\n    }\n    \n    hash := sha256.Sum256([]byte(userID))\n    value := binary.BigEndian.Uint64(hash[:8]) % 1                

Blogs

Book Recommendations