BLOG | NGINX

Executando testes A/B com NGINX e NGINX Plus

NGINX-Parte-de-F5-horiz-preto-tipo-RGB
Miniatura de Kevin Jones
Kevin Jones
Publicado em 25 de julho de 2016

Ao testar alterações em um aplicativo, há alguns fatores que você só pode medir em um ambiente de produção e não em um ambiente de testes de desenvolvimento. Exemplos incluem o efeito das alterações da interface do usuário no comportamento do usuário e o impacto no desempenho geral. Um método de teste comum é o teste A/B – também conhecido como teste de divisão – no qual uma proporção (geralmente pequena) de usuários é direcionada para a nova versão de um aplicativo, enquanto a maioria dos usuários continua usando a versão atual.

Nesta postagem do blog, exploraremos por que é importante realizar testes A/B ao implantar novas versões do seu aplicativo web e como usar o NGINX e o NGINX Plus para controlar qual versão do aplicativo os usuários veem. Exemplos de configuração ilustram como você pode usar diretivas, parâmetros e variáveis do NGINX e do NGINX Plus para obter testes A/B precisos e mensuráveis.

Por que fazer testes A/B?

Como mencionamos, o teste A/B permite medir a diferença no desempenho ou na eficácia do aplicativo entre duas versões. Talvez sua equipe de desenvolvimento queira alterar a disposição visual dos botões na interface do usuário ou reformular todo o processo do carrinho de compras, mas queira comparar a taxa de fechamento de transações para garantir que a alteração tenha o impacto comercial desejado. Usando o teste A/B, você pode enviar uma porcentagem definida do tráfego para a nova versão e o restante para a versão antiga e medir a eficácia de ambas as versões do aplicativo.

Ou talvez sua preocupação seja menos com o efeito no comportamento do usuário e mais com o impacto no desempenho. Digamos que você planeja implantar um grande conjunto de mudanças em seu aplicativo web e não acha que os testes em seu ambiente de garantia de qualidade realmente capturam o possível efeito no desempenho na produção. Nesse caso, uma implantação A/B permite que você exponha a nova versão a uma porcentagem pequena e definida de visitantes para medir o impacto do desempenho das alterações e aumentar gradualmente a porcentagem até que você finalmente implemente o aplicativo alterado para todos os usuários.

Usando NGINX e NGINX Plus para testes A/B

O NGINX e o NGINX Plus fornecem alguns métodos para controlar para onde o tráfego do aplicativo web é enviado. O primeiro método está disponível em ambos os produtos, enquanto o segundo está disponível apenas no NGINX Plus.

Ambos os métodos escolhem o destino de uma solicitação com base nos valores de uma ou mais variáveis NGINX que capturam características do cliente (como seu endereço IP) ou do URI da solicitação (como um argumento nomeado), mas as diferenças entre eles os tornam adequados para diferentes casos de uso de teste A/B:

  • O método split_clients escolhe o destino de uma solicitação com base em um hash dos valores das variáveis extraídos da solicitação. O conjunto de todos os valores de hash possíveis é dividido entre as versões do aplicativo, e você pode atribuir uma proporção diferente do conjunto a cada aplicativo. A escolha do destino acaba sendo aleatória.
  • O método de rota fixa oferece muito mais controle sobre o destino de cada solicitação. A escolha do aplicativo é baseada nos próprios valores das variáveis (não em um hash), então você pode definir explicitamente qual aplicativo receberá solicitações que tenham determinados valores de variáveis. Você também pode usar expressões regulares para basear a decisão apenas em partes de um valor de variável e pode escolher preferencialmente uma variável em vez de outra como base para a decisão.

Usando o método split_clients

Neste método, o bloco de configuração split_clients define uma variável para cada solicitação que determina para qual grupo upstream a diretiva proxy_pass envia a solicitação. Na configuração de exemplo abaixo, o valor da variável $appversion determina para onde a diretiva proxy_pass envia uma solicitação. O bloco split_clients usa uma função hash para definir dinamicamente o valor da variável para um dos dois nomes de grupo upstream, version_1a ou version_1b .

