Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. MongoDB
Code

Recherche en plein texte avec MongoDB

by
Difficulty:IntermediateLength:LongLanguages:

French (Français) translation by Pierre Choffé (you can also view the original English article)

MongoDB est un des leaders des bases de données NoSQL, il est bien connu pour ses performances, la flexibilité de son schéma, sa bonne évolutivité et ses capacités d'indexation. Au coeur de ses excellentes performances se trouvent les index, qui permettent l'exécution efficace de requêtes en évitant les scans de collections entières, limitant par là-même le nombre de documents recherchés par MongoDB.

À partir de la version 2.4, MongoDB a lancé une fonctionnalité expérimentale supportant la recherche en plein texte et les index de texte. Cette fonctionnalité fait maintenant partie intégrante du produit et n'est plus expérimentale. Dans cet article, nous allons explorer les fonctionnalités de recherche en plein texte de MongoDB, en commençant par les bases.

Si vous débutez avec MongoDB, je vous recommande de lire  les articles suivants sur Envato Tuts+ qui vous aideront à comprendre les concepts de base de MongoDB :

Les bases

Avant d'entrer dans les détails, prenons un peu de recul. La recherche en plein texte est une technique consistant à effectuer une recherche sur une base de données en plein texte à partir de critères spécifiés par l'utilisateur. C'est assez similaire à la façon dont nous recherchons un contenu sur Google (ou en fait sur tout autre application) en entrant des chaînes de caractères comportant des mots-clés ou des phrases et dont nous obtenons les résultats triés et hiérarchisés.

Voici quelques scénarios dans lesquels s'appliquerait une recherche en plein texte :

  • Imaginez une recherche sur votre sujet favori sur Wiki. Lorsque vous entrez le texte de votre recherche sur Wiki, le moteur de recherche vous retourne les résultats de tous les articles liés aux mots-clés ou aux phrases que vous avez cherchés (même si ces mots-clés sont utilisés à l'intérieur de l'article). Ces résultats de recherche sont triés par pertinence en fonction de leur score.
  • Autre exemple, imaginez un réseau social dans lequel l'utilisateur peut faire une recherche pour trouver tous les articles contenant le mot chats. Ou pour faire plus compliqué, tous les articles ayant des commentaires contenant le mot chats.

Avant d'aller plus loin, il y a certains termes généraux liés à la recherche en plein texte que vous devez connaître. Ces termes sont applicables à toute implémentation de recherche en plein texte (ils ne sont pas spécifiques à MongoDB).

Mots Vides

Les mots vides sont des mots non pertinents qui doivent être filtrés et supprimés du texte. Par exemple : un, le, est, à, qui, etc.

Stemming

Le stemming est le procédé par lequel on réduit les mots à leur racine. Par exemple : les mots tels que tenant, tient, tenait, etc. ont une base commune qui est tenir.

Scoring

Une hiérarchisation à partir de la pertinence des résultats de recherche.

Alternatives à la recherche en plein texte dans MongoDB

Avant que MongoDB ne propose le concept d'index textuel, nous pouvions déjà modéliser nos données de façon à ce qu'elles supportent les recherches par mots-clés ou utiliser des expressions régulières pour mettre en oeuvre une telle fonctionnalité de recherche. Toutefois, ces approches présentaient des limites :

  • D'abord, aucune ne supporte les fonctionnalités telles que le stemming ou les mots vides, la hiérarchisation, etc.
  • L'utilisation de la recherche par mots-clés requiert la création d'index à plusieurs clés qui ne sont pas suffisants comparés au plein texte.
  • L'utilisation d'expressions régulières n'est pas efficace du point de vue de la performance, car ces expressions n'utilisent pas les index.
  • Par ailleurs, aucune de ces techniques ne peut être utilisée pour exécuter des recherches sur des phrases (comme une requête sur les 'films réalisés en 2015') ou des requêtes pondérées.

À part ces approches, pour des applications de recherches plus complexes et plus avancées, il existe des solutions alternatives telles que Elastic Search ou Solr. Mais l'utilisation de ces solutions accroît la complexité architecturale de l'application, car MongoDB doit s'adresser à une base de données externe supplémentaire.

Remarquez bien que la recherche en plein texte de MongoDB n'est pas proposée comme une solution de rechange aux moteurs de recherche de bases de données tels qu'Elastic ou Solr. Cependant, il peut être utilisé efficacement pour la majorité des applications construites à partir de MongoDB aujourd'hui.

Introduction à la recherche en plein texte de MongoDB

Lorsque vous utilisez la recherche en plein texte de MongoDB, vous pouvez définir un index textuel sur tout champ du document dont la valeur est une chaîne de caractères ou un array de chaînes de caractères. Lorsqu'on crée un index textuel sur un champ, MongoDB tokenize et applique un stemming sur le contenu textuel du champ indexé, et configure les index.

