Article rédigé et publié par Yannick Pereira-Reis expert ElasticSearch, Symfony et AngularJS.

Avant de commencer, décrivons le besoin que nous avons en termes d’indexation et recherche de contenu pour notre exemple, nous détaillerons ensuite comment implémenter la meilleure solution avec ElasticSearch.

Notre application contient de nombreux utilisateurs dont les données (images, vidéos, textes, pdf, …) doivent être indéxées afin d’offrir une recherche multicritères performante sur ces documents. Chaque utilisateur ne pourra rechercher que parmi les documents lui appartenant.

The « Users » data flow

La solution qui vient à l’esprit dans un premier temps consiste en la création d’un index ElasticSearch par utilisateur. Cette méthode à différents avantages :

  • L’indexation et la recherche sont des mécanismes très simples et intuitifs car pour un utilisateur donné tous les documents sont stockés dans le même index.
  • ElasticSearch permet de contrôler le nombre de « shards » et de « replicas » par index, ce qui permet de définir des configurations différentes en fonctions du volume de données indexées pour chaque utilisateur.

Avec ce « pattern » les recherches sont très simples :

curl -XPUT localhost:9200/user_1/documents/_search -d '{...}'
curl -XPOST localhost:9200/user_1/documents -d '{...}'

Malheureusement cette solution a un inconvénient majeur : avec le succès de l’application et l’augmentation du nombre d’utilisateurs, il y a une augmentation du nombre d’index, du nombre de « shards » et de « replicas » qui ont besoin de beaucoup de ressources système, un shard étant une instance Lucene. Avec quelques milliers d’utilisateurs, on peut faire tomber un petit cluster ElasticSearch très facilement.

Il faut également noter que le nombre de documents indexés peut grandement varier d’un utilisateur à l’autre, ainsi certains « shards » n’indexent que quelques documents alors qu’ils pourraient en indexer des millions. Donc un gaspillage important de ressources système.

 L’utilisation des alias et des filtres

Puisqu’on ne souhaite pas que le nombre de « shards » explose, il nous faut diminuer le nombre d’index. On pourrait par exemple indexer tous les documents de tous les utilisateurs dans un seul index dédié aux utilisateurs, en stockant un identifiant dans le document pour être capable de filtrer par utilisateur.

Cette approche a elle aussi un inconvénient majeur : une recherche de documents filtrée sur un utilisateur conduirait à rechercher dans tous les shards de l’index, potentiellement répartis dans un grand nombre de nœuds du cluster. En effet, par défaut ElasticSearch se base sur l’id du document pour déterminer le routing des documents vers les « shards » de l’index.

Heureusement, Elastic Search permet de définir une clé différente pour le routing des documents vers les « shards ». Il faudrait que l’on puisse router tous les documents d’un utilisateur avec la même clé, pour que la recherche ne porte ensuite que sur les « shards » pertinents. On pourrait ainsi router tous les documents d’un utilisateur vers le même « shard » et fortement améliorer les performances des requêtes lors d’une recherche.

Ceci peut être fait en utilisant les alias et les filtres…

Définissons un index global pour les utilisateurs :

curl -XPUT localhost:9200/users -d '{
  "settings": {
    "index": {
      "number_of_shards": 500,
      "number_of_replicas": 1
    }
  }
}'

Puis définissions un alias pour chaque utilisateur avec une clé de routing et un filtre :

curl -XPOST localhost:9200/_aliases -d '{
  "actions": [{
    "add": {
      "index": "users",
      "alias": user_1,
      "filter": {"term": {"user_id": 1}},
      "routing": 1
    }
  }]
}'

Maintenant on agit sur les données de la même façon qu’avant mais en utilisant les alias.

Lorsque l’on indexe des documents en utilisant l’alias, la clé de routing est automatiquement ajoutée au document. De la même manière, pas besoin d’ajouter cette clé lors des recherches car elle est automatiquement ajoutée par l’alias. Désormais notre recherche ne ciblera que les « shards » pertinents et la recherche sera extrêmement performante.

Publié par Yannick Pereira-Reis

Yannick (@yannickpr69) est Ingénieur études et développement, établit dans la région Lyonnaise. Il publie régulièrement des entrées sur ce blog pour partager ses connaissances en tant qu'expert ElasticSearch, Symfony et AngularJS.

Rejoindre la conversation

2 commentaires

  1. Salut,

    Article très intéressant et très pertinent car la problématique d’indexation de contenu par utilisateur ou segmentée différemment est récurrente.

    Merci pour cette explication !

  2. Bonjour,

    Génial l’article ! Exactement le genre de problème sur lesquels je travaille actuellement pour un site de vente en ligne. J’ai une problématique supplémentaire concernant la percolation, et l’enregistrement de requête/alerte pour chaque utilisateur. Comment traiteriez-vous le problème ?

    Merci !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *