Server-side testing : quelle est l'architecture de Kameleoon ?
Cet article a pour objectif de présenter les considérations et conclusions qui nous ont amenés à définir l’architecture de nos SDKs serveur fin 2017. Le sujet de la performance en particulier a fait l’objet d’une réflexion approfondie, comme il l’avait été pour notre solution de testing et personnalisation côté client il y a 6 ans. A l’époque nous avions dès le départ posé des fondamentaux solides (comme par exemple un unique call de téléchargement depuis le navigateur, regroupant l’ensemble du code nécessaire au fonctionnement de la plateforme, en sur-optimisant la taille du script) qui ont été ensuite consolidés par des innovations régulières (comme par exemple l’éradication de l’effet flicker ou, dernier exemple en date, l’utilisation de la compression Brotli plutôt que le traditionnel GZIP dans le transfert de nos scripts, réduisant le poids de celui-ci d’environ 30 %). Les explications ci-dessous pourront être utiles aux équipes techniques intéressées par l’implémentation de tests A/B côté back-end pour comprendre rapidement tous les enjeux de ce sujet.
Le « bucketing » des visiteurs : Comment l’implémenter, ou pourquoi nous retrouvons la problématique Synchrone vs Asynchrone
Les opérations techniques qui sont nécessaires à l’implémentation d’un test A/B peuvent être regroupées en quatre grandes étapes :- le ciblage et le « triggering » du test : il s’agit ici de déterminer quels visiteurs vont « rentrer » dans un test A/B, et à quel moment de leur visite.
- l’assignation d’une variante à un visiteur (ou « bucketing » en anglais) : à quelle variante ce visiteur va t-il être soumis, et – point très important – comment s’assurer que ce visiteur sera soumis à la même variante à l’avenir.
- la visualisation de la variante, avec son implémentation associée : une fois la variante connue pour un visiteur donné, il faut l’afficher et donc créer son HTML (on entend ici « visualisation » au sens général, car il est possible de définir des variantes modifiant la logique business, tels que des options de livraison différentes).
- le tracking du visiteur : pour visualiser les résultats du test, il est nécessaire d’enregistrer à la fois l’association de ce visiteur avec la variante (triplet de données : ID visiteur – ID experiment – ID variante) et les actions poursuivies par le visiteur (notamment son éventuelle conversion ou son éventuel achat au cours de la visite).
Aller encore plus loin et problématiques de caching : Où exécuter le code du bucketing ?
Dans cette partie, nous abordons une problématique courante des sites Web à fort trafic : la plupart (pour ne pas dire la totalité) des sites utilisent une stratégie de mise en cache (dite caching), qui peut avoir lieu à plusieurs niveaux. Comme nous allons le voir, la mise en place d’A/B tests côté serveur peut présenter de sérieux challenges pour les différentes implémentations de cache couramment utilisées. Ici, notre propos n’est pas de parler de comment nous avons traité le sujet dans Kameleoon - car il y autant de situations possibles que de clients et une plateforme n’est pas à même d’apporter une solution unique - mais de donner notre avis et de formuler quelques recommandations d’après les nombreuses expériences que nous avons eues sur le sujet. Imaginons un site e-commerce à fort trafic. Le CMS gérant la partie e-commerce, qu’il soit basé sur Java, PHP ou sur n’importe quel autre stack technologique, doit générer des pages HTML dynamiquement. Par exemple, la homepage du site va contenir les offres promotionnelles du moment et mettre en avant certains produits. Toutes ces informations proviennent des bases de données du back-end, et le code applicatif du CMS est, dans le cas standard, exécuté à chaque requête HTTP vers la homepage. Il rapatrie les informations de la base de données et renvoie la page web au visiteur. Mais évidemment, en réalité, on peut imaginer que cette page web n’est modifiée qu’une fois par jour. Dans ce cas, il est très intéressant, plutôt que d’exécuter du code Java / PHP à chaque requête HTTP, couplé à des requêtes en base (généralement SQL) coûteuses, de sauvegarder la page construite et de la resservir, identique, à chaque visiteur. Il s’agit ici d’une simplification grossière, mais l’idée du caching est bien là : on transforme des réponses dynamiques en réponses statiques (toujours identiques), qui sont beaucoup plus légères à servir. Les implémentations de ces mises en cache sont nombreuses. Mentionnons simplement deux cas assez courants qui illustreront bien nos propos : la mise en cache via CDN (Content Delivery Network), où le navigateur accède à un serveur du réseau du CDN qui aura une copie ; et la mise en cache via serveur cache HTTP (tels que Varnish, Squid) où typiquement un serveur front-end (nginx, lighttpd) sert une page mise en cache dans Varnish, plutôt que de la récupérer d’Apache ou Tomcat. Dès que l’on veut implémenter un test, un problème apparaît : le bucketing, une fois de plus. En effet, pour réaliser le bucketing, il faut exécuter du code, et il n’est plus suffisant de servir simplement la page mise en cache. Il est intéressant de noter que les parties de génération de HTML peuvent être assez facilement réalisées sans perturber le cache. Par exemple, avec Varnish, on peut imaginer mettre en place un header « Vary » au niveau du serveur front nginx ; ce header serait positionné en fonction de la valeur d’un cookie qui représenterait l'ID de la variante associée à un test. Il est donc possible de mettre en cache les différentes versions / variantes d’une expérience A/B. Mais en revanche, le bucketing lui-même reste problématique : pour chaque nouveau visiteur qui va rentrer dans un test, il est bel et bien nécessaire d’exécuter ce code de bucketing, qui ne peut absolument pas être mis en cache. Pour certains sites, cela peut être prohibitif. Plusieurs stratégies sont possibles pour faire face à ce challenge. Un mix de client-side et de server-side est ainsi une option pertinente : on peut imaginer faire tourner le code de bucketing dans un script côté navigateur, et positionner en avance le (ou les) cookie(s) de variantes. Dans ce cas, on peut alors mettre en place derrière, côté back, plusieurs variantes en cache, par exemple via la méthode header « Vary » discutée précédemment. Il faut noter que dans ce cas, le tracking des résultats doit également être réalisé côté client (puisque si ce n’est pas le cas, on retombe sur la problématique de nécessité d’exécution de code, avec l’impossibilité de mise en cache associée). Cependant, cela n’est malheureusement pas toujours possible. Notamment dans le cas d’un test qui devrait avoir lieu sur la première page du parcours d’un visiteur (tel la homepage, ou même une page profonde qui serait visitée directement depuis Google), puisque dans ce cas, nous avons besoin de l’information de variante avant même que la première page (et donc le script de bucketing) ne soit chargée dans le navigateur client ! Pour ces cas difficiles où la performance maximale est recherchée, et où une implémentation server-side serait souhaitée (NB : il faudrait vraiment se poser la question de la légimité de la méthode server-side, car il peut être plus pertinent alors de recourir à une approche full client-side), notre recommandation est de positionner le code de bucketing le plus en amont possible côté serveur. Par exemple, au niveau du front-end nginx, plutôt que via nos SDKs serveur. On pourrait ainsi imaginer l’écriture d’un module nginx réalisant le bucketing et le « branchage » associé, alors que la génération du HTML serait traditionnellement déléguée au back-end Apache / Tomcat et mise en cache. Un module nginx sera nécessairement beaucoup plus performant que l’appel à nos SDKs, quel qu’en soit le langage, et devrait répondre positivement à un besoin de performance extrême. Nous réfléchissons d’ailleurs actuellement chez Kameleoon à l’implémentation officielle d’un module de ce type. Autre option intéressante en guise de conclusion, positionner le bucketing au niveau du CDN (qui est, dans un schéma de flot des requêtes HTTP, en quelque sorte l’équivalent d’un serveur nginx front, mais encore plus en amont). Cloudflare, un CDN très innovant, a ainsi annoncé il y a quelques mois la mise à disposition de workers JavaScript. Ce concept permet d’exécuter du code « on the edge » : comprendre au niveau du CDN, lorsque la requête HTTP du visiteur arrive sur un serveur Cloudflare après l’aiguillage DNS. Ainsi, il est très simple d’implémenter un code de bucketing en NodeJS, qui s’appuie sur la vaste infrastructure IT de Cloudflare (milliers de serveurs) et qui sera en mesure de répondre aux problématiques de scaling et de performance les plus exigeantes.Thèmes traités dans cet article
Full-stack
Ces articles pourraient vous plaire