Pour mieux comprendre, prenons un cas pratique. Je vous propose de suivre le tutoriel avec moi en testant les exemples dans l'interpréteur de commande mongo (mongo shell). Nous allons tout d'abord créer un échantillon de données que nous utiliserons tout au long de cet article, puis nous passerons à l'examen des concepts-clés.

Pour cet article, nous allons créer une collection messages qui enregistre des documents ayant la structure suivante :

Nous insérons quelques documents en utilisant la commande insert afin de créer notre échantillon de données :

Créer un index textuel

Pour créer un index textuel, on procède comme avec un index normal, à cette différence près qu'on spécifie text au lieu d'indiquer l'ordre croissant/décroissant.

Indexer un champ simple

On crée un index textuel sur le champ subject de notre document en utilisant la commande suivante :

Pour tester notre index sur le champ subject, nous allons effectuer une recherche dans le document en utilisant l'opérateur $text. Nous allons chercher tous les documents qui contiennent le mot dogs dans le champ subject.

Étant donné que nous effectuons une recherche textuelle, nous pouvons également être intéressés par les statistiques relatives à la pertinence des documents trouvés. Pour ce faire, nous allons utiliser  l'expression { $meta: "textScore" } qui nous fournit des informations sur les traitements opérés par $text. Nous allons également trier les documents par leur score textScore grâce à la commande sort. Plus le textScore est élevé, et plus le résultat est pertinent.

La requête précédente retourne les documents suivants, contenant le mot dogs dans leur champ subject.

Comme vous pouvez le voir, le premier document a un score égal à 1 (puisque le mot dog apparaît deux fois dans son sujet) alors que le second document a un score de 0.66. La requête a également trié les documents retournés dans l'ordre décroissant de leur score.

Une question qui vous est sans doute venue immédiatement à l'esprit est : comment se fait-il que le moteur de recherche prenne le mot dog en considération  alors que nous avons fait une recherche sur dogs (avec un 's') ? Vous vous rappelez notre discussion sur le stemming, ce procédé par lequel tous les mots-clés sont réduits à leur racine ? Eh bien, c'est la raison pour laquelle le mot-clé dogs est réduit à dog.

Indexer des champs multiples (indexation composée)

Bien souvent, nous aurons besoin de faire une recherche textuelle sur plusieurs champs d'un document. Dans notre exemple, nous allons réaliser une indexation composée sur les champs subject et content. Exécutez la commande suivante dans mongo shell :

Est-ce que ça a marché ? Non !! Le fait de créer un deuxième index vous renvoie un message d'erreur disant que l'index de recherche plein texte existe déjà. Pourquoi cela ? La réponse est que les index textuels sont limités à un par collection. Du coup, si vous voulez créer un autre index textuel, vous allez devoir laisser tomber le premier (dropIndex) et en créer un nouveau (createIndex).

Après avoir supprimé l'ancien index et en avoir créé un nouveau comme indiqué ci-dessus, faites une nouvelle recherche sur tous les documents contenant le mot cat.

La requête précédente devrait retourner les documents suivants :

Vous pouvez constater que le score du premier document, qui contient le mot cat à la fois dans les champs subject et content, est plus élevé.

Indexer tout le document (indexation wildcard)

Dans le dernier exemple, nous avons utilisé un index combiné sur les champs subject et content. Mais dans certains cas, vous aurez besoin de rechercher tous les contenus textuels de vos documents.

Par exemple, imaginons que nous enregistrons des emails dans des documents MongoDB. Dans le cas des emails, tous les champs, y compris l'expéditeur, le destinataire, l'objet et le corps du message, doivent pouvoir faire l'objet de la recherche. Dans de tels cas, vous pouvez indexer tous les champs contenant des chaînes de caractères en utilisant le "joker" (wildcard) $**.

