Lorsque vous testez des modifications apportées à une application, certains facteurs ne peuvent être mesurés que dans un environnement de production plutôt que sur un banc d'essai de développement. Les exemples incluent l’effet des modifications de l’interface utilisateur sur le comportement de l’utilisateur et l’impact sur les performances globales. Une méthode de test courante est le test A/B , également connu sous le nom de test fractionné , dans lequel une proportion (généralement faible) d’utilisateurs est dirigée vers la nouvelle version d’une application tandis que la plupart des utilisateurs continuent d’utiliser la version actuelle.
Dans cet article de blog, nous explorerons pourquoi il est important d'effectuer des tests A/B lors du déploiement de nouvelles versions de votre application Web et comment utiliser NGINX et NGINX Plus pour contrôler la version d'une application que les utilisateurs voient. Les exemples de configuration illustrent comment vous pouvez utiliser les directives, paramètres et variables NGINX et NGINX Plus pour réaliser des tests A/B précis et mesurables.
Comme nous l’avons mentionné, les tests A/B vous permettent de mesurer la différence de performances ou d’efficacité d’une application entre deux versions. Peut-être que votre équipe de développement souhaite modifier la disposition visuelle des boutons dans l'interface utilisateur ou remanier l'ensemble du processus de panier d'achat, mais souhaite comparer le taux de clôture des transactions pour s'assurer que le changement a l'impact commercial souhaité. Grâce aux tests A/B, vous pouvez envoyer un pourcentage défini du trafic vers la nouvelle version et le reste vers l'ancienne version et mesurer l'efficacité des deux versions de l'application.
Ou peut-être que votre préoccupation concerne moins l’effet sur le comportement de l’utilisateur et davantage l’impact sur les performances. Supposons que vous prévoyez de déployer un vaste ensemble de modifications sur votre application Web et que vous ne pensez pas que les tests au sein de votre environnement d’assurance qualité capturent réellement l’effet possible sur les performances en production. Dans ce cas, un déploiement A/B vous permet d'exposer la nouvelle version à un petit pourcentage défini de visiteurs pour mesurer l'impact des modifications sur les performances et d'augmenter progressivement le pourcentage jusqu'à ce que vous déployiez finalement l'application modifiée à tous les utilisateurs.
NGINX et NGINX Plus fournissent plusieurs méthodes pour contrôler où le trafic des applications Web est envoyé. La première méthode est disponible dans les deux produits, tandis que la seconde n'est disponible que dans NGINX Plus.
Les deux méthodes choisissent la destination d'une requête en fonction des valeurs d'une ou plusieurs variables NGINX qui capturent les caractéristiques du client (comme son adresse IP) ou de l'URI de la requête (comme un argument nommé), mais les différences entre elles les rendent adaptées à différents cas d'utilisation de tests A/B :
split_clients
choisit la destination d'une requête en fonction d'un hachage des valeurs de variables extraites de la requête. L'ensemble de toutes les valeurs de hachage possibles est divisé entre les versions de l'application et vous pouvez attribuer une proportion différente de l'ensemble à chaque application. Le choix de la destination finit par être aléatoire.de routage
collant
vous offre un contrôle beaucoup plus important sur la destination de chaque requête. Le choix de l'application est basé sur les valeurs des variables elles-mêmes (pas sur un hachage), vous pouvez donc définir explicitement quelle application reçoit les requêtes ayant certaines valeurs de variables. Vous pouvez également utiliser des expressions régulières pour baser la décision sur des parties seulement d'une valeur de variable et choisir de préférence une variable plutôt qu'une autre comme base de la décision.split_clients
Dans cette méthode, le bloc de configuration split_clients
définit une variable pour chaque demande qui détermine à quel groupe en amont la directive proxy_pass
envoie la demande. Dans l'exemple de configuration ci-dessous, la valeur de la variable $appversion
détermine où la directive proxy_pass
envoie une requête. Le bloc split_clients
utilise une fonction de hachage pour définir dynamiquement la valeur de la variable sur l'un des deux noms de groupe en amont, soit version_1a ou version_1b .
http { # ... # version de application 1a version amont_1a { serveur 10.0.0.100:3001; serveur 10.0.0.101:3001; } # version de application 1b version amont_1b { serveur 10.0.0.104:6002; serveur 10.0.0.105:6002; } split_clients "${arg_token} " $appversion { 95% version_1a; * version_1b; } serveur { # ... écoute 80; emplacement / { proxy_set_header Hôte $host; proxy_pass http://$appversion; } } }
Le premier paramètre de la directive split_clients
est la chaîne ( "${arg_token} "
dans notre exemple) qui est haché à l'aide d'une fonction MurmurHash2 lors de chaque requête. Les arguments URI sont disponibles pour NGINX sous forme de variables appelées $ arg_name
– dans notre exemple, la variable $arg_token
capture l'argument URI appelé token . Vous pouvez utiliser n'importe quelle variable NGINX ou chaîne de variables comme chaîne à hacher. Par exemple, vous pouvez hacher l'adresse IP du client (la variable $remote_addr
), le port ( $remote_port
) ou la combinaison des deux. Vous souhaitez utiliser une variable générée avant le traitement de la requête par NGINX. Les variables contenant des informations sur la requête initiale du client sont idéales ; par exemple, l'adresse IP/le port du client, comme mentionné précédemment, l'URI de la requête, ou encore les en-têtes de requête HTTP.
Le deuxième paramètre de la directive split_clients
( $appversion
dans notre exemple) est la variable qui est définie dynamiquement en fonction du hachage du premier paramètre. Les instructions à l’intérieur des accolades divisent la table de hachage en « compartiments », chacun contenant un pourcentage des hachages possibles. Vous pouvez créer n'importe quel nombre de buckets et ils ne doivent pas tous avoir la même taille. Notez que le pourcentage du dernier bucket est toujours représenté par l'astérisque (*) plutôt que par un nombre spécifique, car le nombre de hachages peut ne pas être divisible de manière égale dans les pourcentages spécifiés.
Dans notre exemple, nous mettons 95 % des hachages dans un bucket associé au groupe en amont version_1a et le reste dans un deuxième bucket associé à version_1b . La plage des valeurs de hachage possibles est comprise entre 0 et 4 294 967 295, de sorte que le premier compartiment contient des valeurs comprises entre 0 et environ 4 080 218 930 (95 % du total). La variable $appversion
est définie sur l'amont associé au bucket contenant le hachage de la variable $arg_token
. À titre d'exemple spécifique, la valeur de hachage 100 000 000 se trouve dans le premier bucket, donc $appversion
est défini dynamiquement sur version_1a .
split_clients
Pour vérifier que le bloc de configuration split_clients
fonctionne comme prévu, nous avons créé une configuration de test qui divise les requêtes entre deux groupes en amont dans la même proportion que ci-dessus (95 % et le reste). Nous avons configuré les serveurs virtuels dans les groupes pour renvoyer une chaîne indiquant quel groupe – version_1a ou version_1b – a traité la demande (vous pouvez voir la configuration de test ici ). Nous avons ensuite utilisé curl
pour générer 20 requêtes, avec la valeur du jeton d’argument URI définie de manière aléatoire en exécutant la commande cat
sur le fichier urandom
. Ceci est purement à des fins de démonstration et de randomisation. Comme prévu, 1 requête sur 20 (95 %) a été traitée à partir de la version_1b (par souci de concision, nous n'affichons que 10 des requêtes).
# pour x dans {1..20} ; do curl 127.0.0.1?token=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1); terminé Jeton : p3Fsa86HfJDFwZ9ZnYz4QbaZLzcb70Ka Servi depuis le site version_1a.
Jeton : a7z7afUz7zQijXwsLGfj6mWyPU8sCRIZ Servi depuis le site version_1a.
Jeton: CjIr6W57NtPzChf4qbYMzD1Estif7jOH Servi depuis le site version_1a. ... sortie pour 10 requêtes omise ...
Jeton : gXq8cbG3jhl1LuYmICPTfDQT855gaO5y Servi depuis le site version_1a.
Jeton: VVqGuNN3cRU2slsl5AWOR93aQX8nXOpk Servi depuis le site version_1a.
Jeton : z7KnewxTX5Sp6wscT0fgmTDohTsQuCmy Servi depuis le site version_1b !!
Jeton : fWOgH9WRrR0kLJZcIaYchpLhceaQgPD1 Servi depuis le site version_1a.
Jeton : mTADMXrVnwnr1cd5JE6QCSkgTwfWUnDk Servi à partir du site version_1a.
Jeton : w7AzSNmNJtxWZaH6cXe2PWIFqst2o3oP Servi depuis le site version_1a.
Jeton: QR7ay0dA39MmVlXtzgOVsj6SBTPi8ECC Servi depuis le site version_1a.
de la route
collante
Dans certains cas, vous souhaiterez peut-être définir un itinéraire statique en prenant des décisions de routage client basées sur tout ou partie de la valeur d'une variable NGINX. Vous pouvez le faire avec la directive sticky
route
, qui n'est disponible que dans NGINX Plus. La directive prend une liste d'un ou plusieurs paramètres et définit la route sur la valeur du premier paramètre non vide de la liste. Nous pouvons utiliser cette fonctionnalité pour classer de manière préférentielle la variable de la demande qui contrôle le choix de la destination, et ainsi prendre en charge plusieurs méthodes de répartition du trafic dans une seule configuration.
Il existe deux approches différentes pour utiliser cette méthode.
agent utilisateur
du client.sticky
route
extrait l'indicateur de route et transmet la requête au serveur approprié.Dans notre exemple, nous utilisons l'approche côté application : la directive sticky
route
dans le groupe en amont
définit préférentiellement la route sur la valeur spécifiée dans le cookie fourni par un serveur (capturé dans $route_from_cookie
). Si le client n'a pas de cookie, l'itinéraire est défini sur une valeur provenant d'un argument de l'URI de la requête ( $route_from_uri
). La valeur de la route détermine ensuite quel serveur
du groupe en amont reçoit la requête : le premier serveur si la route est a
, le deuxième serveur si la route est b
(les deux serveurs correspondent aux deux versions de notre application).
backend en amont { zone backend 64k;
serveur 10.0.0.200:8098 route=a;
serveur 10.0.0.201:8099 route=b;
route collante $route_from_cookie $route_from_uri;
}
Mais le a
ou le b
est intégré dans une chaîne de caractères beaucoup plus longue dans le cookie ou l'URI réel. Pour extraire uniquement la lettre, nous configurons un bloc de configuration de carte
pour chacun des cookies et de l'URI :
carte $cookie_route $route_from_cookie { ~.(?P<route>w+)$ $route;
}
carte $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}
Dans le premier bloc de carte
, la variable $cookie_route
représente la valeur d'un cookie nommé ROUTE . L'expression régulière de la deuxième ligne, qui utilise la syntaxe Perl Compatible Regular Expression (PCRE), extrait une partie de la valeur ( dans ce cas, la chaîne de caractères ( w+
) après le point) dans la route
du groupe de capture nommé et l'affecte à la variable interne portant ce nom. La valeur est également attribuée à la variable $route_from_cookie
sur la première ligne, ce qui la rend disponible pour être transmise à la directive sticky
route
.
À titre d’exemple, le premier bloc de carte
extrait la valeur « a » de ce cookie et l’attribue à $route_from_cookie
:
ROUTE=iDmDe26BdBDS28FuVJlWc1FH4b13x4fn .a
Dans le deuxième bloc de mappage
, la variable $arg_route
représente un argument nommé route dans l'URI de la requête. Comme pour le cookie, l'expression régulière de la deuxième ligne extrait une partie de l'URI. Dans ce cas, il s'agit de la chaîne de caractères ( w+
) après le point dans l'argument route . La valeur est lue dans le groupe de capture nommé, affectée à une variable interne et également affectée à la variable $route_from_uri
.
À titre d’exemple, le deuxième bloc de carte
extrait la valeur b de cet URI et l’affecte à $route_from_uri
:
www.exemple.com/shopping/mon-panier?route=iLbLr35AeAET39GvWK2Xd2GI5c24y5go. b
Voici l’exemple de configuration complet.
http { # ...
map $cookie_route $route_from_cookie {
~.(?P<route>w+)$ $route;
}
map $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}
backend en amont {
zone backend 64k;
serveur 10.0.0.200:8098 route=a;
serveur 10.0.0.201:8099 route=b;
route collante $route_from_cookie $route_from_uri;
}
serveur {
écoute 80;
emplacement / {
# ...
proxy_pass http://backend;
}
}
}
de la route
collante
Quant à la méthode split_clients
, nous avons créé une configuration de test, à laquelle vous pouvez accéder ici . Nous avons utilisé curl
soit pour envoyer un cookie nommé ROUTE , soit pour inclure un argument de route dans l'URI. La valeur du cookie ou de l'argument est une chaîne aléatoire générée en exécutant la commande cat
sur le fichier urandom
, avec .a ou .b ajouté.
Tout d'abord, nous testons avec un cookie qui se termine par .a :
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a" 127.0.0.1 Valeur du cookie : R0TdyJOJvxBkLC3f75Coa29I1pPySOeQ.a URI de la requête : / Résultats : Site A - Exécuté sur le port 8089
Ensuite, nous testons avec un cookie qui se termine par .b .
# curl --cookie "ROUTE=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).b" 127.0.0.1 Valeur du cookie : JhdJZrScTnPBLhqmzK3podNRcJAIc8ST.b URI de la requête : / Résultats : Site B - Exécuté sur le port 8099
Enfin, nous testons sans cookie et à la place avec un argument de route dans l'URI de requête qui se termine par .a . La sortie confirme que lorsqu'il n'y a pas de cookie (le champ Valeur
du cookie
est vide), NGINX Plus utilise la valeur de route dérivée de l'URI.
# curl 127.0.0.1?route=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1).a Valeur du cookie :
URI de la requête : /?route=yNp8pHskvukXK6XqbWefhVUcOBjbJv4v.a Résultats : Site A - Exécuté sur le port 8089
Le type de test que nous décrivons ici est suffisant pour vérifier qu'une configuration distribue les requêtes comme prévu, mais l'interprétation des résultats des tests A/B réels nécessite une journalisation et une analyse beaucoup plus détaillées de la manière dont les requêtes sont traitées. La manière appropriée d’effectuer la journalisation et l’analyse dépend de nombreux facteurs et dépasse le cadre de cet article, mais NGINX et NGINX Plus fournissent une journalisation et une surveillance sophistiquées et intégrées du traitement des demandes.
Vous pouvez utiliser la directive log_format
pour définir un format de journal personnalisé qui inclut n'importe quelle variable NGINX. Les valeurs des variables enregistrées dans les journaux NGINX sont ensuite disponibles pour une analyse ultérieure. Pour plus de détails sur la journalisation personnalisée et la surveillance de l'exécution, consultez le Guide d'administration NGINX Plus .
Lors de la conception d'un plan d'expérience ou de test A/B, assurez-vous que la manière dont vous répartissez les demandes entre les versions de votre application ne prédétermine pas les résultats. Si vous souhaitez une expérience complètement aléatoire, l'utilisation de la méthode split_clients
et le hachage d'une combinaison de plusieurs variables fournissent les meilleurs résultats. Par exemple, la génération d'un jeton expérimental unique basé sur la combinaison du cookie et de l'ID utilisateur à partir d'une demande fournit un modèle de test plus aléatoire que le hachage uniquement du type et de la version du navigateur du client, car il y a de fortes chances que de nombreux utilisateurs aient le même type de navigateur et la même version et soient donc tous dirigés vers la même version de l'application.
Il faut également prendre en compte que de nombreux utilisateurs appartiennent à ce qu’on appelle le groupe mixte . Ils accèdent aux applications Web à partir de plusieurs appareils, peut-être à la fois leur ordinateur professionnel et personnel, mais également un appareil mobile tel qu'une tablette ou un smartphone. Ces utilisateurs disposent de plusieurs adresses IP client. Par conséquent, si vous utilisez l'adresse IP client comme base pour choisir la version de l'application, ils risquent de voir les deux versions de votre application, corrompant ainsi vos résultats expérimentaux.
La solution la plus simple est probablement d’obliger les utilisateurs à se connecter afin que vous puissiez suivre leur cookie de session, comme dans notre exemple de la méthode de l’itinéraire
collant
. De cette façon, vous pouvez les suivre et toujours les envoyer vers la même version qu’ils ont vue la première fois qu’ils ont accédé au test. Si vous ne pouvez pas le faire, il est parfois judicieux de placer les utilisateurs dans des groupes qui ne sont pas susceptibles de changer au cours des tests, par exemple en utilisant la géolocalisation pour afficher une version aux utilisateurs de Los Angeles et une autre aux utilisateurs de San Francisco.
Les tests A/B sont un moyen efficace d'analyser et de suivre les modifications apportées à votre application et de surveiller les performances de l'application en répartissant différentes quantités de trafic entre des serveurs alternatifs. NGINX et NGINX Plus fournissent tous deux des directives, des paramètres et des variables qui peuvent être utilisés pour créer un cadre solide pour les tests A/B. Ils vous permettent également d'enregistrer des détails précieux sur chaque demande. Bon test !
Essayez NGINX Plus et la méthode de l'itinéraire
collant
par vous-même - démarrez votre essai gratuit de 30 jours dès aujourd'hui ou contactez-nous pour discuter de vos cas d'utilisation .
« Cet article de blog peut faire référence à des produits qui ne sont plus disponibles et/ou qui ne sont plus pris en charge. Pour obtenir les informations les plus récentes sur les produits et solutions F5 NGINX disponibles, explorez notre famille de produits NGINX . NGINX fait désormais partie de F5. Tous les liens NGINX.com précédents redirigeront vers un contenu NGINX similaire sur F5.com."