Al probar cambios en una aplicación, hay algunos factores que solo se pueden medir en un entorno de producción, y no en un entorno de pruebas de desarrollo. Algunos ejemplos son el efecto de los cambios en la interfaz de usuario sobre el comportamiento de los usuarios y su repercusión en el rendimiento general. Un método de prueba habitual es la prueba A/B —también conocida como prueba dividida—, en la que se dirige a una proporción (normalmente pequeña) de usuarios a la nueva versión de una aplicación, mientras que la mayoría sigue utilizando la versión actual.
En esta entrada del blog analizaremos por qué es importante realizar pruebas A/B al implementar nuevas versiones de una aplicación web y cómo utilizar NGINX y NGINX Plus para controlar qué versión de la aplicación ven los usuarios. Los ejemplos de configuración muestran cómo utilizar las directivas, los parámetros y las variables de NGINX y NGINX Plus para realizar pruebas A/B precisas y cuantificables.
¿Por qué realizar pruebas A/B?
Como hemos mencionado, las pruebas A/B permiten medir la diferencia de rendimiento o eficacia de una aplicación entre dos versiones. Puede que su equipo de desarrollo quiera cambiar la disposición visual de los botones de la interfaz de usuario o rediseñar por completo el proceso del carrito de compra, pero desee comparar la tasa de cierre de las transacciones para asegurarse de que el cambio tenga el impacto empresarial esperado. Mediante las pruebas A/B, puede dirigir un porcentaje determinado del tráfico a la nueva versión y el resto a la versión anterior, y medir la eficacia de ambas versiones de la aplicación.
O quizá le preocupe menos el efecto en el comportamiento de los usuarios y más el impacto en el rendimiento. Supongamos que tiene previsto implementar un gran conjunto de cambios en su aplicación web y considera que las pruebas realizadas en su entorno de control de calidad no reflejan fielmente el posible impacto en el rendimiento en producción. En este caso, una implementación A/B le permite mostrar la nueva versión a un porcentaje reducido y definido de visitantes para medir el impacto de los cambios en el rendimiento y aumentar gradualmente ese porcentaje hasta que, finalmente, implemente la aplicación modificada para todos los usuarios.
Uso de NGINX y NGINX Plus para pruebas A/B
NGINX y NGINX Plus ofrecen un par de métodos para controlar adónde se envía el tráfico de las aplicaciones web. El primer método está disponible en ambos productos, mientras que el segundo solo está disponible en NGINX Plus.
Ambos métodos eligen el destino de una solicitud en función de los valores de una o varias variables de NGINX que recogen características del cliente (como su dirección IP) o del URI de la solicitud (como un argumento con nombre), pero las diferencias entre ellos hacen que sean adecuados para distintos casos de uso de pruebas A/B:
- El método
split_clientselige el destino de una solicitud basándose en un hash de los valores de las variables extraídos de la solicitud. El conjunto de todos los valores hash posibles se divide entre las versiones de la aplicación, y se puede asignar una proporción diferente del conjunto a cada aplicación. La elección del destino acaba siendo aleatoria. - El método
stickyroutele proporciona un control mucho mayor sobre el destino de cada solicitud. La aplicación se elige en función de los propios valores de las variables (no de un hash), por lo que puede establecer explícitamente qué aplicación recibe las solicitudes que tienen determinados valores de variables. También puede utilizar expresiones regulares para basar la decisión únicamente en partes del valor de una variable y elegir una variable con preferencia sobre otra como base para la decisión.
Uso del método split_clients Method
En este método, el bloque de configuración split_clients establece una variable para cada solicitud que determina a qué grupo de servidores ascendentes envía la solicitud la directiva proxy_pass. En la configuración de ejemplo que aparece a continuación, el valor de la variable $appversion determina adónde envía una solicitud la directiva proxy_pass. El bloque split_clients utiliza una función hash para asignar dinámicamente a la variable el nombre de uno de los dos grupos de servidores ascendentes: version_1a o version_1b.
http { # ...
# application version 1a
upstream version_1a {
server 10.0.0.100:3001;
server 10.0.0.101:3001;
}
# application version 1b
upstream version_1b {
server 10.0.0.104:6002;
server 10.0.0.105:6002;
}
split_clients "${arg_token}" $appversion {
95% version_1a;
* version_1b;
}
server {
# ...
listen 80;
location / {
proxy_set_header Host $host;
proxy_pass http://$appversion;
}
}
}El primer parámetro de la directiva split_clients es la cadena ("${arg_token}" en nuestro ejemplo) a la que se aplica un hash mediante una función MurmurHash2 durante cada solicitud. Los argumentos del URI están disponibles para NGINX como variables denominadas $arg_name ; en nuestro ejemplo, la variable $arg_token captura el argumento del URI denominado token. Puede utilizar cualquier variable de NGINX o cadena de variables como cadena a la que aplicar el hash. Por ejemplo, puede aplicar el hash a la dirección IP del cliente (la variable $remote_addr), al puerto ($remote_port) o a la combinación de ambos. Conviene utilizar una variable que se genere antes de que NGINX procese la solicitud. Las variables que contienen información sobre la solicitud inicial del cliente son ideales; algunos ejemplos son la dirección IP o el puerto del cliente, como ya se ha mencionado, el URI de la solicitud o incluso las cabeceras de la solicitud HTTP.
El segundo parámetro de la directiva split_clients ($appversion en nuestro ejemplo) es la variable cuyo valor se establece dinámicamente en función del hash del primer parámetro. Las instrucciones entre llaves dividen la tabla hash en «grupos», cada uno de los cuales contiene un porcentaje de los posibles hashes. Puede crear tantos grupos como desee y no es necesario que todos tengan el mismo tamaño. Tenga en cuenta que el porcentaje del último grupo siempre se representa mediante un asterisco (*) en lugar de un número concreto, ya que es posible que el número de hashes no se pueda dividir de manera uniforme según los porcentajes especificados.
En nuestro ejemplo, colocamos el 95 % de los hashes en un segmento asociado al grupo de servidores upstream version_1a, y el resto en un segundo segmento asociado a version_1b. El intervalo de valores hash posibles va de 0 a 4.294.967.295, por lo que el primer segmento contiene valores desde 0 hasta aproximadamente 4.080.218.930 (el 95 % del total). La variable $appversion se establece en el grupo de servidores upstream asociado al segmento que contiene el hash de la variable $arg_token. Como ejemplo concreto, el valor hash 100.000.000 se encuentra en el primer segmento, por lo que $appversion se establece dinámicamente en version_1a.
Prueba de la configuración de split_clients Configuration
Para verificar que el bloque de configuración split_clients funciona según lo previsto, creamos una configuración de prueba que distribuye las solicitudes entre dos grupos de servidores upstream en la misma proporción que antes (el 95 % y el resto). Configuramos los servidores virtuales de los grupos para que devolvieran una cadena que indicara qué grupo —version_1a o version_1b— había gestionado la solicitud (puede consultar la configuración de prueba aquí). A continuación, utilizamos curl para generar 20 solicitudes, estableciendo aleatoriamente el valor del argumento de URI token mediante la ejecución del comando cat en el archivo urandom. Esto se hizo únicamente con fines de demostración y aleatorización. Tal como pretendíamos, 1 de cada 20 solicitudes (el 95 %) fue atendida por version_1b (para abreviar, solo mostramos 10 de las solicitudes).
# for x in {1..20}; do curl 127.0.0.1?token=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1); doneToken: p3Fsa86HfJDFwZ9ZnYz4QbaZLzcb70Ka Served from site version_1a.
Token: a7z7afUz7zQijXwsLGfj6mWyPU8sCRIZ Served from site version_1a.
Token: CjIr6W57NtPzChf4qbYMzD1Estif7jOH Served from site version_1a.
... output for 10 requests omitted ...
Token: gXq8cbG3jhl1LuYmICPTfDQT855gaO5y Served from site version_1a.
Token: VVqGuNN3cRU2slsl5AWOR93aQX8nXOpk Served from site version_1a.
Token: z7KnewxTX5Sp6wscT0fgmTDohTsQuCmy Served from site version_1b!!
Token: fWOgH9WRrR0kLJZcIaYchpLhceaQgPD1 Served from site version_1a.
Token: mTADMXrVnwnr1cd5JE6QCSkgTwfWUnDk Served from site version_1a.
Token: w7AzSNmNJtxWZaH6cXe2PWIFqst2o3oP Served from site version_1a.
Token: QR7ay0dA39MmVlXtzgOVsj6SBTPi8ECC Served from site version_1a.Uso del método sticky route Method
En algunos casos, puede que quiera definir una ruta estática tomando decisiones de enrutamiento del cliente basadas en la totalidad o en parte del valor de una variable de NGINX. Puede hacerlo con la directiva sticky route, que solo está disponible en NGINX Plus. La directiva acepta una lista de uno o varios parámetros y establece la ruta en el valor del primer parámetro no vacío de la lista. Podemos usar esta función para establecer un orden de prioridad entre las variables de la solicitud que controlan la elección del destino y, de este modo, admitir más de un método de división del tráfico en una única configuración.
Hay dos enfoques diferentes para utilizar este método.
- Con el enfoque del lado del cliente puede elegir la ruta en función de variables de NGINX que contienen valores enviados inicialmente de forma directa por el cliente, como la dirección IP del cliente o encabezados de solicitud HTTP específicos del navegador, como el
User-Agentdel cliente. - Con el enfoque del lado del servidor o de la aplicación, la aplicación decide a qué grupo de prueba se asigna un usuario nuevo y le envía una cookie o un URI de redirección que incluye un indicador de ruta correspondiente al grupo elegido. La próxima vez que el cliente envía una solicitud, presenta la cookie o utiliza el URI de redirección; la directiva
stickyrouteextrae el indicador de ruta y reenvía la solicitud al servidor correspondiente.
En nuestro ejemplo utilizamos el enfoque del lado de la aplicación: la directiva sticky route del grupo upstream establece preferentemente la ruta en el valor especificado en la cookie proporcionada por un servidor (capturado en $route_from_cookie). Si el cliente no tiene ninguna cookie, la ruta se establece en un valor de un argumento del URI de la solicitud ($route_from_uri). A continuación, el valor de la ruta determina qué server del grupo upstream recibe la solicitud: el primer servidor si la ruta es a y el segundo servidor si la ruta es b (los dos servidores corresponden a las dos versiones de nuestra aplicación).
upstream backend { zone backend 64k;
server 10.0.0.200:8098 route=a;
server 10.0.0.201:8099 route=b;
sticky route $route_from_cookie $route_from_uri;
}Pero la letra a o b está integrada en una cadena de caracteres mucho más larga en la cookie o el URI reales. Para extraer únicamente la letra, configuramos un bloque de configuración map para la cookie y otro para el URI:
map $cookie_route $route_from_cookie { ~.(?P<route>w+)$ $route;
}
map $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}En el primer bloque map, la variable $cookie_route representa el valor de una cookie denominada ROUTE. La expresión regular de la segunda línea, que utiliza la sintaxis de expresiones regulares compatibles con Perl (PCRE), extrae parte del valor —en este caso, la cadena de caracteres (w+) situada después del punto— en el grupo de captura con nombre route y la asigna a la variable interna con ese nombre. El valor también se asigna a la variable $route_from_cookie de la primera línea, lo que permite pasarlo a la directiva sticky route.
Por ejemplo, el primer bloque map extrae el valor «a» de esta cookie y lo asigna a $route_from_cookie:
ROUTE=iDmDe26BdBDS28FuVJlWc1FH4b13x4fn.aEn el segundo bloque map, la variable $arg_route representa un argumento denominado route en el URI de la solicitud. Al igual que con la cookie, la expresión regular de la segunda línea extrae parte del URI; en este caso, se trata de la cadena de caracteres (w+) situada después del punto en el argumento route. El valor se lee en el grupo de captura con nombre, se asigna a una variable interna y también a la variable $route_from_uri.
Por ejemplo, el segundo bloque map extrae el valor b de este URI y lo asigna a $route_from_uri:
www.example.com/shopping/my-cart?route=iLbLr35AeAET39GvWK2Xd2GI5c24y5go.bAquí tienes la configuración de ejemplo completa.
http { # ...
map $cookie_route $route_from_cookie {
~.(?P<route>w+)$ $route;
}
map $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}
upstream backend {
zone backend 64k;
server 10.0.0.200:8098 route=a;
server 10.0.0.201:8099 route=b;
sticky route $route_from_cookie $route_from_uri;
}
server {
listen 80;
location / {
# ...
proxy_pass http://backend;
}
}
}Prueba de la configuración sticky route Configuration
En cuanto al método split_clients, creamos una configuración de prueba, a la que puede acceder aquí. Utilizamos curl para enviar una cookie llamada ROUTE o incluir un argumento route en el URI. El valor de la cookie o del argumento es una cadena aleatoria generada al ejecutar el comando cat en el archivo urandom, con .a o .b añadido al final.
Primero, hacemos una prueba con una cookie que termina en .a:
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a" 127.0.0.1Cookie Value: R0TdyJOJvxBkLC3f75Coa29I1pPySOeQ.a
Request URI: /
Results: Site A - Running on port 8089A continuación, hacemos una prueba con una cookie que termina en .b.
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).b" 127.0.0.1Cookie Value: JhdJZrScTnPBLhqmzK3podNRcJAIc8ST.b
Request URI: /
Results: Site B - Running on port 8099Por último, realizamos una prueba sin una cookie y, en su lugar, con un argumento de ruta en el URI de la solicitud que termina en .a. El resultado confirma que, cuando no hay ninguna cookie (el campo Cookie Value está vacío), NGINX Plus utiliza el valor de ruta obtenido del URI.
# curl 127.0.0.1?route=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).aCookie Value:
Request URI: /?route=yNp8pHskvukXK6XqbWefhVUcOBjbJv4v.a
Results: Site A - Running on port 8089Registro y análisis de los resultados
El tipo de pruebas que describimos aquí es suficiente para verificar que una configuración distribuye las solicitudes según lo previsto, pero interpretar los resultados de pruebas A/B reales requiere un registro y un análisis mucho más detallados de cómo se procesan las solicitudes. La forma adecuada de realizar el registro y el análisis depende de muchos factores y queda fuera del alcance de esta publicación, pero NGINX y NGINX Plus ofrecen funciones avanzadas e integradas de registro y supervisión del procesamiento de solicitudes.
Puede utilizar la directiva log_format para definir un formato de registro personalizado que incluya cualquier variable de NGINX. Los valores de las variables registrados en los registros de NGINX estarán disponibles para analizarlos posteriormente. Para obtener más información sobre el registro personalizado y la supervisión en tiempo de ejecución, consulte la Guía de administración de NGINX Plus.
Algunas consideraciones finales
Al diseñar un experimento o un plan de pruebas A/B, asegúrate de que la forma en que distribuyes las solicitudes entre las distintas versiones de tu aplicación no predetermine los resultados. Si quieres realizar un experimento completamente aleatorio, el uso del método split_clients y del hash de una combinación de varias variables ofrece los mejores resultados. Por ejemplo, generar un token experimental único a partir de la combinación de la cookie y el ID de usuario de una solicitud proporciona un patrón de prueba más aleatorio que aplicar el hash únicamente al tipo y la versión del navegador del cliente, ya que es muy probable que muchos usuarios tengan el mismo tipo y versión de navegador y, por tanto, todos sean dirigidos a la misma versión de la aplicación.
También debe tener en cuenta que muchos usuarios pertenecen a lo que se denomina el grupo mixto. Acceden a aplicaciones web desde varios dispositivos, como el ordenador del trabajo, el de casa y también un dispositivo móvil, como una tableta o un smartphone. Estos usuarios tienen varias direcciones IP de cliente, por lo que, si utiliza la dirección IP del cliente como criterio para elegir la versión de la aplicación, podrían ver ambas versiones, lo que invalidaría los resultados del experimento.
Probablemente, la solución más sencilla sea exigir a los usuarios que inicien sesión para poder hacer un seguimiento de su cookie de sesión, como en nuestro ejemplo del método sticky route. De este modo, podrá hacer un seguimiento de los usuarios y dirigirlos siempre a la misma versión que vieron la primera vez que accedieron a la prueba. Si esto no es posible, a veces conviene agrupar a los usuarios según criterios que probablemente no cambien durante la prueba; por ejemplo, usar la geolocalización para mostrar una versión a los usuarios de Los Ángeles y otra a los de San Francisco.
Conclusión
Las pruebas A/B son una forma eficaz de analizar y hacer un seguimiento de los cambios en su aplicación, así como de supervisar su rendimiento distribuyendo distintas cantidades de tráfico entre servidores alternativos. Tanto NGINX como NGINX Plus proporcionan directivas, parámetros y variables que pueden utilizarse para crear un marco sólido para las pruebas A/B. También permiten registrar información valiosa sobre cada solicitud. ¡Disfrute de las pruebas!
Pruebe NGINX Plus y el método sticky route – comience hoy mismo su prueba gratuita de 30 días o póngase en contacto con nosotros para hablar sobre sus casos de uso.
Sobre el autor

