DataDome

Amélioration des performances d’Elasticsearch avec le rollover et l’ILM

Table des matières

Introduction

DataDome est une société mondiale de cybersécurité qui propose une solution SaaS conçue pour protéger les sites Web de nos clients contre les menaces automatisées OWASP : credential stuffing, attaques DDoS de la couche 7, injection SQL et scraping intensif. La solution protège tous les points de vulnérabilité de nos clients (Web, applications mobiles et API) grâce à une technologie d’intelligence artificielle de pointe, capable de détecter des bots en temps réel et de prendre des décisions de blocage automatiques.

La solution DataDome utilise Apache Flink pour l’analyse en temps réel des événements afin de détecter de nouvelles menaces, et Elasticsearch pour stocker les requêtes de tous les visiteurs de nos clients. Nous recevons ces requêtes des serveurs Web de nos clients et les utilisons pour fournir des statistiques sur le trafic des bots, des boucles de rétroaction, de l’intelligence économique et des détails sur les attaques de bots dans le tableau de bord des utilisateurs.

Quelques chiffres : notre cluster stocke plus de 150 To de données et 60 milliards de documents, répartis en 3 000 index et 15 000 shards sur 80 nœuds.

Chaque jour, en période de pointe, notre cluster Elasticsearch écrit plus de 200 000 requêtes par seconde et a un taux de recherche de plus de 20 000 requêtes par seconde.

Nos index sont quotidiens et nous avons un index par client afin d’assurer une séparation logique des données.

Notre défi en matière de politique sur le sharding

Il y a de cela un an, notre cluster était composé de 30 serveurs dédiés répartis dans une architecture « hot-warm » (à accès rapide-moyen). Notre couche hot était composée de 15 serveurs avec des CPU avec 20 threads et 5 disques SSD en RAID0, alors que notre couche warm était composée de 15 serveurs avec des CPU inférieurs afin de réduire les coûts, car les données à l’intérieur de la couche warm sont moins sollicitées.

Nous fournissons à nos clients jusqu’à 30 jours de conservation de leurs données. Les sept premiers jours de données étaient stockés dans la couche hot, et le reste dans la couche warm.

Une bonne pratique courante consiste à conserver une taille de shard d’environ 50 Go. Comme décrit ci-dessus, nous avons des index dédiés pour chaque client, mais tous nos clients n’ont pas la même charge de travail. Nos plus gros clients écrivent des dizaines de milliers de documents par seconde, tandis que nos plus petits en écrivent quelques centaines. De plus, nous devons constamment adapter le nombre de shards à l’évolution du trafic de nos clients.

Afin de résoudre ce problème, nous avons mis en place une routine qui s’exécute chaque jour afin de mettre à jour le modèle de mapping et de créer l’index pour le jour suivant, avec le bon nombre de shards en fonction du nombre de requêtes reçus par notre client le jour précédent. Comme nous savons qu’un document indexé pèse environ 1 000 octets, nous pouvons prédire le nombre de shards dont nous avons besoin pour chaque index afin de respecter le best practice de 50 Go par shard.

Si nous écrivons un document dans un index non existant, le document déclenchera la création de l’index. Cependant, en raison de la conception de notre index quotidien, cela peut causer des problèmes à notre cluster (statut jaune, shards non assignées…), car notre débit d’écritures est élevé et les nœuds primaires doivent allouer les shards aux nœuds au même moment (vers minuit).

Grâce à notre routine, nous pouvons cependant créer l’index à l’avance (un jour avant) et éviter ces problèmes.

Dans l’exemple ci-dessous, le modèle de notre index est « index_clientID_date »

sharding policy challenge

Grâce à cette politique de sharding, nous avons eu près de 72 shards pour notre plus gros client et seulement 1 shard pour nos clients plus petits.

Avec le flux constant de nouveaux clients utilisant notre solution, notre cluster a été overshardé et nous avons connu une baisse de performance, tant en lecture qu’en écriture. Nous avons également constaté une augmentation significative du load average sur nos nœuds de données.

Nous avons donc fait une étude comparative de certaines requêtes de recherche et d’écriture, et nous avons constaté que plus nos shards grandissaient pendant la journée, plus nos performances de recherche et d’écriture diminuaient. Les soirs, lorsque nous avons un pic de trafic et que les shards sont plus grosses que le matin, nos performances Elasticsearch étaient particulièrement dégradées.

Chaque fois qu’un nœud rencontrait des problèmes et tombait en panne, notre cluster en souffrait, car relocaliser un gros index (72 shards de 50 Go) coûte très cher en threads d’écriture, en entrées/sorties des disques, en CPU et en bande passante, surtout en écriture.

