Lorsque vous testez des modifications apportées à une application, certains facteurs ne peuvent être mesurés que dans un environnement de production, et non dans un environnement de test de développement. Il peut s’agir, par exemple, de l’effet des modifications de l’interface utilisateur sur le comportement des utilisateurs et de leur incidence sur les performances globales. Une méthode de test courante est le test A/B – également appelé test fractionné – qui consiste à diriger une proportion généralement faible d’utilisateurs vers la nouvelle version d’une application, tandis que la plupart continuent d’utiliser la version actuelle.
Dans cet article de blog, nous verrons 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 de l’application présentée aux utilisateurs. Des exemples de configuration montrent comment utiliser les directives, paramètres et variables de NGINX et NGINX Plus afin de réaliser des tests A/B précis et mesurables.
Pourquoi effectuer des tests A/B ?
Comme nous l’avons indiqué, les tests A/B vous permettent de mesurer les différences de performances ou d’efficacité entre deux versions d’une application. Votre équipe de développement souhaite peut-être modifier la disposition visuelle des boutons dans l’interface utilisateur ou remanier entièrement le processus du panier d’achat, tout en comparant le taux de conversion des transactions afin de s’assurer que ce changement produit l’effet commercial escompté. Grâce aux tests A/B, vous pouvez diriger un pourcentage défini du trafic vers la nouvelle version et le reste vers l’ancienne, puis mesurer l’efficacité des deux versions de l’application.
Ou peut-être vous préoccupez-vous moins de l’effet sur le comportement des utilisateurs que de l’impact sur les performances. Imaginons que vous prévoyiez de déployer un vaste ensemble de modifications sur votre application web et que vous estimiez que les tests réalisés dans votre environnement d’assurance qualité ne permettent pas de mesurer fidèlement leur impact potentiel sur les performances en production. Dans ce cas, un déploiement A/B vous permet de proposer la nouvelle version à un faible pourcentage défini de visiteurs afin de mesurer l’impact des modifications sur les performances, puis d’augmenter progressivement ce pourcentage jusqu’à déployer finalement l’application modifiée auprès de tous les utilisateurs.
Utiliser NGINX et NGINX Plus pour les tests A/B
NGINX et NGINX Plus proposent deux méthodes permettant de contrôler la destination du trafic des applications web. La première méthode est disponible dans les deux produits, tandis que la seconde est uniquement disponible 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 reflètent les caractéristiques du client (telles que son adresse IP) ou de l’URI de la requête (tel qu’un argument nommé), mais leurs différences les rendent adaptées à différents cas d’utilisation des tests A/B :
- La méthode
split_clientschoisit la destination d’une requête en fonction d’un hachage des valeurs de variables extraites de la requête. L’ensemble des valeurs de hachage possibles est réparti entre les différentes versions de l’application, et vous pouvez attribuer à chacune une proportion différente de cet ensemble. La destination est ainsi choisie de manière aléatoire. - La méthode
stickyroutevous offre un contrôle bien plus précis sur la destination de chaque requête. Le choix de l’application repose sur les valeurs des variables elles-mêmes (et non sur un hachage), ce qui vous permet de définir explicitement quelle application reçoit les requêtes présentant certaines valeurs de variables. Vous pouvez également utiliser des expressions régulières pour fonder la décision sur certaines parties seulement de la valeur d’une variable, et privilégier une variable plutôt qu’une autre comme critère de décision.
Utilisation de la méthode « split_clients »
Dans cette méthode, le bloc de configuration split_clients définit pour chaque requête une variable qui détermine le groupe en amont auquel la directive proxy_pass envoie la requête. 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 attribuer dynamiquement à la variable le nom de l’un des deux groupes en amont, soit version_1a, soit 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;
}
}
}Le premier paramètre de la directive split_clients est la chaîne ("${arg_token}" dans notre exemple) qui est hachée à l’aide d’une fonction MurmurHash2 lors de chaque requête. Les arguments d’URI sont accessibles à NGINX sous forme de variables appelées $arg_name ; dans notre exemple, la variable $arg_token récupère l’argument d’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), son port ($remote_port) ou une combinaison des deux. Il est préférable d’utiliser une variable générée avant que la requête ne soit traitée par NGINX. Les variables contenant des informations sur la requête initiale du client sont idéales ; il peut s’agir, par exemple, de l’adresse IP ou du port du client, comme indiqué précédemment, de l’URI de la requête, voire des en-têtes de requête HTTP.
Le deuxième paramètre de la directive split_clients ($appversion dans notre exemple) est la variable dont la valeur est définie dynamiquement en fonction du hachage du premier paramètre. Les instructions entre accolades divisent la table de hachage en « compartiments », chacun contenant un pourcentage des hachages possibles. Vous pouvez créer autant de compartiments que vous le souhaitez, et ils ne doivent pas nécessairement être tous de la même taille. Notez que le pourcentage du dernier compartiment est toujours représenté par un astérisque (*) plutôt que par un nombre précis, car le nombre de hachages peut ne pas être divisible de manière égale selon les pourcentages spécifiés.
Dans notre exemple, nous plaçons 95 % des hachages dans un compartiment associé au groupe de serveurs en amont version_1a, et le reste dans un second compartiment associé à version_1b. Les valeurs de hachage possibles sont comprises entre 0 et 4 294 967 295. Le premier compartiment contient donc les valeurs allant de 0 à environ 4 080 218 930 (95 % du total). La variable $appversion est définie sur le groupe de serveurs en amont associé au compartiment contenant le hachage de la variable $arg_token. Prenons un exemple concret : la valeur de hachage 100 000 000 se trouve dans le premier compartiment. Par conséquent, $appversion est définie dynamiquement sur version_1a.
Test de la configuration split_clients Configuration
Pour vérifier que le bloc de configuration split_clients fonctionne comme prévu, nous avons créé une configuration de test qui répartit les requêtes entre deux groupes en amont selon les mêmes proportions que ci-dessus (95 % et le reste). Nous avons configuré les serveurs virtuels des groupes pour qu’ils renvoient une chaîne indiquant quel groupe – version_1a ou version_1b – a traité la requête (vous pouvez consulter la configuration de test ici). Nous avons ensuite utilisé curl pour générer 20 requêtes, en attribuant une valeur aléatoire à l’argument d’URI token à l’aide de la commande cat exécutée sur le fichier urandom. Cette méthode est utilisée uniquement à des fins de démonstration et de génération aléatoire. Comme prévu, 1 requête sur 20 (95 %) a été traitée par version_1b (par souci de concision, nous ne présentons que 10 requêtes).
# 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.Utilisation de la méthode « sticky route »
Dans certains cas, vous pouvez souhaiter définir une route statique en prenant des décisions de routage du client en fonction de tout ou partie de la valeur d’une variable NGINX. Pour ce faire, vous pouvez utiliser la directive sticky route, disponible uniquement dans NGINX Plus. Cette directive accepte 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. Cette fonctionnalité permet d’établir un ordre de priorité entre les variables de la requête qui déterminent le choix de la destination, et ainsi de prendre en charge plusieurs méthodes de répartition du trafic dans une même configuration.
Il existe deux approches différentes pour utiliser cette méthode.
- Avec l’approche côté client, vous pouvez choisir la route en fonction de variables NGINX contenant des valeurs initialement envoyées directement par le client, telles que l’adresse IP du client ou des en-têtes de requête HTTP propres au navigateur, comme le champ «
User-Agent». - Avec l’approche côté serveur ou côté application, votre application détermine à quel groupe de test un nouvel utilisateur est affecté et lui envoie un cookie ou un URI de redirection contenant un indicateur de routage représentant le groupe choisi. Lors de la requête suivante, le client présente le cookie ou utilise l’URI de redirection ; la directive
stickyrouteextrait l’indicateur de routage et transmet la requête au serveur approprié.
Dans notre exemple, nous utilisons l’approche côté application : la directive sticky route du groupe upstream définit en priorité la route sur la valeur indiquée dans le cookie fourni par un serveur (enregistrée dans $route_from_cookie). Si le client ne possède pas de cookie, la route est définie sur la valeur d’un argument de l’URI de la requête ($route_from_uri). La valeur de la route détermine ensuite quel server du groupe upstream reçoit la requête : le premier serveur si la route est a, le second si la route est b (les deux serveurs correspondent aux deux versions de notre application).
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;
}Mais la lettre « a » ou « b » est intégrée dans une chaîne de caractères beaucoup plus longue dans le cookie ou l’URI réels. Pour extraire uniquement la lettre, nous configurons un bloc de configuration map pour le cookie et un autre pour l’URI :
map $cookie_route $route_from_cookie { ~.(?P<route>w+)$ $route;
}
map $arg_route $route_from_uri {
~.(?P<route>w+)$ $route;
}Dans le premier bloc map, 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 des expressions régulières compatibles avec Perl (PCRE), extrait une partie de la valeur – dans ce cas, la chaîne de caractères (w+) située après le point – dans le groupe de capture nommé route et l’affecte à la variable interne du même nom. La valeur est également affectée à la variable $route_from_cookie sur la première ligne, ce qui permet de la transmettre à la directive sticky route.
Par exemple, le premier bloc « map » extrait la valeur « a » de ce cookie et l’affecte à $route_from_cookie :
ROUTE=iDmDe26BdBDS28FuVJlWc1FH4b13x4fn.aDans le deuxième bloc map, 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+) située après le point dans l’argument route. La valeur est lue dans le groupe de capture nommé, affectée à une variable interne, puis également affectée à la variable $route_from_uri.
Par exemple, le deuxième bloc « map » extrait la valeur b de cet URI et l’affecte à $route_from_uri :
www.example.com/shopping/my-cart?route=iLbLr35AeAET39GvWK2Xd2GI5c24y5go.bVoici l’exemple complet de configuration.
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;
}
}
}Test de la configuration sticky route Configuration
Pour la méthode « split_clients », nous avons créé une configuration de test, accessible ici. Nous avons utilisé curl soit pour envoyer un cookie nommé ROUTE, soit pour inclure un argument 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, à laquelle est ajouté le suffixe .a ou .b.
Tout d’abord, nous effectuons un test avec un cookie se terminant par « .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 8089Ensuite, nous effectuons un test 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.1Cookie Value: JhdJZrScTnPBLhqmzK3podNRcJAIc8ST.b
Request URI: /
Results: Site B - Running on port 8099Enfin, nous effectuons un test sans cookie, en utilisant à la place un argument de route dans l’URI de la requête qui se termine par .a. Le résultat confirme qu’en l’absence de cookie (le champ Cookie Value 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).aCookie Value:
Request URI: /?route=yNp8pHskvukXK6XqbWefhVUcOBjbJv4v.a
Results: Site A - Running on port 8089Journalisation et analyse de vos résultats
Le type de test que nous décrivons ici suffit à vérifier qu’une configuration répartit les requêtes comme prévu. Toutefois, l’interprétation des résultats de véritables tests A/B nécessite une journalisation et une analyse beaucoup plus détaillées du traitement des requêtes. La méthode appropriée pour journaliser et analyser les données dépend de nombreux facteurs et dépasse le cadre de cet article, mais NGINX et NGINX Plus intègrent des fonctionnalités avancées de journalisation et de surveillance du traitement des requêtes.
Vous pouvez utiliser la directive « log_format » pour définir un format de journal personnalisé incluant n’importe quelle variable NGINX. Les valeurs des variables consignées dans les journaux NGINX peuvent ensuite être analysées ultérieurement. Pour en savoir plus sur la journalisation personnalisée et la surveillance en temps réel, consultez le Guide d’administration de NGINX Plus.
Quelques derniers éléments à prendre en compte
Lorsque vous concevez une expérience ou un plan de test A/B, veillez à ce que la manière dont vous répartissez les requêtes entre les différentes versions de votre application ne prédétermine pas les résultats. Si vous souhaitez mener une expérience entièrement aléatoire, l’utilisation de la méthode split_clients et le hachage d’une combinaison de plusieurs variables offrent les meilleurs résultats. Par exemple, la génération d’un jeton expérimental unique à partir de la combinaison du cookie et de l’identifiant utilisateur d’une requête produit un schéma de test plus aléatoire que le simple hachage du type et de la version du navigateur du client. En effet, de nombreux utilisateurs sont susceptibles d’avoir le même type et la même version de navigateur et seront donc tous dirigés vers la même version de l’application.
Vous devez également tenir compte du fait que de nombreux utilisateurs appartiennent à ce que l’on appelle le groupe mixte. Ils accèdent aux applications web depuis plusieurs appareils : par exemple, leur ordinateur professionnel, leur ordinateur personnel, ainsi qu’un appareil mobile tel qu’une tablette ou un smartphone. Ces utilisateurs disposent de plusieurs adresses IP clientes. Par conséquent, si vous vous basez sur l’adresse IP cliente pour choisir la version de l’application, ils risquent de voir les deux versions de votre application, ce qui fausserait les résultats de votre expérience.
La solution la plus simple consiste probablement à demander aux utilisateurs de se connecter afin de pouvoir suivre leur cookie de session, comme dans notre exemple de la méthode sticky route. Vous pouvez ainsi les suivre et toujours les rediriger vers la version qu’ils ont vue lors de leur premier accès au test. Si cela n’est pas possible, il peut parfois être judicieux de répartir les utilisateurs dans des groupes qui ne risquent pas de changer au cours du test, par exemple en utilisant la géolocalisation pour afficher une version aux utilisateurs de Los Angeles et une autre à ceux de San Francisco.
Conclusion
Les tests A/B constituent un moyen efficace d’analyser et de suivre les modifications apportées à votre application, ainsi que de surveiller ses performances en répartissant le trafic dans des proportions variables entre différents serveurs. NGINX et NGINX Plus proposent tous deux des directives, des paramètres et des variables permettant de créer une infrastructure robuste pour les tests A/B. Ils vous permettent également de consigner des informations précieuses sur chaque requête. Bons tests !
Essayez NGINX Plus et la méthode sticky route par vous-même : commencez dès aujourd’hui votre essai gratuit de 30 jours ou contactez-nous pour discuter de vos cas d’utilisation.
À propos de l’auteur