Artículos relacionados del blog
Únete a F5 NGINX en AppWorld 2024
Descubre las numerosas razones para asistir al evento insignia de F5, que incluirá un emocionante anuncio sobre la cartera de productos NGINX.
Rotación de certificados SSL/TLS sin reinicios en NGINX Open Source
La función de diccionario compartido permite configurar NGINX sin necesidad de reiniciarlo al renovar los certificados SSL/TLS.
Exposición de APIs en Kubernetes
La unidad 2 de Microservices March 2022 se centra en cómo exponer tus API en Kubernetes. Aquí tienes enlaces a los recursos que necesitas: la retransmisión en directo del 14 de marzo, artículos de blog y otros recursos de preparación, y el laboratorio práctico «Protege las API de Kubernetes mediante la limitación de solicitudes».
NGINX Sprint 2.0: visión clara, código renovado y nuevos compromisos con el código abierto
Hoy, en NGINX Sprint 2.0, anunciamos cómo respaldaremos nuestra visión del futuro de las aplicaciones modernas con una mayor presencia en la comunidad de código abierto, incluida la participación en el proyecto comunitario Kubernetes, nuevos proyectos de software de código abierto y la arquitectura de referencia para aplicaciones modernas.
Presentamos NGINX Plus R22
NGINX Plus R22 incorpora nuevas funcionalidades, como la compatibilidad con OCSP para verificar la revocación de certificados en TLS mutuo, el uso de varios proveedores de identidad OIDC en una configuración, el seguimiento en tiempo real de la limitación de solicitudes y conexiones en el panel de NGINX Plus y mejoras en NGINX JavaScript.
Presentamos NGINX Plus R17
NGINX Plus R17 incorpora compatibilidad con la limitación de velocidad en dos fases y con TLS 1.3, la versión más reciente del protocolo Transport Layer Security. La configuración de OpenID Connect es más sencilla y NGINX ModSecurity WAF es el doble de rápido que antes. También se ha actualizado el módulo JavaScript de NGINX.