Notre étude comparative a montré que la taille parfaite de shards pour nous est d’environ 20 Go, compte tenu de notre cas d’utilisation, de la taille de nos documents, de notre trafic, de notre mapping d’index et de notre type de nœud. Au-delà de cette taille, les performances (tant en lecture et qu’en écriture) commencent à diminuer.

Alors comment pouvons-nous garder nos shards autour de 20 Go ?

  • En ajoutant plus de shards à nos index et en rendant notre cluster encore plus shardé? Certainement pas !
  • En utilisant le rollover ? Oui !

Comment nous avons résolu notre défi en matière de politique sur le sharding

Grâce au rollover, nous avons réduit notre nombre de shards par trois, mais aussi la charge et la consommation des CPU sur nos nœuds. Le rollover nous permet d’utiliser moins de shards simultanément pendant les écritures (c’est-à-dire de réduire le load average et l’utilisation de nos CPU). L’index le plus grand a maintenant un shard maximum sur chaque nœud hot (donc 15 au total), et les index petits et moyens peuvent avoir de 1 à 6 shards en fonction de leur charge de travail.

Le rollover nous a également permis d’optimiser nos performances de lecture en utilisant le cache plus efficacement, car chaque écriture sur un index invalide l’ensemble du cache.

De plus, nous sommes plus à l’aise lorsqu’un nœud tombe en panne et qu’un grand nombre de shards sont déplacées, car des shards plus petits signifient moins de temps pour la récupération des données, moins de bande passante et moins de ressources consommées.

Vous pouvez trouver la documentation officielle du rollover ici : https://www.elastic.co/guide/index.html

En quelques mots, le rollover est composée d’un alias qui reçoit les requêtes de lecture et d’écriture. Derrière l’alias, nous avons un ou plusieurs index. Les requêtes de lecture sont transmises à tous les index, tandis que les requêtes d’écriture ne sont transmises qu’à l’index avec l’indicateur « is_write_index » défini sur true.

Enfin, nous avons appliqué un type de politique « max_size » : à chaque fois qu’un index atteint 400 Go, un rollover se produit et un nouvel index est créé.

rollover sharding policy

Comme vous pouvez le voir, une écriture sur « index_10_2019-01-01-000002 » n’invalide pas le cache de « index_10_2019-01-01-000001 ». Par ailleurs, nos index sont plus petits que nos shards.

En conséquence, nos shards ne dépassent pas 20 Go, et nous avons réduit la charge moyenne et optimisé les performances d’écriture et de lecture.

 

Le défi des hotspots

Avec cela, avons-nous réussi à concevoir le cluster parfait ? Malheureusement non.

Après quelques semaines pendant lesquelles notre cluster s’est très bien comporté, il est devenu instable juste après qu’un nœud hot se soit effondré ; nous l’avons rétabli et l’avons remis dans le cluster.

Que s’est-il passé ?

Quelques heures après que nous ayons rétabli le nœud  dans notre cluster, beaucoup d’index ont déclenché un rollover et toutes les nouveaux shards sont allées vers ce nœud. Nous nous sommes retrouvés avec un cluster déséquilibré où un seul nœud recevait presque tout le trafic en écriture.

Pourquoi ?

Car par défaut, Elasticsearch prend soin d’équilibrer le nombre de shards pour chaque nœud dans la même couche (hot ou warm). En conséquence, presque tous les nouveaux shards ont rollover, même les 14 shards du grand index.

Voyons un exemple qui montre comment notre cluster pourrait devenir déséquilibré. Nous supposons que nous sommes le 3 janvier 2019. Nous avons réglé « replica 0 » dans nos paramètres d’index (souvenez-vous, nous avons un index par jour et par client [10, 20 et 30 sont les ID de nos clients]) et dans ce cas l’équilibrage est parfait, car les écritures sont réparties de manière uniforme sur nos nœuds. Nous avons un shard en écriture par nœud.

cluster could - hotspot challenge

Supposons maintenant que le nœud 3 tombe :

cluster could unbalanced - node down

Comme prévu, tous les shards du nœud 3 sont déplacées vers les nœuds 1 et 2. Et maintenant, que se passera-t-il si le nœud 3 fonctionne à nouveau et qu’un rollover se produit quelques secondes plus tard ?

cluster could rollover

En raison de l’heuristique par défaut de placement des shards d’Elasticsearch, nous avons maintenant tous les shards en écriture sur le nœud 3 !