Articles de blog associés

Tendances actuelles des technologies cloud natives, de l’ingénierie de plateforme et de l’IA
Découvrez les principaux enseignements tirés des réponses à l’enquête annuelle F5 NGINX sur les dernières tendances en matière de technologies cloud natives, de sécurité, d’ingénierie de plateforme et d’IA.
Quels contrôleurs Ingress NGINX sont affectés par les vulnérabilités CVE-2022-4886, CVE-2023-5043 et CVE-2023-5044 ?
Lorsque de nouvelles CVE sont signalées, il est facile de ne plus savoir si elles concernent un outil de contrôleur Ingress NGINX particulier, car il existe plusieurs projets. Cet article présente les CVE et explique comment distinguer les différents projets de contrôleurs Ingress afin de dissiper toute confusion.
Votre API est-elle en temps réel ? Testez sa latence avec l’outil rtapi de NGINX
Votre API répond-elle aux requêtes en moins de 30 ms ? Si ce n’est pas le cas, il ne s’agit pas d’une API en temps réel offrant la faible latence exigée par la grande majorité des organisations. Mesurez la latence de votre API avec l’outil rtapi de NGINX et découvrez comment NGINX peut l’aider à offrir une réactivité en temps réel.

Un testeur d’expressions régulières pour NGINX et NGINX Plus
Le testeur d’expressions régulières (regex) pour NGINX et NGINX Plus vous évite de tâtonner en vous indiquant si une expression régulière utilisée dans un bloc location ou map correspond aux valeurs comme prévu. Il génère la configuration appropriée et affiche les valeurs des groupes de capture.
Caches partagés avec les clusters de cache NGINX Plus, partie 1
Optimisez la capacité de votre cache web en le répartissant entre plusieurs serveurs de cache web NGINX ou NGINX Plus. Découvrez comment procéder dans cet article.
Introduction aux microservices
Les microservices suscitent actuellement beaucoup d’intérêt. Cet article de blog est le premier d’une série de sept consacrée à la conception, au développement et au déploiement de microservices.