http { # ... # versão do application 1a versão upstream_1a { servidor 10.0.0.100:3001; servidor 10.0.0.101:3001; } # versão do application 1b versão upstream_1b { servidor 10.0.0.104:6002; servidor 10.0.0.105:6002; } split_clients "${arg_token} " $appversion { 95% version_1a; * version_1b; } servidor { # ... ouvir 80; localização / { proxy_set_header Host $host; proxy_pass http://$appversion; } } }

O primeiro parâmetro para a diretiva split_clients é a string ( "${arg_token} " no nosso exemplo) que é hash usando uma função MurmurHash2 durante cada solicitação. Os argumentos de URI estão disponíveis para o NGINX como variáveis chamadas $arg_ name – em nosso exemplo, a variável $arg_token captura o argumento de URI chamado token . Você pode usar qualquer variável NGINX ou sequência de variáveis como a sequência a ser hash. Por exemplo, você pode fazer hash do endereço IP do cliente (a variável $remote_addr ), da porta ( $remote_port ) ou da combinação de ambos. Você quer usar uma variável que é gerada antes que a solicitação seja processada pelo NGINX. Variáveis que contêm informações sobre a solicitação inicial do cliente são ideais; exemplos incluem o endereço IP/porta do cliente, como já mencionado, o URI da solicitação ou até mesmo cabeçalhos de solicitação HTTP.

O segundo parâmetro da diretiva split_clients ( $appversion em nosso exemplo) é a variável que é definida dinamicamente de acordo com o hash do primeiro parâmetro. As instruções dentro das chaves dividem a tabela de hash em “buckets”, cada um dos quais contém uma porcentagem dos hashes possíveis. Você pode criar quantos baldes quiser e eles não precisam ser todos do mesmo tamanho. Observe que a porcentagem do último intervalo é sempre representada pelo asterisco (*) em vez de um número específico, porque o número de hashes pode não ser divisível uniformemente nas porcentagens especificadas.

Em nosso exemplo, colocamos 95% dos hashes em um bucket associado ao grupo upstream version_1a e o restante em um segundo bucket associado ao version_1b . O intervalo de valores de hash possíveis é de 0 a 4.294.967.295, então o primeiro intervalo contém valores de 0 a cerca de 4.080.218.930 (95% do total). A variável $appversion é definida como o upstream associado ao bucket que contém o hash da variável $arg_token . Como exemplo específico, o valor de hash 100.000.000 cai no primeiro intervalo, então $appversion é definido dinamicamente como version_1a .

Testando a configuração split_clients

Para verificar se o bloco de configuração split_clients funciona conforme o esperado, criamos uma configuração de teste que divide as solicitações entre dois grupos upstream na mesma proporção acima (95% e o restante). Configuramos os servidores virtuais nos grupos para retornar uma string indicando qual grupo – version_1a ou version_1b – lidou com a solicitação (você pode ver a configuração do teste aqui ). Em seguida, usamos curl para gerar 20 solicitações, com o valor do token do argumento URI definido aleatoriamente pela execução do comando cat no arquivo urandom . Isto é puramente para fins de demonstração e randomização. Como pretendíamos, 1 em cada 20 solicitações (95%) foi atendida pela versão_1b (para resumir, mostramos apenas 10 das solicitações).

# para x em {1..20}; faça curl 127.0.0.1?token=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1); feito Token: p3Fsa86HfJDFwZ9ZnYz4QbaZLzcb70Ka Servido do site version_1a. 
Token: a7z7afUz7zQijXwsLGfj6mWyPU8sCRIZ Servido pelo site version_1a. 
Ficha: CjIr6W57NtPzChf4qbYMzD1Estif7jOH Servido do site version_1a. ... saída para 10 solicitações omitida ...
Token: gXq8cbG3jhl1LuYmICPTfDQT855gaO5y Servido do site version_1a. 
Ficha: VVqGuNN3cRU2slsl5AWOR93aQX8nXOpk Servido do site version_1a. 
Token: z7KnewxTX5Sp6wscT0fgmTDohTsQuCmy Servido do site version_1b!!
Token: fWOgH9WRrR0kLJZcIaYchpLhceaQgPD1 Servido pelo site version_1a. 
Token: mTADMXrVnwnr1cd5JE6QCSkgTwfWUnDk Servido pelo site version_1a.
Token: w7AzSNmNJtxWZaH6cXe2PWIFqst2o3oP Servido pelo site version_1a. 
Ficha: QR7ay0dA39MmVlXtzgOVsj6SBTPi8ECC Servido do site version_1a.

Usando o método de rota pegajosa

Em alguns casos, você pode querer definir uma rota estática tomando decisões de roteamento do cliente com base em todo ou parte do valor de uma variável NGINX. Você pode fazer isso com a diretiva de rota fixa , que está disponível apenas no NGINX Plus. A diretiva pega uma lista de um ou mais parâmetros e define a rota para o valor do primeiro parâmetro não vazio na lista. Podemos usar esse recurso para classificar preferencialmente qual variável da solicitação controla a escolha do destino e, assim, acomodar mais de um método de divisão de tráfego em uma única configuração.

Há duas abordagens diferentes para usar esse método.

  • Usando a abordagem do lado do cliente, você pode escolher a rota com base em variáveis NGINX que contêm valores que são inicialmente enviados diretamente do cliente, como o endereço IP do cliente ou cabeçalhos de solicitação HTTP específicos do navegador, como o User-Agent do cliente.
  • Com a abordagem do lado do servidor ou do lado do aplicativo , seu aplicativo decide a qual grupo de teste um usuário iniciante é atribuído e envia a ele um cookie ou um URI de redirecionamento que inclui um indicador de rota que representa o grupo escolhido. Na próxima vez que o cliente enviar uma solicitação, ele apresentará o cookie ou usará o URI de redirecionamento; a diretiva de rota fixa extrai o indicador de rota e encaminha a solicitação para o servidor apropriado.

Estamos usando a abordagem do lado do aplicativo em nosso exemplo: a diretiva de rota fixa no grupo upstream define preferencialmente a rota para o valor especificado no cookie fornecido por um servidor (capturado em $route_from_cookie ). Se o cliente não tiver um cookie, a rota será definida como um valor de um argumento para o URI da solicitação ( $route_from_uri ). O valor da rota determina então qual servidor no grupo upstream recebe a solicitação – o primeiro servidor se a rota for a , o segundo servidor se a rota for b (os dois servidores correspondem às duas versões do nosso aplicativo).

backend upstream { zona backend 64k;
servidor 10.0.0.200:8098 rota=a;
servidor 10.0.0.201:8099 rota=b;

rota pegajosa $route_from_cookie $route_from_uri;
}

Mas o a ou b é incorporado em uma sequência de caracteres muito mais longa no cookie ou URI real. Para extrair apenas a letra, configuramos um bloco de configuração de mapa para cada cookie e URI:

mapa $cookie_route $route_from_cookie { ~.(?P<rota>w+)$ $rota;
}

mapa $arg_route $route_from_uri {
~.(?P<rota>w+)$ $rota;
}

No primeiro bloco do mapa , a variável $cookie_route representa o valor de um cookie chamado ROUTE . A expressão regular na segunda linha, que usa a sintaxe Perl Compatible Regular Expression (PCRE), extrai parte do valor – neste caso, a sequência de caracteres ( w+ ) após o ponto – para a rota do grupo de captura nomeado e o atribui à variável interna com esse nome. O valor também é atribuído à variável $route_from_cookie na primeira linha, o que o torna disponível para passagem para a diretiva de rota fixa .

Como exemplo, o primeiro bloco de mapa extrai o valor “ a ” deste cookie e o atribui a $route_from_cookie :

ROTA=iDmDe26BdBDS28FuVJlWc1FH4b13x4fn .a

No segundo bloco de mapa , a variável $arg_route representa um argumento chamado route no URI da solicitação. Assim como com o cookie, a expressão regular na segunda linha extrai parte do URI – neste caso, é a string de caracteres ( w+ ) após o ponto no argumento route . O valor é lido no grupo de captura nomeado, atribuído a uma variável interna e também atribuído à variável $route_from_uri .

Como exemplo, o segundo bloco de mapa extrai o valor b deste URI e o atribui a $route_from_uri :

www.example.com/shopping/meu-carrinho?route=iLbLr35AeAET39GvWK2Xd2GI5c24y5go.b

Aqui está a configuração de amostra completa.

http { # ...
mapa $cookie_route $route_from_cookie {
~.(?P<route>w+)$ $route;
}

mapa $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}

upstream backend {
zona backend 64k;
servidor 10.0.0.200:8098 rota=a;
servidor 10.0.0.201:8099 rota=b;

rota fixa $route_from_cookie $route_from_uri;
}

servidor {
escutar 80;

localização / {
# ...
proxy_pass http://backend;
}
}
}