Comment pourrait-on résoudre ce problème ? Peut-on modifier l’algorithme d’heuristique https://www.elastic.co/guide/en/elasticsearch/reference/current/shards-allocation.html ?

Malheureusement, cela ne nous aurait pas aidés. Comme je l’ai dit, par défaut, Elasticsearch essaie d’équilibrer le nombre de shards par nœud. La modification de ce paramètre pourrait nous aider à équilibrer le nombre de shards par index et par nœud au lieu du nombre de shards par nœud, mais cela n’aurait été utile que pour les grands index qui ont un shard par nœud. Pour le reste des index, qui ont moins de shards (disons 10 shards) que les nœuds hot, ce paramètre n’empêche pas Elasticsearch de mettre tous les shards sur les dix premiers nœuds seulement.

cluster could heuristic algorithm

Dans cet exemple, nous pouvons voir 6 index :

  • index_10_2019-01-03-000001 qui est en écriture
  • index_20_2019-01-03-000001 qui est en écriture
  • index_30_2019-01-03-00001 qui est en écriture
  • index_10_2019-01-02-000001 qui n’est pas en écriture
  • index_20_2019-01-02-000001 qui n’est pas en écriture
  • index_30_2019-01-02-00001 qui n’est pas en écriture

Comme vous pouvez le voir, même si nous essayons de répartir les shards par index et par nœud au lieu de les répartir uniquement par nœud, nous pouvons trouver un cas où notre cluster sera déséquilibré.

Ici, une solution pourrait être de définir un nombre de shards égal au nombre de nœuds, mais comme nous l’avons vu plus haut, un shard a un coût.

Comment nous avons résolu notre problème lié aux hotspots

En avril 2019, Elasticsearch a publié la version 7.0 qui introduit une nouvelle fonctionnalité : la gestion du cycle de vie des index (ILM).

Grâce à cette nouvelle fonctionnalité, nous pouvons maintenant diviser nos nœuds de données en trois couches : hot, warm et cold.

Le principal avantage de la fonction de gestion du cycle de vie des index est qu’elle nous permet de déplacer un shard de hot à warm immédiatement après le rollover de l’index. Nous pouvons garder une couche hot avec seulement des index en écriture, une couche warm avec des shards pour les 7 derniers jours de données en lecture seule, et la couche cold avec des shards pour les 7 à 30 derniers jours (ou plus) de données.

Comme les écritures représentent quatre-vingts pour cent de notre activité, nous voulons avoir une couche hot avec seulement des shards en écriture. Cela nous permettra de garder un cluster équilibré, et nous n’avons pas besoin de nous inquiéter de la question des hotspots.

Comment configurer l’ILM ?

Tout d’abord, créez une politique : https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-index-lifecycle-management.html#ilm-gs-create-policy

Ensuite, liez cette politique à votre modèle : https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-index-lifecycle-management.html#ilm-gs-apply-policy

Et enfin, créez votre index en mode de rollover avec le suffixe « -000001 »

Prenons le processus pour un index du nœud hot au nœud cold en passant par le nœud warm :

index lifecycle management

Conclusion

Grâce aux fonctionnalités de rollover et de gestion du cycle de vie des index, nous avons résolu tous les principaux problèmes de performance et de stabilité de notre cluster Elasticsearch.

L’amélioration de notre suivi nous a permis de mieux comprendre ce qui se passe à l’intérieur de notre cluster.

Pour chaque index, peu importe sa taille, nous avons maintenant des shards avec un maximum de 25 Go de données sur chacune d’entre elles. Le fait d’avoir des shards plus petites permet également un meilleur rééquilibrage et une meilleure relocalisation en cas de besoin.

Nous évitons les problèmes de hotspots parce que notre couche hot n’a que des shards en écriture, et l’architecture hot, warm et cold améliore notre utilisation du cache pour les requêtes de lecture.

Cela dit, le cluster n’est toujours pas parfait. Nous avons encore besoin :

  • d’éviter les hotspots à l’intérieur des couches hot et cold (même si notre souci principal est l’évolutivité des opérations d’écriture)
  • de surveiller de près nos routines qui créent les alias de rollover
  • de créer nos index un jour avant de les utiliser.

Alors, que fait-on ensuite ? Nous continuons d’évoluer ! Alors que de plus en plus de professionnels de la sécurité réalisent le besoin de détection comportementale et de protection anti-bot en temps réel, le volume de requêtes que nous traitons augmente de manière exponentielle. Notre prochaine étape est donc de pouvoir traiter 500 000 requêtes d’écriture par seconde. Restez à l’écoute !