La requête ressemble à ceci (n'oubliez pas de supprimer l'index précédent avant d'en créer un nouveau) :

Cette commande créera automatiquement des index textuels sur tous les champs de notre document contenant des chaînes de caractères. Faisons un test en insérant un nouveau document contenant un nouveau champ location :

À présent, si vous faites une recherche sur le mot chicago (voir la requête ci-dessous), il retournera le document que nous venons d'insérer.

Quelques petites choses sur lesquelles je voudrais insister :

  • Remarquez que nous n'avons pas défini explicitement d'index sur le champ location après avoir inséré un nouveau document. Ceci parce que nous avons déjà défini un index textuel sur le document tout entier via l'opérateur $**.
  • Les index wildcard ("joker") peuvent être lents, surtout si vos données sont nombreuses. Planifiez vos index judicieusement car ils peuvent avoir un impact sur la performance.

Recherche avancée

Recherche sur les phrases

Vous pouvez faire des recherches sur des phrases comme "oiseaux malins aimant cuisiner" via les index textuels. Par défaut, la recherche sur la phrase fait une recherche utilisant OR (ou) sur tous les mots-clés spécifiés, autrement dit il va chercher les documents contenant les mots malin, oiseau, aimer ou cuisiner.

Cette requête retournera les documents suivants :

Au cas où vous voudriez faire une recherche exacte, (correspondant à AND logique), vous pouvez le faire en entourant de guillemets doubles le texte recherché.

Cette requête retournera le document suivant qui contient les termes "cook food" ensemble :

Recherche négative

Si vous préfixez un mot de recherche avec un signe moins (-), les documents contenant le terme nié seront exclus. Par exemple, essayez de faire une recherche sur tout document contenant le mot rat mais ne contenant pas le mot birds, en utilisant la requête suivante :

En coulisses

Une fonctionnalité importante que je n'ai pas encore dévoilée est la possibilité d'aller voir en coulisses la façon dont est appliqué le stemming sur les mots-clés, les mots-vides, le mots niés, etc. $explain vient à notre secours. Vous pouvez la requête explain ("expliquer") en passant true comme premier paramètre, ce qui vous donnera des statistiques détaillées sur l'exécution de la requête.

Si vous jetez un coup d'oeil à l'objet queryPlanner retourné par la commande explain, vous pourrez voir comment MongoDB a parsé la chaîne de caractère de la requête. Remarquez comment il a laissé de côté les mots-vides comme who et appliqué un stemming à dogs qui est devenu dog.

Vous pouvez également voir les termes écartés de notre recherche et les phrases utilisées dans la section parseTextQuery ci-dessous.

La requête explain sera très utile quand nous serons amenés à effectuer des recherches plus complexes et que nous voudrons en analyser les résultats.

Recherche textuelle pondérée

Quand nous avons des indexes sur plus d'un champ du document, il est fréquent qu'un champ soit plus important (qu'il ait plus de poids) qu'un autre. Par exemple, si vous faites une recherche à travers un blog, le titre du blog aura le plus de poids, suivi par le contenu du blog.

Le poids par défaut de chaque champ indexé est égal à 1. Pour attribuer des poids reatifs aux champs indexés, vous pouvez inclure l'option weights lorsque vous utilisez la commande createIndex .

Prenons un exemple pour mieux comprendre. Si vous essayez de chercher le mot cook avec nos index actuels, vous aurez deux résultats, les deux documents ayant le même score.

Maintenant, modifions nos index en intégrant le poids : avec notre champ subject qui a un poids égal à 3 alors que le champ content a un poids de 1.

Refaites la recherche sur cook, et vous verrez que le document qui contient ce mot dans le champ subject a maintenant un score plus élevé (de 2 contre 0.66).

Partitionner les index textuels

À mesure que le nombre de données stockées dans nos applications augmente, la taille des index textuels s'accroît. Cette augmentation de la taille des index textuels a pour conséquence que MongoDB doit faire des recherches sur une quantité considérable d'entrées indexées à chaque fois qu'une recherche est effectuée.

Une bonne technique pour conserver l'efficacité de votre recherche textuelle consiste à limiter le nombre d'entrées indexées scannées en utilisant les conditions d'égalité via une recherche habituelle $text. Un exemple courant serait la recherche de tous les articles écrits pendant pendant un mois ou une année donnée, ou sur tous les articles ayant un tag ou une catégorie donnée.

Si vous remarquez que les documents sur lesquels vous travaillez ont un champ year que nous n'avons pas encore utilisé. Un scénario courant serait la recherche de messages par année, ainsi que la recherche en plein texte.

Pour cela, nous pouvons créer un index composé qui spécifie un index croissant/décroissant sur year suivi d'un index textuel sur le champ subject. Ce faisant, nous réalisons deux choses importantes :

  • Nous partitionnons logiquement la collection entière en sous-ensembles séparés par année.
  • Cela limite la recherche textuelle à scanner uniquement les documents correspondant à une année spécifique.

Effacez les index (dropIndex) et créez un nouvel index composé sur (year, subject) :

Exéutez maintenant la requête suivante pour chercher tous les messages créés en 2015 et contenant le mot cats :

La requête retourne uniquement un document, comme nous nous y attendions. Si vous faites explain sur cette requête dans les executionStats, vous trouverez que le nombre total de documents examinés (totalDocsExamined) est 1, ce qui confirme que notre nouvel index a été utilisé correctement et que MongoDB n'a eu besoin de scanner qu'un seul document, en ignorant les autres en toute sécurité puisqu'ils nétaient pas datés de 2015.

Index textuels, les avantages

Que peuvent faire de plus les index textuels ?

Nous avons parcouru un long chemin dans cet article sur les index textuels. Il y a encore beaucoup d'autres concepts que vous pouvez expérimenter avce les index textuels. Mais en raison des limites de cet article, nous ne pourrons pas entrer dans les détails aujourd'hui. Cependant, jetons un coup d'oeil à ces fonctionnalités :

  • Les index textuels offrent un support multilingue, vous permettant d'effectuer des recherches dans différentes langues en utilisant l'opératuer $language. Actuellement, MongoDB supporte environ 15 langues, dont le français, l'allemand, le russe, etc.
  • Les index textuels peuvent être utilisés dans des requêtes agrégées.
  • Vous pouvez utiliser les opérateurs habituels pour les projections, filtres, limitations, tris, etc. en même temps que les index textuels.

Indexation textuelle de MongoDB vs. Bases de données externes

Si l'on garde à l'esprit que la recherche en plein texte de MongoDB ne remplace pas complètement les moteurs de recherche traditionnels utilisés avec MongoDB l'utilisation de la fonctionnalité native de MongoDB est recommandée pour les raisons suivantes :

  • Comme il ressort d'une conversation que j'ai eue récemment à MongoDB, la portée de la recherche en plein texte est tout à fait suffisante pour une majorité d'applicatons (environ 80%) construites avec MongoDB aujourd'hui.
  • Le fait de construire les capacités de recherche de votre application à l'intérieur de la même base de données d'application en réduit la complexité architecturale.
  • La recherche en plein texte de MongoDB fonctionne en temps réel, sans décalages ou mises à jour par lots. Les entrées d'index textuels sont mis à jour au moment même où vous insérez ou mettez à jour un document.
  • La recherche en plein texte étant intégrée dans les fonctionnalités de MongoDB, elle est en cohérence totale et fonctionne bien, y compris pour le sharding et la réplication.
  • Elle s'intègre parfaitement à vos fonctionnalités Mongo existantes comme les filtres, l'agrégation, les mises à jour, etc.

Index textuels : inconvénients

La recherche en plein texte est une fonctionnalité relativement récente dans MongoDB, de ce fait il lui manque encore certaines fonctionnalités. Je les diviserai en trois catégories. Passons-les en revue.

Fonctionnalités manquantes dans la recherche en plein texte

  • Les index textuels n'ont pas encore la capacité d'intégrer des interfaces pluggables telles que les stemmers ou les mots-vides pluggables, etc.
  • Ils ne supportent pas actuellement les fonctionnalités comme la recherche basée sur les synonymes, les mots similaires, etc.
  • Ils n'enregistrent pas les positions de termes, c'est à dire le nombre de mots séparant deux mots-clés.
  • Vous ne pouvez pas spécifier l'ordre de tri d'une expression triée à partir d'un index textuel.

Restrictions dans les fonctionnalités existantes

  • Un index de texte composé ne peut pas inclure un autre type d'index, comme des index à clés multiples ou des index géospaciaux. De plus, si votre index textuel composé inclut des clés d'index avant la clé d'index textuel, toutes les requêtes doivent spécifier les opérateurs d'égalité pour les clés qui précèdent.
  • Il y a quelques limitations relatives aux requêtes. Par exemple, une requête ne peut spécifier qu'une seule expression $text, vous ne pouvez pas utiliser $text avec $nor, vous ne pouvez pas utiliser la commande hint() avec $text, si vous utilisez $text avec $or vous devez indexer toutes les clauses de votre expression $or, etc.

Inconvénients liés à la performance

  • Les index textuels créent un overhead lorsqu'on insère de nouveaux documents. Cela impacte le débit d'insertion.
  • Certaines requêtes comme les recherches sur les phrases peuvent être relativement lentes.

Pour conclure

La recherche en plein texte est depuis toujours l'une des fonctionnalités les plus demandées de MongoDB. Dans cet article, nous avons d'abord introduit ce qu'était la recherche en plein texte, puis nous avons exposé les bases de la création d'index textuels.

Nous avons ensuite exploré l'indexation composée, l'indexation avec "wilcards", les recherches par phrases et par négation. À partir de là, nous avons exploré quelques concepts importants comme l'analyse d'index textuels, la recherche pondérée et le partitionnage logique des index. On peut s'attendre à des mises à jour majeures de cette fonctionnalité dans les prochaines versions de MongoDB.

Je vous recommande d'expérimenter la recherche en plein texte et de partager vos avis. Si vous l'avez déjà implémentée dans votre application, merci de partager votre expérience ici-même. Enfin, n'hésitez pas à faire part de vos questions, remarques et suggestions sur cet article dans les commentaires.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.