Testando a configuração da rota pegajosa

Quanto ao método split_clients , criamos uma configuração de teste, que você pode acessar aqui . Usamos curl para enviar um cookie chamado ROUTE ou para incluir um argumento de rota no URI. O valor do cookie ou argumento é uma string aleatória gerada pela execução do comando cat no arquivo urandom , com .a ou .b anexado.

Primeiro, testamos com um cookie que termina em .a :

# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a" 127.0.0.1 Valor do cookie: R0TdyJOJvxBkLC3f75Coa29I1pPySOeQ.a URI da solicitação: / Resultados: Site A - Executando na porta 8089

Depois testamos com um cookie que termina em .b .

# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).b" 127.0.0.1 Valor do cookie: JhdJZrScTnPBLhqmzK3podNRcJAIc8ST.b URI da solicitação: / Resultados: Site B - Executando na porta 8099

Por fim, testamos sem um cookie e, em vez disso, com um argumento de rota no URI da solicitação que termina em .a . A saída confirma que quando não há cookie (o campo Valor do Cookie está vazio), o NGINX Plus usa o valor da rota derivado do URI.

# curl 127.0.0.1?route=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a Valor do cookie:
URI da solicitação: /?route=yNp8pHskvukXK6XqbWefhVUcOBjbJv4v.a Resultados: Site A - Executando na porta 8089

