Elasticsearch : comment Zalando a géré la montée de version majeure

Zalando Elasticsearch

Zalando a récemment basculé sur la dernière version majeure (8.x) d’Elasticsearch. Quels furent les tenants et les aboutissants de la démarche ?

La recherche approximative des plus proches voisins, raison de basculer vers Elasticsearch 8.x ? Cette fonctionnalité a en tout cas motivé la démarche au sein du département Search & Browse de Zalando.

Ce dernier pouvait déjà, avec Elasticsearch 7.17, exploiter la recherche exacte des plus proches voisins. Une méthode plus précise, donc, mais aussi plus gourmande en ressources… et dont la mise à l’échelle peut ainsi se révéler problématique.

Chez Zalando, le déploiement Elasticsearch a effectivement atteint une certaine échelle : de multiples clusters de plusieurs To chacun. Il fait office de datastore pour l’essentiel des applications de recherche. La principale s’appelle Origami. Écrite en Scala, elle s’intègre à divers microservices internes entre lesquels elle fait le liant.

Dans le cadre du déploiement Elasticsearch 7.17, Origami utilisait le HLRC (client REST de haut niveau). Celui-ci ayant été abandonné avec Elasticsearch au profit d’une bibliothèque décorrélée du code serveur, Zalando a dû modifier l’application en conséquence.

Il a également fallu choisir entre ce nouveau client et le client Scala non officiel. Ce dernier a ses avantages : open source (Apache 2.0), sécurité vide, sûreté du typage, implémentation de Relative Streams, support des classes de types pour l’indexation, etc. Le client Java l’a toutefois emporté, en premier lieu pour sa prise en charge officielle et sa maintenance synchronisée avec celle d’Elasticsearch.

Mode compatibilité activé sur le client API

Vu le périmètre de son déploiement, Zalando allait devoir, pendant un temps, exploiter en parallèle Elasticsearch 7.17 et 8.x. Aussi a-t-il choisi d’utiliser initialement le HLRC en mode compatibilité. Avec cette option activée, il envoie des en-têtes supplémentaires signalant à Elasticsearch 8.x qu’il doit se comporter comme un serveur Elasticsearch 7.x. Cela permettrait d’upgrader d’abord le cluster, puis, progressivement, le client. Et ainsi, d’éviter d’écrire trop de code à la fois.

Testcontainers a servi à écrire les tests. Il en a résulté trois classes :

– NewClientWithOldElasticTest (serveur 7.x avec API 8.x)
– NewClientWithNewElasticTest (serveur 8.x avec API 8.x)
– OldClientWithNewElasticTest (serveur 8.x avec API 7.x)

Chacun vérifiait notamment que l’application pouvait créer des index, des documents et des mappings vectoriels, indexer des données, rechercher un document, supprimer un index et fermer le client.

Les tests ont permis d’appliquer des correctifs, liés entre autres au fait qu’avec Elasticsearch 8.x :

– Le paramètre de cluster action.destructive_requires_name est réglé par défaut sur true
– Le paramètre is_write ne peut pas prendre de valeur null lorsqu’on crée un alias pour l’index
– Le champ _type n’existe plus dans les réponses

Préparer la migration a impliqué d’oublier d’autres éléments devenus obsolètes avec Elasticsearch 8.x. Par exemple, fixed_auto_queue_size, qui utilise la loi de Little pour gérer la taille des threads dans les files d’attente. Ou la définition du rôle des nœuds par l’intermédiaire de plusieurs paramètres (node.data, node.ingest, node.master…). Zalando a aussi ajouté un processus d’approbation manuelle pour empêcher l’upgrade de nœuds maîtres avant les nœuds de données.

La réindexation préférée au déploiement continu

L’application semblant prête, Zalando est passé au cluster lui-même. L’entreprise n’a pas suivi la méthode principale que recommande l’éditeur d’Elasticsearch, à savoir le déploiement continu. Elle y a préféré la méthode alternative fondée sur la réindexation.

Le déploiement continu se fait à chaud : on upgrade nœud par nœud – en commençant par les nœuds de données – et le cluster reste disponible. Un processus non seulement chronophage sur le périmètre envisagé, mais aussi risqué, a conclu Zalando. Une perte de données supposerait un long processus de restauration depuis les snapshots, doublé d’une réinitialisation des flux d’entrée. Pendant ce temps, le catalogue de produits serait soit indisponible, soit incomplet.

La deuxième option induit la création de nouveaux clusters sur lesquels on réindexe les données des anciens, avant de basculer le trafic. Moins risquée à cette échelle, elle est aussi moins disruptive. Et en cas de problème, le rollback est quasi instantané (on rebascule le trafic). Par ailleurs, avoir les deux clusters en fonctionnement parallèle donne du temps pour tester le nouveau, d’abord en dupliquant le trafic.

Telle quelle, la migration s’apparente à du blue/green… sauf qu’à la fin, on décommissionne les anciennes ressources. Pour combler l’écart entre le snapshot et l’activation du trafic sur les nouveaux clusters, on réinitialise les flux d’entrée à l’instant précédant la réalisation dudit snapshot.

En guise de contrôleur ingress, Zalando utilise Skipper, un reverse proxy maison open source. Et, pour le routage, une ressource Kubernetes personnalisée (RouteGroup). Pour le monitoring, l’équipe Search & Browse s’est appuyée sur le couple Lightstep-Grafana. Sur la procédure de restauration, elle lorgne des workflows Kibana, en remplacement de ses scripts.

Illustration © daily_creativity – Adobe Stock