Registrando e analisando seus resultados

O tipo de teste que descrevemos aqui é suficiente para verificar se uma configuração distribui solicitações conforme o esperado, mas interpretar os resultados dos testes A/B reais requer registro e análise muito mais detalhados de como as solicitações são processadas. A maneira correta de fazer registro e análise depende de muitos fatores e está além do escopo desta postagem, mas o NGINX e o NGINX Plus fornecem registro e monitoramento sofisticados e integrados do processamento de solicitações.

Você pode usar a diretiva log_format para definir um formato de log personalizado que inclua qualquer variável NGINX. Os valores variáveis registrados nos logs do NGINX ficam disponíveis para análise posteriormente. Para obter detalhes sobre registro personalizado e monitoramento de tempo de execução, consulte o Guia de administração do NGINX Plus .

Algumas últimas coisas a considerar

Ao projetar um experimento ou plano de teste A/B, certifique-se de que a maneira como você distribui as solicitações entre as versões do seu aplicativo não predetermine os resultados. Se você quiser um experimento completamente aleatório, usar o método split_clients e fazer hash de uma combinação de múltiplas variáveis fornece os melhores resultados. Por exemplo, gerar um token experimental exclusivo com base na combinação do cookie e do ID do usuário de uma solicitação fornece um padrão de teste mais aleatório do que fazer hash apenas do tipo e da versão do navegador do cliente, porque há uma boa chance de que muitos usuários tenham o mesmo tipo de navegador e versão e, portanto, todos serão direcionados para a mesma versão do aplicativo.

Você também precisa levar em conta que muitos usuários pertencem ao que é chamado de grupo misto . Eles acessam aplicativos da web de vários dispositivos – talvez tanto seus computadores de trabalho quanto de casa e também um dispositivo móvel, como um tablet ou smartphone. Esses usuários têm vários endereços IP de cliente, portanto, se você usar o endereço IP do cliente como base para escolher a versão do aplicativo, eles poderão ver as duas versões do seu aplicativo, corrompendo seus resultados experimentais.

Provavelmente, a solução mais fácil é exigir que os usuários efetuem login para que você possa rastrear o cookie de sessão deles, como em nosso exemplo do método de rota fixa . Dessa forma você pode rastreá-los e sempre enviá-los para a mesma versão que eles viram na primeira vez que acessaram o teste. Se você não puder fazer isso, às vezes faz sentido colocar os usuários em grupos que provavelmente não mudarão durante o teste, por exemplo, usando geolocalização para mostrar uma versão para usuários em Los Angeles e outra para usuários em São Francisco.

Conclusão

O teste A/B é uma maneira eficaz de analisar e rastrear alterações em seu aplicativo e monitorar seu desempenho dividindo diferentes quantidades de tráfego entre servidores alternativos. Tanto o NGINX quanto o NGINX Plus fornecem diretivas, parâmetros e variáveis que podem ser usados para construir uma estrutura sólida para testes A/B. Eles também permitem que você registre detalhes valiosos sobre cada solicitação. Divirta-se testando!

Experimente o NGINX Plus e o método de rota fixa você mesmo – comece seu teste gratuito de 30 dias hoje mesmo ou entre em contato conosco para discutir seus casos de uso .


"Esta postagem do blog pode fazer referência a produtos que não estão mais disponíveis e/ou não têm mais suporte. Para obter as informações mais atualizadas sobre os produtos e soluções F5 NGINX disponíveis, explore nossa família de produtos NGINX . O NGINX agora faz parte do F5. Todos os links anteriores do NGINX.com redirecionarão para conteúdo semelhante do NGINX no F5.com."